XNAP: add encode/decode and unit test for Xn RAN Paging

- Add RAN Paging message type definitions, including paging DRX
  enumeration (§9.2.3.66) and RAN paging area cell-list
  structure (§9.2.3.38)
- Implement encoder and decoder for RAN Paging
    RAN Paging (3GPP TS 38.423v16.2.0 §9.1.1.7)
    - UE Identity Index Value (M)
    - UE RAN Paging Identity (M)
    - Paging DRX (M)
    - RAN Paging Area (M)
- Add equality check and memory management helpers
- Add required ASN.1 header includes for RAN Paging IEs
- Extend XNAP library unit tests to cover RAN Paging

Reviewed-by: Venkatareddy Akumalla <venkatareddy.akumalla@openairinterface.org>
Signed-off-by: Mohammed Safwan <mohammed.safwan@openairinterface.org>
This commit is contained in:
Mohammed Safwan
2026-06-12 17:47:43 +05:30
parent e7f12fdd61
commit b31846c62f
5 changed files with 346 additions and 0 deletions

View File

@@ -400,4 +400,54 @@ typedef struct {
xnap_ngran_cgi_t target_cgi;
} xnap_handover_success_t;
/* 3GPP TS 38.423 9.2.3.66 Paging DRX */
typedef enum {
XNAP_PAGING_DRX_32 = 0,
XNAP_PAGING_DRX_64,
XNAP_PAGING_DRX_128,
XNAP_PAGING_DRX_256,
XNAP_PAGING_DRX_512,
XNAP_PAGING_DRX_1024,
} xnap_paging_drx_t;
typedef enum {
XNAP_RAN_PAGING_AREA_CELL_LIST = 0,
XNAP_RAN_PAGING_AREA_RAN_AREA_ID,
} xnap_ran_paging_area_choice_t;
/* RAN Area ID entry: TAC (M) + optional RANAC */
typedef struct {
uint32_t tac; /* 24-bit Tracking Area Code (M) */
bool ranac_present;
uint8_t ranac; /* RAN Area Code 0..255 (O) */
} xnap_ran_area_id_t;
/* 3GPP TS 38.423 9.2.3.38 RAN Paging Area */
typedef struct {
plmn_id_t plmn;
xnap_ran_paging_area_choice_t choice;
union {
struct {
uint8_t num_cells;
uint64_t *cell_ids; /* NR-Cell-Identity, 36-bit values */
};
struct {
uint8_t num_ran_area_ids;
xnap_ran_area_id_t *ran_area_ids;
};
};
} xnap_ran_paging_area_t;
/* 3GPP TS 38.423 9.1.1.7 RAN Paging */
typedef struct {
/* UE Identity Index Value 10-bit index (M) */
uint16_t ue_identity_index_value;
/* UE RAN Paging Identity I-RNTI, 40-bit (M) */
uint64_t ue_ran_paging_identity;
/* Paging DRX (M) */
xnap_paging_drx_t paging_drx;
/* RAN Paging Area (M) */
xnap_ran_paging_area_t ran_paging_area;
} xnap_ran_paging_t;
#endif /* XNAP_MESSAGES_TYPES_H_ */

View File

@@ -1488,3 +1488,225 @@ void free_xnap_handover_success(xnap_handover_success_t *msg)
// Nothing to free
UNUSED(msg);
}
/**
* @brief XnAP RAN Paging encoding
*/
XNAP_XnAP_PDU_t *encode_xnap_ran_paging(const xnap_ran_paging_t *msg)
{
XNAP_XnAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu));
pdu->present = XNAP_XnAP_PDU_PR_initiatingMessage;
asn1cCalloc(pdu->choice.initiatingMessage, initMsg);
initMsg->procedureCode = XNAP_ProcedureCode_id_rANPaging;
initMsg->criticality = XNAP_Criticality_reject;
initMsg->value.present = XNAP_InitiatingMessage__value_PR_RANPaging;
XNAP_RANPaging_t *out = &initMsg->value.choice.RANPaging;
/* UE Identity Index Value (M) 10-bit BIT STRING */
asn1cSequenceAdd(out->protocolIEs.list, XNAP_RANPaging_IEs_t, ie1);
ie1->id = XNAP_ProtocolIE_ID_id_UEIdentityIndexValue;
ie1->criticality = XNAP_Criticality_reject;
ie1->value.present = XNAP_RANPaging_IEs__value_PR_UEIdentityIndexValue;
XNAP_UEIdentityIndexValue_t *ue_idx = &ie1->value.choice.UEIdentityIndexValue;
ue_idx->present = XNAP_UEIdentityIndexValue_PR_indexLength10;
BIT_STRING_t *idx_bs = &ue_idx->choice.indexLength10;
UEIDENTITYINDEX_TO_BIT_STRING(msg->ue_identity_index_value, idx_bs);
/* UE RAN Paging Identity (M) I-RNTI, 40-bit BIT STRING */
asn1cSequenceAdd(out->protocolIEs.list, XNAP_RANPaging_IEs_t, ie2);
ie2->id = XNAP_ProtocolIE_ID_id_UERANPagingIdentity;
ie2->criticality = XNAP_Criticality_ignore;
ie2->value.present = XNAP_RANPaging_IEs__value_PR_UERANPagingIdentity;
XNAP_UERANPagingIdentity_t *ue_paging_id = &ie2->value.choice.UERANPagingIdentity;
ue_paging_id->present = XNAP_UERANPagingIdentity_PR_i_RNTI_full;
BIT_STRING_t *rnti_bs = &ue_paging_id->choice.i_RNTI_full;
IRNTI_TO_BIT_STRING(msg->ue_ran_paging_identity, rnti_bs);
/* Paging DRX (M) */
asn1cSequenceAdd(out->protocolIEs.list, XNAP_RANPaging_IEs_t, ie3);
ie3->id = XNAP_ProtocolIE_ID_id_PagingDRX;
ie3->criticality = XNAP_Criticality_ignore;
ie3->value.present = XNAP_RANPaging_IEs__value_PR_PagingDRX;
ie3->value.choice.PagingDRX = msg->paging_drx;
/* RAN Paging Area (M) */
asn1cSequenceAdd(out->protocolIEs.list, XNAP_RANPaging_IEs_t, ie4);
ie4->id = XNAP_ProtocolIE_ID_id_RANPagingArea;
ie4->criticality = XNAP_Criticality_reject;
ie4->value.present = XNAP_RANPaging_IEs__value_PR_RANPagingArea;
XNAP_RANPagingArea_t *paging_area = &ie4->value.choice.RANPagingArea;
MCC_MNC_TO_PLMNID(msg->ran_paging_area.plmn.mcc,
msg->ran_paging_area.plmn.mnc,
msg->ran_paging_area.plmn.mnc_digit_length,
&paging_area->pLMN_Identity);
if (msg->ran_paging_area.choice == XNAP_RAN_PAGING_AREA_CELL_LIST) {
paging_area->rANPagingAreaChoice.present = XNAP_RANPagingAreaChoice_PR_cell_List;
asn1cCalloc(paging_area->rANPagingAreaChoice.choice.cell_List, cell_list);
for (int i = 0; i < msg->ran_paging_area.num_cells; i++) {
asn1cSequenceAdd(cell_list->list, XNAP_NG_RAN_Cell_Identity_t, cell_item);
cell_item->present = XNAP_NG_RAN_Cell_Identity_PR_nr;
NR_CELL_ID_TO_BIT_STRING(msg->ran_paging_area.cell_ids[i], &cell_item->choice.nr);
}
} else {
paging_area->rANPagingAreaChoice.present = XNAP_RANPagingAreaChoice_PR_rANAreaID_List;
asn1cCalloc(paging_area->rANPagingAreaChoice.choice.rANAreaID_List, ran_area_list);
for (int i = 0; i < msg->ran_paging_area.num_ran_area_ids; i++) {
const xnap_ran_area_id_t *src = &msg->ran_paging_area.ran_area_ids[i];
asn1cSequenceAdd(ran_area_list->list, XNAP_RANAreaID_t, ran_area_item);
INT24_TO_OCTET_STRING(src->tac, &ran_area_item->tAC);
if (src->ranac_present) {
ran_area_item->rANAC = calloc_or_fail(1, sizeof(*ran_area_item->rANAC));
*ran_area_item->rANAC = src->ranac;
}
}
}
return pdu;
}
/**
* @brief XnAP RAN Paging decoding
*/
bool decode_xnap_ran_paging(xnap_ran_paging_t *out, const XNAP_XnAP_PDU_t *pdu)
{
_EQ_CHECK_INT(pdu->present, XNAP_XnAP_PDU_PR_initiatingMessage);
AssertError(pdu->choice.initiatingMessage != NULL, return false, "initiatingMessage is NULL");
_EQ_CHECK_LONG(pdu->choice.initiatingMessage->procedureCode, XNAP_ProcedureCode_id_rANPaging);
_EQ_CHECK_INT(pdu->choice.initiatingMessage->value.present, XNAP_InitiatingMessage__value_PR_RANPaging);
XNAP_RANPaging_t *in = &pdu->choice.initiatingMessage->value.choice.RANPaging;
XNAP_RANPaging_IEs_t *ie;
XNAP_LIB_FIND_IE(XNAP_RANPaging_IEs_t, ie, &in->protocolIEs.list, XNAP_ProtocolIE_ID_id_UEIdentityIndexValue, true);
XNAP_LIB_FIND_IE(XNAP_RANPaging_IEs_t, ie, &in->protocolIEs.list, XNAP_ProtocolIE_ID_id_UERANPagingIdentity, true);
XNAP_LIB_FIND_IE(XNAP_RANPaging_IEs_t, ie, &in->protocolIEs.list, XNAP_ProtocolIE_ID_id_PagingDRX, true);
XNAP_LIB_FIND_IE(XNAP_RANPaging_IEs_t, ie, &in->protocolIEs.list, XNAP_ProtocolIE_ID_id_RANPagingArea, true);
for (int i = 0; i < in->protocolIEs.list.count; i++) {
DevAssert(in->protocolIEs.list.array[i]);
ie = in->protocolIEs.list.array[i];
switch (ie->id) {
case XNAP_ProtocolIE_ID_id_UEIdentityIndexValue: {
_EQ_CHECK_INT(ie->value.present, XNAP_RANPaging_IEs__value_PR_UEIdentityIndexValue);
const XNAP_UEIdentityIndexValue_t *ue_idx = &ie->value.choice.UEIdentityIndexValue;
if (ue_idx->present != XNAP_UEIdentityIndexValue_PR_indexLength10)
return false;
out->ue_identity_index_value = BIT_STRING_to_uint16(&ue_idx->choice.indexLength10);
} break;
case XNAP_ProtocolIE_ID_id_UERANPagingIdentity: {
_EQ_CHECK_INT(ie->value.present, XNAP_RANPaging_IEs__value_PR_UERANPagingIdentity);
const XNAP_UERANPagingIdentity_t *ue_paging_id = &ie->value.choice.UERANPagingIdentity;
if (ue_paging_id->present != XNAP_UERANPagingIdentity_PR_i_RNTI_full)
return false;
out->ue_ran_paging_identity = BIT_STRING_to_uint64(&ue_paging_id->choice.i_RNTI_full);
} break;
case XNAP_ProtocolIE_ID_id_PagingDRX: {
_EQ_CHECK_INT(ie->value.present, XNAP_RANPaging_IEs__value_PR_PagingDRX);
out->paging_drx = ie->value.choice.PagingDRX;
} break;
case XNAP_ProtocolIE_ID_id_RANPagingArea: {
_EQ_CHECK_INT(ie->value.present, XNAP_RANPaging_IEs__value_PR_RANPagingArea);
const XNAP_RANPagingArea_t *paging_area = &ie->value.choice.RANPagingArea;
PLMNID_TO_MCC_MNC(&paging_area->pLMN_Identity,
out->ran_paging_area.plmn.mcc,
out->ran_paging_area.plmn.mnc,
out->ran_paging_area.plmn.mnc_digit_length);
switch (paging_area->rANPagingAreaChoice.present) {
case XNAP_RANPagingAreaChoice_PR_cell_List: {
out->ran_paging_area.choice = XNAP_RAN_PAGING_AREA_CELL_LIST;
const XNAP_NG_RAN_Cell_Identity_ListinRANPagingArea_t *cell_list = paging_area->rANPagingAreaChoice.choice.cell_List;
out->ran_paging_area.num_cells = cell_list->list.count;
out->ran_paging_area.cell_ids = calloc_or_fail(out->ran_paging_area.num_cells, sizeof(uint64_t));
for (int j = 0; j < cell_list->list.count; j++) {
const XNAP_NG_RAN_Cell_Identity_t *cell = cell_list->list.array[j];
if (cell->present != XNAP_NG_RAN_Cell_Identity_PR_nr)
return false;
BIT_STRING_TO_NR_CELL_IDENTITY(&cell->choice.nr, out->ran_paging_area.cell_ids[j]);
}
} break;
case XNAP_RANPagingAreaChoice_PR_rANAreaID_List: {
out->ran_paging_area.choice = XNAP_RAN_PAGING_AREA_RAN_AREA_ID;
const XNAP_RANAreaID_List_t *ran_area_list = paging_area->rANPagingAreaChoice.choice.rANAreaID_List;
out->ran_paging_area.num_ran_area_ids = ran_area_list->list.count;
out->ran_paging_area.ran_area_ids = calloc_or_fail(out->ran_paging_area.num_ran_area_ids, sizeof(xnap_ran_area_id_t));
for (int j = 0; j < ran_area_list->list.count; j++) {
const XNAP_RANAreaID_t *ran_area = ran_area_list->list.array[j];
xnap_ran_area_id_t *dst = &out->ran_paging_area.ran_area_ids[j];
OCTET_STRING_TO_INT24(&ran_area->tAC, dst->tac);
if (ran_area->rANAC) {
dst->ranac_present = true;
dst->ranac = *ran_area->rANAC;
}
}
} break;
default:
AssertError(0, return false, "Unknown RANPagingAreaChoice.present=%d\n", paging_area->rANPagingAreaChoice.present);
}
} break;
case XNAP_ProtocolIE_ID_id_PagingPriority:
PRINT_ERROR("XNAP_ProtocolIE_ID_id %ld not handled, skipping\n", ie->id);
break;
case XNAP_ProtocolIE_ID_id_AssistanceDataForRANPaging:
PRINT_ERROR("XNAP_ProtocolIE_ID_id %ld not handled, skipping\n", ie->id);
break;
case XNAP_ProtocolIE_ID_id_UERadioCapabilityForPaging:
PRINT_ERROR("XNAP_ProtocolIE_ID_id %ld not handled, skipping\n", ie->id);
break;
default:
PRINT_ERROR("XNAP_ProtocolIE_ID_id %ld unknown, skipping\n", ie->id);
break;
}
}
return true;
}
/**
* @brief XnAP RAN Paging equality function
*/
bool eq_xnap_ran_paging(const xnap_ran_paging_t *a, const xnap_ran_paging_t *b)
{
_EQ_CHECK_INT(a->ue_identity_index_value, b->ue_identity_index_value);
_EQ_CHECK_UINT64(a->ue_ran_paging_identity, b->ue_ran_paging_identity);
_EQ_CHECK_INT(a->paging_drx, b->paging_drx);
if (!eq_xnap_plmn(&a->ran_paging_area.plmn, &b->ran_paging_area.plmn))
return false;
_EQ_CHECK_INT(a->ran_paging_area.choice, b->ran_paging_area.choice);
if (a->ran_paging_area.choice == XNAP_RAN_PAGING_AREA_CELL_LIST) {
_EQ_CHECK_INT(a->ran_paging_area.num_cells, b->ran_paging_area.num_cells);
for (int i = 0; i < a->ran_paging_area.num_cells; i++)
_EQ_CHECK_UINT64(a->ran_paging_area.cell_ids[i], b->ran_paging_area.cell_ids[i]);
} else {
_EQ_CHECK_INT(a->ran_paging_area.num_ran_area_ids, b->ran_paging_area.num_ran_area_ids);
for (int i = 0; i < a->ran_paging_area.num_ran_area_ids; i++) {
_EQ_CHECK_UINT32(a->ran_paging_area.ran_area_ids[i].tac, b->ran_paging_area.ran_area_ids[i].tac);
_EQ_CHECK_INT(a->ran_paging_area.ran_area_ids[i].ranac_present, b->ran_paging_area.ran_area_ids[i].ranac_present);
if (a->ran_paging_area.ran_area_ids[i].ranac_present)
_EQ_CHECK_INT(a->ran_paging_area.ran_area_ids[i].ranac, b->ran_paging_area.ran_area_ids[i].ranac);
}
}
return true;
}
/**
* @brief XnAP RAN Paging memory management
*/
void free_xnap_ran_paging(xnap_ran_paging_t *msg)
{
if (msg->ran_paging_area.choice == XNAP_RAN_PAGING_AREA_CELL_LIST)
free(msg->ran_paging_area.cell_ids);
else
free(msg->ran_paging_area.ran_area_ids);
}

View File

@@ -15,6 +15,7 @@ XNAP_XnAP_PDU_t *encode_xnap_sn_status_transfer(const xnap_sn_status_transfer_t
XNAP_XnAP_PDU_t *encode_xnap_ue_context_release(const xnap_ue_context_release_t *msg);
XNAP_XnAP_PDU_t *encode_xnap_handover_cancel(const xnap_handover_cancel_t *msg);
XNAP_XnAP_PDU_t *encode_xnap_handover_success(const xnap_handover_success_t *msg);
XNAP_XnAP_PDU_t *encode_xnap_ran_paging(const xnap_ran_paging_t *msg);
bool decode_xnap_handover_request(xnap_handover_req_t *req, const XNAP_XnAP_PDU_t *pdu);
bool decode_xnap_handover_request_acknowledge(xnap_handover_req_ack_t *out, const XNAP_XnAP_PDU_t *pdu);
@@ -23,6 +24,7 @@ bool decode_xnap_sn_status_transfer(xnap_sn_status_transfer_t *out, const XNAP_X
bool decode_xnap_ue_context_release(xnap_ue_context_release_t *out, const XNAP_XnAP_PDU_t *pdu);
bool decode_xnap_handover_cancel(xnap_handover_cancel_t *out, const XNAP_XnAP_PDU_t *pdu);
bool decode_xnap_handover_success(xnap_handover_success_t *out, const XNAP_XnAP_PDU_t *pdu);
bool decode_xnap_ran_paging(xnap_ran_paging_t *out, const XNAP_XnAP_PDU_t *pdu);
bool eq_xnap_handover_request(const xnap_handover_req_t *a, const xnap_handover_req_t *b);
bool eq_xnap_handover_request_acknowledge(const xnap_handover_req_ack_t *a, const xnap_handover_req_ack_t *b);
@@ -32,6 +34,7 @@ bool eq_xnap_sn_status_transfer(const xnap_sn_status_transfer_t *a, const xnap_s
bool eq_xnap_ue_context_release(const xnap_ue_context_release_t *a, const xnap_ue_context_release_t *b);
bool eq_xnap_handover_cancel(const xnap_handover_cancel_t *a, const xnap_handover_cancel_t *b);
bool eq_xnap_handover_success(const xnap_handover_success_t *a, const xnap_handover_success_t *b);
bool eq_xnap_ran_paging(const xnap_ran_paging_t *a, const xnap_ran_paging_t *b);
void free_xnap_handover_request(xnap_handover_req_t *msg);
void free_xnap_handover_request_acknowledge(xnap_handover_req_ack_t *msg);
@@ -40,5 +43,6 @@ void free_xnap_sn_status_transfer(xnap_sn_status_transfer_t *msg);
void free_xnap_ue_context_release(xnap_ue_context_release_t *msg);
void free_xnap_handover_cancel(xnap_handover_cancel_t *msg);
void free_xnap_handover_success(xnap_handover_success_t *msg);
void free_xnap_ran_paging(xnap_ran_paging_t *msg);
#endif // XNAP_GNB_MOBILITY_MANAGEMENT_H_

View File

@@ -39,5 +39,15 @@
#include "XNAP_DRBBStatusTransfer18bitsSN.h"
#include "XNAP_DRBBStatusTransfer12bitsSN.h"
#include "XNAP_UEIdentityIndexValue.h"
#include "XNAP_UERANPagingIdentity.h"
#include "XNAP_PagingDRX.h"
#include "XNAP_RANPagingArea.h"
#include "XNAP_RANPagingAreaChoice.h"
#include "XNAP_NG-RAN-Cell-Identity-ListinRANPagingArea.h"
#include "XNAP_NG-RAN-Cell-Identity.h"
#include "XNAP_RANAreaID-List.h"
#include "XNAP_RANAreaID.h"
#endif // XNAP_LIB_INCLUDES_H

View File

@@ -726,6 +726,65 @@ static void test_xn_handover_success(void)
printf("%s() successful\n", __func__);
}
/**
* 11. XnAP RAN Paging Testing cell_List and rANAreaID_List variants
*/
static void test_xn_ran_paging(void)
{
/* ---------- create message ---------- */
plmn_id_t plmn0 = {.mcc = 208, .mnc = 95, .mnc_digit_length = 2};
uint64_t *cell_ids = calloc_or_fail(3, sizeof(uint64_t));
cell_ids[0] = 0x123456789ULL & 0xFFFFFFFFFULL;
cell_ids[1] = 0xABCDEF012ULL & 0xFFFFFFFFFULL;
cell_ids[2] = 0x100200300ULL & 0xFFFFFFFFFULL;
xnap_ran_area_id_t *ran_area_ids = calloc_or_fail(2, sizeof(xnap_ran_area_id_t));
ran_area_ids[0] = (xnap_ran_area_id_t){.tac = 0x001234, .ranac_present = true, .ranac = 7};
ran_area_ids[1] = (xnap_ran_area_id_t){.tac = 0x005678, .ranac_present = false};
xnap_ran_paging_t cases[] = {
{.ue_identity_index_value = 0x2A5,
.ue_ran_paging_identity = 0xABCDE01234ULL,
.paging_drx = XNAP_PAGING_DRX_128,
.ran_paging_area = {.plmn = plmn0,
.choice = XNAP_RAN_PAGING_AREA_CELL_LIST,
.num_cells = 3,
.cell_ids = cell_ids}},
{.ue_identity_index_value = 0x155,
.ue_ran_paging_identity = 0x1234567890ULL,
.paging_drx = XNAP_PAGING_DRX_64,
.ran_paging_area = {.plmn = plmn0,
.choice = XNAP_RAN_PAGING_AREA_RAN_AREA_ID,
.num_ran_area_ids = 2,
.ran_area_ids = ran_area_ids}},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(*cases); i++) {
/* ---------- encode ---------- */
XNAP_XnAP_PDU_t *xnenc = encode_xnap_ran_paging(&cases[i]);
AssertFatal(xnenc != NULL, "encode_xnap_ran_paging failed");
XNAP_XnAP_PDU_t *xndec = xnap_encode_decode(xnenc);
xnap_msg_free(xnenc);
/* ---------- decode ---------- */
xnap_ran_paging_t decoded = {0};
bool ret = decode_xnap_ran_paging(&decoded, xndec);
AssertFatal(ret, "decode_xnap_ran_paging failed");
xnap_msg_free(xndec);
/* ---------- equality ---------- */
ret = eq_xnap_ran_paging(&cases[i], &decoded);
AssertFatal(ret, "XnAP RAN Paging mismatch (case %zu)\n", i);
/* ---------- cleanup ---------- */
free_xnap_ran_paging(&decoded);
free_xnap_ran_paging(&cases[i]);
}
printf("%s() successful\n", __func__);
}
int main() {
printf("Starting XnAP Library Unit Tests...\n");
@@ -742,6 +801,7 @@ int main() {
test_xn_ue_context_release();
test_xn_handover_cancel();
test_xn_handover_success();
test_xn_ran_paging();
printf("All XnAP tests passed!\n");
return 0;