Merge remote-tracking branch 'origin/n2-handover' into integration_2025_w38 (!3334)
N2 handover
This MR introduces full support for N2 handover, covering handover
decision, preparation, execution, and notification between source and
target NG-RAN nodes.
Key features:
1. Handover Decision & Preparation:
- Implement N2 handover decision based on UE measurement reports.
- Generate the Handover Preparation Information message, including
RRCReconfiguration.
- Send Handover Required message from the source NG-RAN to the AMF.
2. Handover Execution:
- Decode and handle the Handover Request on the target NG-RAN.
- Process the Handover Command on the source gNB, triggering
RRCReconfiguration.
- Initiate Handover Notify after successful completion.
3 NGAP & RRC Enhancements:
- Implement NGAP encoding/decoding functions for Handover Required,
Handover Request, Handover Command, Handover Failure, and Handover
Notify (3GPP TS 38.413).
- Adds NG-RAN Status Transfer support (UL/DL) for PDCP COUNT
preservation during mobility:
- support for NGAP Uplink and Downlink RAN Status Transfer messages
(TS 38.413 §9.2.3.13-14) to enable proper PDCP COUNT value
transfer during handover or recovery: NGAP encoding/decoding for
RAN Status Transfer, PDCP helpers to extract and apply COUNT (HFN
+ SN) per DRB. This aligns with TS 38.300 §9.2.3.2.1 and TS 23.502
§4.9.1.3.3, ensuring PDCP status preservation in AM.
4. Testing:
- Introduce a Telnet command to manually trigger N2 handover using UE
ID and neighbor PCI.
- Add configuration file for testing N2 handover with a target gNB.
This commit is contained in:
@@ -283,6 +283,22 @@ typedef struct bearer_context_pdcp_config_s {
|
||||
bool pDCP_Reestablishment;
|
||||
} bearer_context_pdcp_config_t;
|
||||
|
||||
/* 9.3.1.35 3GPP TS 38.463 */
|
||||
typedef struct {
|
||||
// PDCP SN
|
||||
uint32_t sn;
|
||||
// HFN
|
||||
uint32_t hfn;
|
||||
} e1_pdcp_count_t;
|
||||
|
||||
/* 9.3.1.58 3GPP TS 38.463 */
|
||||
typedef struct {
|
||||
// UL Count Value
|
||||
e1_pdcp_count_t ul_count;
|
||||
// DL Count Value
|
||||
e1_pdcp_count_t dl_count;
|
||||
} e1_pdcp_status_info_t;
|
||||
|
||||
typedef struct drb_to_setup_s {
|
||||
long drbId;
|
||||
bearer_context_pdcp_config_t pdcp_config;
|
||||
@@ -384,6 +400,8 @@ typedef struct DRB_nGRAN_to_modify_s {
|
||||
bearer_context_pdcp_config_t *pdcp_config;
|
||||
// PDCP SN Status Request (O)
|
||||
bool pdcp_sn_status_requested;
|
||||
// PDCP SN Status Information
|
||||
e1_pdcp_status_info_t *pdcp_status;
|
||||
// DL UP Transport Layer Information (O) (clause 9.3.1.13, 9.3.2.1)
|
||||
int numDlUpParam;
|
||||
up_params_t DlUpParamList[E1AP_MAX_NUM_UP_PARAM];
|
||||
@@ -561,9 +579,8 @@ typedef struct DRB_nGRAN_modified_s {
|
||||
// Flow Failed List (O)
|
||||
int numQosFlowFailed;
|
||||
qos_flow_failed_t qosFlowsFailed[E1AP_MAX_NUM_QOS_FLOWS];
|
||||
// Old QoS Flow List (O)
|
||||
int numOldQosFlow;
|
||||
qos_flow_list_t oldQosFlows[E1AP_MAX_NUM_QOS_FLOWS];
|
||||
// PDCP SN Status Information (0)
|
||||
e1_pdcp_status_info_t *pdcp_status;
|
||||
} DRB_nGRAN_modified_t;
|
||||
|
||||
typedef struct DRB_nGRAN_failed_s {
|
||||
|
||||
@@ -71,6 +71,13 @@ MESSAGE_DEF(NGAP_UE_CTXT_MODIFICATION_FAIL , MESSAGE_PRIORITY_MED, ngap_ue_ctxt_
|
||||
MESSAGE_DEF(NGAP_PDUSESSION_SETUP_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_setup_resp_t , ngap_pdusession_setup_resp)
|
||||
MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_resp_t , ngap_pdusession_modify_resp)
|
||||
MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdusession_release_resp_t , ngap_pdusession_release_resp)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_REQUEST_ACKNOWLEDGE, MESSAGE_PRIORITY_MED, ngap_handover_request_ack_t, ngap_handover_request_ack)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_NOTIFY, MESSAGE_PRIORITY_MED, ngap_handover_notify_t, ngap_handover_notify)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_CANCEL, MESSAGE_PRIORITY_MED, ngap_handover_cancel_t, ngap_handover_cancel)
|
||||
MESSAGE_DEF(NGAP_UL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_ul_ran_status_transfer)
|
||||
MESSAGE_DEF(NGAP_DL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_dl_ran_status_transfer)
|
||||
|
||||
/* NGAP -> RRC messages */
|
||||
MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas )
|
||||
@@ -81,6 +88,9 @@ MESSAGE_DEF(NGAP_PDUSESSION_SETUP_REQ , MESSAGE_PRIORITY_MED, ngap_pd
|
||||
MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_REQ , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_req_t , ngap_pdusession_modify_req )
|
||||
MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_COMMAND , MESSAGE_PRIORITY_MED, ngap_pdusession_release_command_t , ngap_pdusession_release_command)
|
||||
MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_COMMAND, MESSAGE_PRIORITY_MED, ngap_ue_release_command_t , ngap_ue_release_command)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_REQUEST, MESSAGE_PRIORITY_MED, ngap_handover_request_t, ngap_handover_request)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_COMMAND, MESSAGE_PRIORITY_MED, ngap_handover_command_t, ngap_handover_command)
|
||||
MESSAGE_DEF(NGAP_HANDOVER_CANCEL_ACK, MESSAGE_PRIORITY_MED, ngap_handover_cancel_ack_t, ngap_handover_cancel_ack)
|
||||
|
||||
/* NGAP <-> RRC messages (can be initiated either by MME or gNB) */
|
||||
MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_REQ , MESSAGE_PRIORITY_MED, ngap_ue_release_req_t , ngap_ue_release_req)
|
||||
|
||||
@@ -65,11 +65,22 @@
|
||||
#define NGAP_PDUSESSION_SETUP_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_setup_req
|
||||
#define NGAP_PDUSESSION_MODIFY_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_modify_req
|
||||
#define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind
|
||||
#define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required
|
||||
#define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure
|
||||
#define NGAP_HANDOVER_REQUEST(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request
|
||||
#define NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request_ack
|
||||
#define NGAP_HANDOVER_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_command
|
||||
#define NGAP_HANDOVER_NOTIFY(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_notify
|
||||
#define NGAP_HANDOVER_CANCEL(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel
|
||||
#define NGAP_HANDOVER_CANCEL_ACK(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel_ack
|
||||
|
||||
#define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req
|
||||
#define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command
|
||||
#define NGAP_PDUSESSION_RELEASE_RESPONSE(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_resp
|
||||
|
||||
#define NGAP_UL_RAN_STATUS_TRANSFER(mSGpTR) (mSGpTR)->ittiMsg.ngap_ul_ran_status_transfer
|
||||
#define NGAP_DL_RAN_STATUS_TRANSFER(mSGpTR) (mSGpTR)->ittiMsg.ngap_dl_ran_status_transfer
|
||||
|
||||
//-------------------------------------------------------------------------------------------//
|
||||
|
||||
/* Length of the transport layer address string
|
||||
@@ -227,6 +238,15 @@ typedef struct ngap_mobility_restriction_s{
|
||||
plmn_id_t serving_plmn;
|
||||
}ngap_mobility_restriction_t;
|
||||
|
||||
/* PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */
|
||||
typedef struct {
|
||||
uint8_t nb_qos;
|
||||
pdusession_level_qos_parameter_t qos[QOSFLOW_MAX_VALUE];
|
||||
pdu_session_type_t pdu_session_type;
|
||||
// UPF endpoint of the NG-U (N3) transport bearer
|
||||
gtpu_tunnel_t n3_incoming;
|
||||
} pdusession_transfer_t;
|
||||
|
||||
typedef enum pdusession_qosflow_mapping_ind_e{
|
||||
QOSFLOW_MAPPING_INDICATION_UL = 0,
|
||||
QOSFLOW_MAPPING_INDICATION_DL = 1,
|
||||
@@ -463,6 +483,244 @@ typedef struct ngap_uplink_nas_s {
|
||||
byte_array_t nas_pdu;
|
||||
} ngap_uplink_nas_t;
|
||||
|
||||
typedef struct target_cell_id_s {
|
||||
plmn_id_t plmn_identity;
|
||||
uint32_t nrCellIdentity;
|
||||
} cell_id_t;
|
||||
|
||||
typedef struct target_ran_node_id_s {
|
||||
uint32_t targetgNBId;
|
||||
plmn_id_t plmn_identity;
|
||||
uint32_t tac;
|
||||
} target_ran_node_id_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.3.1.29 */
|
||||
typedef struct {
|
||||
// QoS Flow Identifier
|
||||
uint8_t qfi;
|
||||
} qosflow_info_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.3.1.29 */
|
||||
typedef struct {
|
||||
// PDU Session ID
|
||||
uint8_t pdusession_id;
|
||||
// QoS Flow Information List
|
||||
uint8_t nb_of_qos_flow;
|
||||
qosflow_info_t qos_flow_info[QOSFLOW_MAX_VALUE];
|
||||
} pdusession_resource_info_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.3.1.97 */
|
||||
typedef struct {
|
||||
// Global Cell ID
|
||||
cell_id_t id;
|
||||
// Cell type
|
||||
uint8_t type;
|
||||
// Time UE Stayed in Cell
|
||||
long time_in_cell;
|
||||
// Cause
|
||||
ngap_cause_t *cause;
|
||||
} last_visited_ngran_cell_info_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.3.1.29 */
|
||||
typedef struct {
|
||||
// RRC Container: HandoverPreparationInformation message
|
||||
byte_array_t handoverInfo;
|
||||
// Target Cell ID
|
||||
cell_id_t targetCellId;
|
||||
// PDU Session Resource Information List
|
||||
uint16_t nb_pdu_session_resource;
|
||||
pdusession_resource_info_t pdu_session_resource[NGAP_MAX_PDU_SESSION];
|
||||
// UE History Information
|
||||
last_visited_ngran_cell_info_t ue_history_info;
|
||||
} source_to_target_transparent_container_t;
|
||||
|
||||
typedef enum {
|
||||
HANDOVER_TYPE_INTRA5GS, // Intra5GS: NG-RAN node to NG-RAN node
|
||||
HANDOVER_TYPE_5GSTOEPS, // 5GStoEPS: NG-RAN node to eNB
|
||||
HANDOVER_TYPE_EPSTO5GS, // EPSto5GS: eNB to NG-RAN node
|
||||
HANDOVER_TYPE_5GSTOUTRA, // 5GStoUTRA: NG-RAN node to UTRA
|
||||
} ho_type_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.2.3.1 */
|
||||
typedef struct {
|
||||
// PDU Session ID
|
||||
uint8_t pdusession_id;
|
||||
// Handover Required Transfer
|
||||
byte_array_t ho_required_transfer;
|
||||
} pdusession_resource_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.2.3.1 */
|
||||
typedef struct {
|
||||
// RAN UE NGAP ID
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
// AMF UE NGAP ID
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// Handover Type
|
||||
ho_type_t handoverType;
|
||||
// Cause
|
||||
ngap_cause_t cause;
|
||||
// Target ID
|
||||
target_ran_node_id_t target_gnb_id;
|
||||
// PDU Session Resource List
|
||||
uint8_t nb_of_pdusessions;
|
||||
pdusession_resource_t pdusessions[NGAP_MAX_PDU_SESSION];
|
||||
// Source to Target Transparent Container
|
||||
source_to_target_transparent_container_t *source2target;
|
||||
} ngap_handover_required_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.2.3.6 */
|
||||
typedef struct {
|
||||
// AMF UE NGAP ID (M)
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// Cause (M)
|
||||
ngap_cause_t cause;
|
||||
} ngap_handover_failure_t;
|
||||
|
||||
typedef struct {
|
||||
// Next-Hop NH
|
||||
uint8_t next_hop[SECURITY_KEY_LENGTH];
|
||||
// Next Hop Chaining Count
|
||||
uint8_t next_hop_chain_count;
|
||||
} ngap_security_context_t;
|
||||
|
||||
/* Handover Request (3GPP TS 38.413 9.2.3.4)
|
||||
PDU Session Resource Setup Item */
|
||||
typedef struct {
|
||||
// PDU Session ID
|
||||
uint8_t pdusession_id;
|
||||
pdu_session_type_t pdu_session_type;
|
||||
// S-NSSAI
|
||||
nssai_t nssai;
|
||||
// Handover Required Transfer
|
||||
pdusession_transfer_t pdusessionTransfer;
|
||||
} ho_request_pdusession_t;
|
||||
|
||||
/* 3GPP TS 38.413 9.2.3.4 */
|
||||
typedef struct {
|
||||
// AMF UE NGAP ID
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// Handover Type
|
||||
ho_type_t ho_type;
|
||||
// Cause
|
||||
ngap_cause_t cause;
|
||||
// UE Aggregate Maximum Bit Rate
|
||||
ngap_ambr_t ue_ambr;
|
||||
// UE Security Capabilities
|
||||
ngap_security_capabilities_t security_capabilities;
|
||||
// Security Context
|
||||
ngap_security_context_t security_context;
|
||||
// PDU Session Resource Setup List
|
||||
uint8_t nb_of_pdusessions;
|
||||
ho_request_pdusession_t pduSessionResourceSetupList[NGAP_MAX_PDU_SESSION];
|
||||
// Allowed NSSAI
|
||||
uint8_t nb_allowed_nssais;
|
||||
nssai_t allowed_nssai[8];
|
||||
// Source to Target Transparent Container contents
|
||||
uint64_t nr_cell_id;
|
||||
byte_array_t ue_ho_prep_info;
|
||||
byte_array_t ue_cap;
|
||||
// Mobility Restriction List
|
||||
ngap_mobility_restriction_t *mobility_restriction;
|
||||
// GUAMI
|
||||
nr_guami_t guami;
|
||||
} ngap_handover_request_t;
|
||||
|
||||
/* 9.3.4.11 3GPP TS 38.413 */
|
||||
typedef struct {
|
||||
// QoS Flow Setup Response List
|
||||
uint8_t nb_of_qos_flow;
|
||||
pdusession_associate_qosflow_t qos_setup_list[QOSFLOW_MAX_VALUE];
|
||||
// DL NG-U UP TNL Information
|
||||
uint32_t gtp_teid;
|
||||
transport_layer_addr_t gNB_addr;
|
||||
} ho_request_ack_transfer_t;
|
||||
|
||||
/* 9.2.3.5 3GPP TS 38.413 */
|
||||
typedef struct {
|
||||
// PDU Session ID (M)
|
||||
uint8_t pdu_session_id;
|
||||
// Handover Request Acknowledge Transfer (M)
|
||||
ho_request_ack_transfer_t ack_transfer;
|
||||
} pdu_session_resource_admitted_t;
|
||||
|
||||
typedef struct {
|
||||
// AMF UE NGAP ID
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
// RAN UE NGAP ID
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// PDU Session Resource Admitted List
|
||||
pdu_session_resource_admitted_t pdusessions[NGAP_MAX_PDU_SESSION];
|
||||
uint8_t nb_of_pdusessions;
|
||||
// Target to Source Transparent Container
|
||||
byte_array_t target2source;
|
||||
} ngap_handover_request_ack_t;
|
||||
|
||||
/* 9.3.4.10 3GPP TS 38.413 */
|
||||
typedef struct {
|
||||
// QoS Flow to be Forwarded List
|
||||
uint8_t nb_of_qos_flow;
|
||||
pdusession_associate_qosflow_t qos_setup_list[QOSFLOW_MAX_VALUE];
|
||||
// UL Forwarding UP TNL Information
|
||||
uint32_t gtp_teid;
|
||||
transport_layer_addr_t gNB_addr;
|
||||
} ho_command_transfer_t;
|
||||
|
||||
typedef struct {
|
||||
// PDU Session ID (M)
|
||||
uint8_t pdusession_id;
|
||||
// Handover Command Transfer (M)
|
||||
ho_command_transfer_t ho_command_transfer;
|
||||
} pdusession_resource_handover_t;
|
||||
|
||||
typedef struct {
|
||||
// AMF UE NGAP ID
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// RAN UE NGAP ID
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
// Handover Type
|
||||
ho_type_t handoverType;
|
||||
// PDU Session Resource Handover List
|
||||
uint8_t nb_of_pdusessions;
|
||||
pdusession_resource_handover_t pdu_sessions[NGAP_MAX_PDU_SESSION];
|
||||
// Target to Source Transparent Container
|
||||
byte_array_t handoverCommand;
|
||||
} ngap_handover_command_t;
|
||||
|
||||
/* 9.3.1.16 3GPP TS 38.413 */
|
||||
typedef struct {
|
||||
// NR user location information
|
||||
target_ran_node_id_t target_ng_ran;
|
||||
uint32_t nrCellIdentity;
|
||||
} user_location_information_t;
|
||||
|
||||
/* 9.2.3.7 3GPP TS 38.413 */
|
||||
typedef struct {
|
||||
// RAN UE NGAP ID
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
// AMF UE NGAP ID
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// User Location Information
|
||||
user_location_information_t user_info;
|
||||
} ngap_handover_notify_t;
|
||||
|
||||
/* 9.2.3.11 3GPP TS 38.413 */
|
||||
typedef struct {
|
||||
// RAN UE NGAP ID
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
// AMF UE NGAP ID
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// Cause
|
||||
ngap_cause_t cause;
|
||||
} ngap_handover_cancel_t;
|
||||
|
||||
/* 9.2.3.12 3GPP TS 38.413 */
|
||||
typedef struct {
|
||||
// RAN UE NGAP ID
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
// AMF UE NGAP ID
|
||||
uint64_t amf_ue_ngap_id;
|
||||
} ngap_handover_cancel_ack_t;
|
||||
|
||||
typedef struct ngap_ue_cap_info_ind_s {
|
||||
uint32_t gNB_ue_ngap_id;
|
||||
byte_array_t ue_radio_cap;
|
||||
@@ -533,15 +791,6 @@ typedef struct ngap_downlink_nas_s {
|
||||
byte_array_t nas_pdu;
|
||||
} ngap_downlink_nas_t;
|
||||
|
||||
/* PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */
|
||||
typedef struct {
|
||||
uint8_t nb_qos;
|
||||
pdusession_level_qos_parameter_t qos[QOSFLOW_MAX_VALUE];
|
||||
pdu_session_type_t pdu_session_type;
|
||||
// UPF endpoint of the NG-U (N3) transport bearer
|
||||
gtpu_tunnel_t n3_incoming;
|
||||
} pdusession_transfer_t;
|
||||
|
||||
/* PDU Session Resource Setup/Modify Request Item */
|
||||
typedef struct {
|
||||
int pdusession_id;
|
||||
@@ -737,4 +986,47 @@ typedef struct ngap_pdusession_release_resp_s {
|
||||
|
||||
} ngap_pdusession_release_resp_t;
|
||||
|
||||
/** 9.2.3.14 Uplink RAN Status Transfer (3GPP TS 38.413)
|
||||
* COUNT value used for both UL and DL PDCP SN + HFN (12-bit or 18-bit SN) */
|
||||
|
||||
// Indicates PDCP SN length
|
||||
typedef enum { NGAP_SN_LENGTH_12 = 0, NGAP_SN_LENGTH_18 = 1 } ngap_sn_length_t;
|
||||
|
||||
typedef struct {
|
||||
// PDCP Sequence Number
|
||||
uint32_t pdcp_sn;
|
||||
// Hyper Frame Number
|
||||
uint32_t hfn;
|
||||
// SN length
|
||||
ngap_sn_length_t sn_len;
|
||||
} ngap_drb_count_value_t;
|
||||
|
||||
// DRBs Subject to Status Transfer Item
|
||||
typedef struct {
|
||||
// DRB ID
|
||||
uint8_t drb_id;
|
||||
// UL COUNT value
|
||||
ngap_drb_count_value_t ul_count;
|
||||
// DL COUNT value
|
||||
ngap_drb_count_value_t dl_count;
|
||||
} ngap_drb_status_t;
|
||||
|
||||
// RAN Status Transfer Transparent Container (9.3.1.108)
|
||||
typedef struct {
|
||||
// Number of DRBs in the list
|
||||
uint8_t nb_drb;
|
||||
// DRB Status List
|
||||
ngap_drb_status_t drb_status_list[MAX_DRBS_PER_UE];
|
||||
} ngap_ran_status_container_t;
|
||||
|
||||
// Uplink RAN Status Transfer message (9.2.3.14)
|
||||
typedef struct {
|
||||
// AMF UE NGAP ID (Mandatory)
|
||||
uint64_t amf_ue_ngap_id;
|
||||
// RAN UE NGAP ID (Mandatory)
|
||||
uint32_t gnb_ue_ngap_id;
|
||||
// RAN Status Transfer Transparent Container (Mandatory)
|
||||
ngap_ran_status_container_t ran_status;
|
||||
} ngap_ran_status_transfer_t;
|
||||
|
||||
#endif /* NGAP_MESSAGES_TYPES_H_ */
|
||||
|
||||
@@ -360,6 +360,39 @@ static E1AP_UP_Parameters_t encode_dl_up_parameters(const int numUpParam, const
|
||||
return out;
|
||||
}
|
||||
|
||||
/** @brief PDCP SN Status Information (9.3.1.58 3GPP TS 38.463) */
|
||||
static E1AP_PDCP_SN_Status_Information_t encode_pdcp_status_info(const e1_pdcp_status_info_t *in)
|
||||
{
|
||||
E1AP_PDCP_SN_Status_Information_t pdcp = {0};
|
||||
pdcp.pdcpStatusTransfer_DL.hFN = in->dl_count.hfn;
|
||||
pdcp.pdcpStatusTransfer_DL.pDCP_SN = in->dl_count.sn;
|
||||
pdcp.pdcpStatusTransfer_UL.countValue.hFN = in->ul_count.hfn;
|
||||
pdcp.pdcpStatusTransfer_UL.countValue.pDCP_SN = in->ul_count.sn;
|
||||
return pdcp;
|
||||
}
|
||||
|
||||
/** @brief PDCP SN Status Information (9.3.1.58 3GPP TS 38.463) */
|
||||
static e1_pdcp_status_info_t decode_pdcp_status_info(const E1AP_PDCP_SN_Status_Information_t* in)
|
||||
{
|
||||
e1_pdcp_status_info_t out = {0};
|
||||
const E1AP_PDCP_Count_t *dl = &in->pdcpStatusTransfer_DL;
|
||||
const E1AP_PDCP_Count_t *ul = &in->pdcpStatusTransfer_UL.countValue;
|
||||
out.dl_count.hfn = dl->hFN;
|
||||
out.dl_count.sn = dl->pDCP_SN;
|
||||
out.ul_count.hfn = ul->hFN;
|
||||
out.ul_count.sn = ul->pDCP_SN;
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool eq_pdcp_info(const e1_pdcp_status_info_t *a, const e1_pdcp_status_info_t *b)
|
||||
{
|
||||
_E1_EQ_CHECK_INT(a->dl_count.hfn, b->dl_count.hfn);
|
||||
_E1_EQ_CHECK_INT(a->dl_count.sn, b->dl_count.sn);
|
||||
_E1_EQ_CHECK_INT(a->ul_count.hfn, b->ul_count.hfn);
|
||||
_E1_EQ_CHECK_INT(a->ul_count.sn, b->ul_count.sn);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equality check for DRB_nGRAN_to_setup_t
|
||||
*/
|
||||
@@ -1409,6 +1442,11 @@ static E1AP_PDU_Session_Resource_To_Modify_Item_t e1_encode_pdu_session_to_mod_i
|
||||
asn1cCalloc(drb2Mod->pDCP_SN_Status_Request, pDCP_SN_Status_Request);
|
||||
*pDCP_SN_Status_Request = E1AP_PDCP_SN_Status_Request_requested;
|
||||
}
|
||||
// PDCP Status Information (O)
|
||||
if (j->pdcp_status) {
|
||||
asn1cCalloc(drb2Mod->pdcp_SN_Status_Information, pdcp);
|
||||
*pdcp = encode_pdcp_status_info(j->pdcp_status);
|
||||
}
|
||||
// DL UP TNL parameters (O)
|
||||
if (j->numDlUpParam > 0) {
|
||||
asn1cCalloc(drb2Mod->dL_UP_Parameters, DL_UP_Param_List);
|
||||
@@ -1658,7 +1696,7 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E
|
||||
drb->id = drb2Mod->dRB_ID;
|
||||
// PDCP Config (O)
|
||||
if (drb2Mod->pDCP_Configuration) {
|
||||
drb->pdcp_config = malloc_or_fail(sizeof(*drb->pdcp_config));
|
||||
drb->pdcp_config = calloc_or_fail(1, sizeof(*drb->pdcp_config));
|
||||
CHECK_E1AP_DEC(e1_decode_pdcp_config(drb->pdcp_config, drb2Mod->pDCP_Configuration));
|
||||
}
|
||||
// SDAP Config (O)
|
||||
@@ -1667,9 +1705,14 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E
|
||||
CHECK_E1AP_DEC(e1_decode_sdap_config(drb->sdap_config, drb2Mod->sDAP_Configuration));
|
||||
}
|
||||
// PDCP SN Status Request (O)
|
||||
if (drb2Mod->pDCP_SN_Status_Request) {
|
||||
if (drb2Mod->pDCP_SN_Status_Request && *drb2Mod->pDCP_SN_Status_Request == E1AP_PDCP_SN_Status_Request_requested) {
|
||||
drb->pdcp_sn_status_requested = true;
|
||||
}
|
||||
// PDCP SN Status Information (O)
|
||||
if (drb2Mod->pdcp_SN_Status_Information) {
|
||||
drb->pdcp_status = malloc_or_fail(sizeof(*drb->pdcp_status));
|
||||
*drb->pdcp_status = decode_pdcp_status_info(drb2Mod->pdcp_SN_Status_Information);
|
||||
}
|
||||
// DL UP TNL parameters (O)
|
||||
if (drb2Mod->dL_UP_Parameters) {
|
||||
E1AP_UP_Parameters_t *dl_up_paramList = drb2Mod->dL_UP_Parameters;
|
||||
@@ -1843,6 +1886,7 @@ static DRB_nGRAN_to_mod_t cp_drb_to_mod_item(const DRB_nGRAN_to_mod_t *msg)
|
||||
cp = *msg;
|
||||
_E1_CP_OPTIONAL_IE(&cp, msg, sdap_config);
|
||||
_E1_CP_OPTIONAL_IE(&cp, msg, pdcp_config);
|
||||
_E1_CP_OPTIONAL_IE(&cp, msg, pdcp_status);
|
||||
return cp;
|
||||
}
|
||||
|
||||
@@ -1904,12 +1948,15 @@ static bool eq_drb_to_mod(const DRB_nGRAN_to_mod_t *a, const DRB_nGRAN_to_mod_t
|
||||
_E1_EQ_CHECK_INT(a->numDlUpParam, b->numDlUpParam);
|
||||
for (int i = 0; i < a->numDlUpParam; i++) {
|
||||
_E1_EQ_CHECK_INT(a->DlUpParamList[i].cell_group_id, b->DlUpParamList[i].cell_group_id);
|
||||
eq_up_tl_info(&a->DlUpParamList[i].tl_info, &b->DlUpParamList[i].tl_info);
|
||||
result &= eq_up_tl_info(&a->DlUpParamList[i].tl_info, &b->DlUpParamList[i].tl_info);
|
||||
}
|
||||
if (a->pdcp_config && b->pdcp_config)
|
||||
result &= eq_pdcp_config(a->pdcp_config, b->pdcp_config);
|
||||
if (a->sdap_config && b->sdap_config)
|
||||
result &= eq_sdap_config(a->sdap_config, b->sdap_config);
|
||||
_E1_EQ_CHECK_OPTIONAL_PTR(a, b, pdcp_status);
|
||||
if (a->pdcp_status && b->pdcp_status)
|
||||
result &= eq_pdcp_info(a->pdcp_status, b->pdcp_status);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1984,6 +2031,7 @@ static void free_drb_to_mod_item(const DRB_nGRAN_to_mod_t *msg)
|
||||
{
|
||||
free(msg->pdcp_config);
|
||||
free(msg->sdap_config);
|
||||
free(msg->pdcp_status);
|
||||
}
|
||||
|
||||
/* Free PDU Session to modify item */
|
||||
@@ -2111,6 +2159,13 @@ struct E1AP_E1AP_PDU *encode_E1_bearer_context_mod_response(const e1ap_bearer_mo
|
||||
asn1cSequenceAdd(drb_mod->flow_Failed_List->list, E1AP_QoS_Flow_Failed_Item_t, fail);
|
||||
encode_qos_flow_failed_item(fail, &drb->qosFlowsFailed[q]);
|
||||
}
|
||||
|
||||
// PDCP Status Information (O)
|
||||
if (drb->pdcp_status) {
|
||||
asn1cCalloc(drb_mod->pDCP_SN_Status_Information, pdcp);
|
||||
*pdcp = encode_pdcp_status_info(drb->pdcp_status);
|
||||
}
|
||||
|
||||
// UL UP Parameters (O)
|
||||
if (drb->numUpParam)
|
||||
drb_mod->uL_UP_Transport_Parameters = calloc_or_fail(1, sizeof(*drb_mod->uL_UP_Transport_Parameters));
|
||||
@@ -2310,6 +2365,11 @@ bool decode_E1_bearer_context_mod_response(e1ap_bearer_modif_resp_t *out, const
|
||||
drbMod->qosFlowsFailed[i] = decode_qos_flow_failed_item(drbIE->flow_Failed_List->list.array[i]);
|
||||
}
|
||||
}
|
||||
// PDCP Status Info (O)
|
||||
if (drbIE->pDCP_SN_Status_Information) {
|
||||
drbMod->pdcp_status = malloc_or_fail(sizeof(*drbMod->pdcp_status));
|
||||
*drbMod->pdcp_status = decode_pdcp_status_info(drbIE->pDCP_SN_Status_Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
// DRB Failed to Mod list
|
||||
@@ -2347,8 +2407,12 @@ e1ap_bearer_modif_resp_t cp_bearer_context_mod_response(const e1ap_bearer_modif_
|
||||
// Shallow copy
|
||||
cp = *msg;
|
||||
// Deep copy PDU sessions modified items
|
||||
for (int i = 0; i < msg->numPDUSessionsMod; i++)
|
||||
for (int i = 0; i < msg->numPDUSessionsMod; i++) {
|
||||
cp.pduSessionMod[i] = cp_pdu_session_mod_item(&msg->pduSessionMod[i]);
|
||||
for (int j = 0; j < msg->pduSessionMod[i].numDRBModified; j++) {
|
||||
_E1_CP_OPTIONAL_IE(&cp.pduSessionMod[i].DRBnGRanModList[j], &msg->pduSessionMod[i].DRBnGRanModList[j], pdcp_status);
|
||||
}
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
@@ -2359,6 +2423,8 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a
|
||||
{
|
||||
if (!a || !b) return false; // Null-check both inputs
|
||||
|
||||
bool result = true;
|
||||
|
||||
// Basic members
|
||||
_E1_EQ_CHECK_INT(a->gNB_cu_cp_ue_id, b->gNB_cu_cp_ue_id);
|
||||
_E1_EQ_CHECK_INT(a->gNB_cu_up_ue_id, b->gNB_cu_up_ue_id);
|
||||
@@ -2375,7 +2441,7 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a
|
||||
|
||||
_E1_EQ_CHECK_OPTIONAL_PTR(pduA, pduB, ng_DL_UP_TL_info);
|
||||
if (pduA->ng_DL_UP_TL_info && pduB->ng_DL_UP_TL_info)
|
||||
eq_up_tl_info(pduA->ng_DL_UP_TL_info, pduB->ng_DL_UP_TL_info);
|
||||
result &= eq_up_tl_info(pduA->ng_DL_UP_TL_info, pduB->ng_DL_UP_TL_info);
|
||||
|
||||
// DRB Modified List
|
||||
_E1_EQ_CHECK_INT(pduA->numDRBModified, pduB->numDRBModified);
|
||||
@@ -2387,6 +2453,10 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a
|
||||
for (int j = 0; j < drbModA->numQosFlowSetup; j++) {
|
||||
_E1_EQ_CHECK_LONG(drbModA->qosFlows[j].qfi, drbModB->qosFlows[j].qfi);
|
||||
}
|
||||
_E1_EQ_CHECK_OPTIONAL_PTR(drbModA, drbModB, pdcp_status);
|
||||
if (drbModA->pdcp_status && drbModB->pdcp_status) {
|
||||
result &= eq_pdcp_info(drbModA->pdcp_status, drbModA->pdcp_status);
|
||||
}
|
||||
}
|
||||
|
||||
// DRB Failed to Modify List
|
||||
@@ -2420,7 +2490,7 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2432,6 +2502,9 @@ void free_e1ap_context_mod_response(const e1ap_bearer_modif_resp_t *msg)
|
||||
free(msg->pduSessionMod[i].confidentialityProtectionIndication);
|
||||
free(msg->pduSessionMod[i].integrityProtectionIndication);
|
||||
free(msg->pduSessionMod[i].ng_DL_UP_TL_info);
|
||||
for (int j = 0; j < msg->pduSessionMod[i].numDRBModified; j++) {
|
||||
free(msg->pduSessionMod[i].DRBnGRanModList[j].pdcp_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -490,6 +490,13 @@ static void test_bearer_context_modification_request(void)
|
||||
.qos_params.qos_characteristics.non_dynamic.fiveqi = 9,
|
||||
};
|
||||
|
||||
const e1_pdcp_status_info_t dummy_pdcp_status = {
|
||||
.dl_count.hfn = 12,
|
||||
.dl_count.sn = 34,
|
||||
.ul_count.hfn = 56,
|
||||
.ul_count.sn = 78,
|
||||
};
|
||||
|
||||
DRB_nGRAN_to_mod_t drb_to_mod = {
|
||||
.numDlUpParam = 1,
|
||||
.DlUpParamList[0].cell_group_id = MCG,
|
||||
@@ -497,10 +504,12 @@ static void test_bearer_context_modification_request(void)
|
||||
.id = 1,
|
||||
.pdcp_config = malloc_or_fail(sizeof(*drb_to_mod.pdcp_config)),
|
||||
.pdcp_sn_status_requested = true,
|
||||
.pdcp_status = malloc_or_fail(sizeof(*drb_to_mod.pdcp_status)),
|
||||
.numQosFlow2Setup = 1,
|
||||
.qosFlows[0] = dummy_qos_flows,
|
||||
};
|
||||
*drb_to_mod.pdcp_config = dummy_pdcp_config;
|
||||
*drb_to_mod.pdcp_status = dummy_pdcp_status;
|
||||
|
||||
pdu_session_to_mod_t pdusession_mod_item = {
|
||||
.sessionId = 1,
|
||||
@@ -592,12 +601,21 @@ const DRB_nGRAN_failed_t dummy_drb_failed = {
|
||||
*/
|
||||
static void test_bearer_context_modification_response(void)
|
||||
{
|
||||
const e1_pdcp_status_info_t dummy_pdcp_status = {
|
||||
.dl_count.hfn = 12,
|
||||
.dl_count.sn = 2,
|
||||
.ul_count.hfn = 12,
|
||||
.ul_count.sn = 3,
|
||||
};
|
||||
|
||||
// DRB Modified List
|
||||
DRB_nGRAN_modified_t drb_mod = {
|
||||
.id = 1,
|
||||
.numQosFlowSetup = 1,
|
||||
.qosFlows[0].qfi = 1,
|
||||
.pdcp_status = malloc_or_fail(sizeof(*drb_mod.pdcp_status)),
|
||||
};
|
||||
*drb_mod.pdcp_status = dummy_pdcp_status;
|
||||
|
||||
// DRB Setup List
|
||||
DRB_nGRAN_setup_t setup = {
|
||||
|
||||
@@ -722,6 +722,8 @@ void RCconfig_verify(configmodule_interface_t *cfg, ngran_node_t node_type)
|
||||
verify_section_notset(cfg, GNB_CONFIG_STRING_GNB_LIST ".[0]", GNB_CONFIG_STRING_AMF_IP_ADDRESS);
|
||||
verify_section_notset(cfg, NULL, CONFIG_STRING_SECURITY);
|
||||
verify_section_notset(cfg, NULL, CONFIG_STRING_NR_PDCP_LIST);
|
||||
verify_section_notset(cfg, NULL, GNB_CONFIG_STRING_NEIGHBOUR_LIST);
|
||||
verify_section_notset(cfg, NULL, GNB_CONFIG_STRING_MEASUREMENT_CONFIGURATION);
|
||||
} // else nothing to be checked
|
||||
|
||||
/* other possible verifications: PNF, VNF, CU-CP, CU-UP, ...? */
|
||||
@@ -1858,21 +1860,21 @@ static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc
|
||||
GNBNEIGHBOURCELLPARAMS_DESC,
|
||||
GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST,
|
||||
neighbourpath);
|
||||
LOG_D(GNB_APP, "HO LOG: For the Cell: %ld Neighbour Cell ELM NUM: %d\n", cell.nr_cell_id, NeighbourCellParamList.numelt);
|
||||
LOG_I(GNB_APP, "Cell %ld has %d neighbours\n", cell.nr_cell_id, NeighbourCellParamList.numelt);
|
||||
if (NeighbourCellParamList.numelt < 1)
|
||||
continue;
|
||||
|
||||
cell.neighbour_cells = malloc_or_fail(sizeof(seq_arr_t));
|
||||
seq_arr_init(cell.neighbour_cells, sizeof(nr_neighbour_cell_t));
|
||||
for (int l = 0; l < NeighbourCellParamList.numelt; ++l) {
|
||||
nr_neighbour_cell_t neighbourCell = {0};
|
||||
neighbourCell.gNB_ID = *(NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr);
|
||||
neighbourCell.nrcell_id = (uint64_t) * (NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr);
|
||||
neighbourCell.physicalCellId = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr;
|
||||
neighbourCell.subcarrierSpacing = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_SCS_IDX].uptr;
|
||||
neighbourCell.band = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_BAND_IDX].uptr;
|
||||
neighbourCell.absoluteFrequencySSB = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr;
|
||||
neighbourCell.tac = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_TAC_IDX].uptr;
|
||||
nr_neighbour_cell_t n = {0};
|
||||
n.gNB_ID = *(NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr);
|
||||
n.nrcell_id = (uint64_t) * (NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr);
|
||||
n.physicalCellId = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr;
|
||||
n.subcarrierSpacing = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_SCS_IDX].uptr;
|
||||
n.band = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_BAND_IDX].uptr;
|
||||
n.absoluteFrequencySSB = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr;
|
||||
n.tac = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_TAC_IDX].uptr;
|
||||
|
||||
char neighbour_plmn_path[CONFIG_MAXOPTLENGTH];
|
||||
snprintf(neighbour_plmn_path,
|
||||
@@ -1884,10 +1886,22 @@ static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc
|
||||
GNB_CONFIG_STRING_NEIGHBOUR_PLMN);
|
||||
GET_PARAMS(NeighbourPlmn, GNBPLMNPARAMS_DESC, neighbour_plmn_path);
|
||||
|
||||
neighbourCell.plmn.mcc = *NeighbourPlmn[GNB_MOBILE_COUNTRY_CODE_IDX].uptr;
|
||||
neighbourCell.plmn.mnc = *NeighbourPlmn[GNB_MOBILE_NETWORK_CODE_IDX].uptr;
|
||||
neighbourCell.plmn.mnc_digit_length = *NeighbourPlmn[GNB_MNC_DIGIT_LENGTH].uptr;
|
||||
seq_arr_push_back(cell.neighbour_cells, &neighbourCell, sizeof(neighbourCell));
|
||||
n.plmn.mcc = *NeighbourPlmn[GNB_MOBILE_COUNTRY_CODE_IDX].uptr;
|
||||
n.plmn.mnc = *NeighbourPlmn[GNB_MOBILE_NETWORK_CODE_IDX].uptr;
|
||||
n.plmn.mnc_digit_length = *NeighbourPlmn[GNB_MNC_DIGIT_LENGTH].uptr;
|
||||
seq_arr_push_back(cell.neighbour_cells, &n, sizeof(n));
|
||||
LOG_I(GNB_APP,
|
||||
" [%d] neighbor ID %d cellId %ld PCI %d SCS %d SSB ARFCN %u TAC %u PLMN %03u.%0*u\n",
|
||||
l,
|
||||
n.gNB_ID,
|
||||
n.nrcell_id,
|
||||
n.physicalCellId,
|
||||
n.subcarrierSpacing,
|
||||
n.absoluteFrequencySSB,
|
||||
n.tac,
|
||||
n.plmn.mcc,
|
||||
n.plmn.mnc_digit_length,
|
||||
n.plmn.mnc);
|
||||
}
|
||||
seq_arr_push_back(rrc->neighbour_cell_configuration, &cell, sizeof(cell));
|
||||
}
|
||||
|
||||
@@ -3461,11 +3461,10 @@ void nr_measgap_scheduling(gNB_MAC_INST *nr_mac, frame_t frame, sub_frame_t slot
|
||||
continue;
|
||||
|
||||
NR_timer_t *t = &UE->UE_sched_ctrl.transm_interrupt;
|
||||
interrupt_followup_action_t a = nr_timer_is_active(t) ? UE->interrupt_action : FOLLOW_INSYNC;
|
||||
// start a timer to stop scheduling UE during MeasGap, or extend timer for
|
||||
// duration of measGap with existing follow-up action
|
||||
if (!nr_timer_is_active(t) || nr_timer_remaining_time(t) < mgc->mgl_slots) {
|
||||
nr_mac_interrupt_ue_transmission(nr_mac, UE, a, mgc->mgl_slots);
|
||||
nr_mac_interrupt_ue_transmission(nr_mac, UE, mgc->mgl_slots);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3549,7 +3548,7 @@ int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs)
|
||||
return (delay_ms << scs) + sl_ahead;
|
||||
}
|
||||
|
||||
int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots)
|
||||
int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots)
|
||||
{
|
||||
DevAssert(mac != NULL);
|
||||
DevAssert(UE != NULL);
|
||||
@@ -3557,7 +3556,6 @@ int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interr
|
||||
|
||||
nr_timer_setup(&UE->UE_sched_ctrl.transm_interrupt, slots, 1);
|
||||
nr_timer_start(&UE->UE_sched_ctrl.transm_interrupt);
|
||||
UE->interrupt_action = action;
|
||||
|
||||
// it might happen that timing advance command should be sent during the UE
|
||||
// inactivity time. To prevent this, set a variable as if we would have just
|
||||
@@ -3565,14 +3563,27 @@ int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interr
|
||||
// frames, after the inactivity of the UE.
|
||||
UE->UE_sched_ctrl.ta_frame = (mac->frame - 1 + 1024) % 1024;
|
||||
|
||||
LOG_D(NR_MAC, "UE %04x: Interrupt UE transmission (%d slots) action %d\n", UE->rnti, slots, UE->interrupt_action);
|
||||
LOG_D(NR_MAC, "UE %04x: Interrupt UE transmission (%d slots)\n", UE->rnti, slots);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nr_mac_ue_transmission_timeout(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots)
|
||||
{
|
||||
DevAssert(mac != NULL);
|
||||
DevAssert(UE != NULL);
|
||||
NR_SCHED_ENSURE_LOCKED(&mac->sched_lock);
|
||||
|
||||
nr_timer_setup(&UE->UE_sched_ctrl.transm_timeout, slots, 1);
|
||||
nr_timer_start(&UE->UE_sched_ctrl.transm_timeout);
|
||||
|
||||
LOG_I(NR_MAC, "UE %04x: Timeout for UE transmission (%d slots)\n", UE->rnti, slots);
|
||||
return;
|
||||
}
|
||||
|
||||
int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info)
|
||||
{
|
||||
int delay = nr_mac_get_reconfig_delay_slots(UE_info->current_DL_BWP.scs);
|
||||
nr_mac_interrupt_ue_transmission(mac, UE_info, FOLLOW_OUTOFSYNC, delay);
|
||||
nr_mac_ue_transmission_timeout(mac, UE_info, delay);
|
||||
LOG_I(NR_MAC, "gNB-DU received the TransmissionActionIndicator with Stop value for UE %04x\n", UE_info->rnti);
|
||||
return 0;
|
||||
}
|
||||
@@ -3641,9 +3652,11 @@ void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot)
|
||||
if (nr_timer_tick(&sched_ctrl->transm_interrupt)) {
|
||||
/* expired */
|
||||
nr_timer_stop(&sched_ctrl->transm_interrupt);
|
||||
if (UE->interrupt_action == FOLLOW_OUTOFSYNC)
|
||||
nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs);
|
||||
/* else: default FOLLOW_INSYNC: nothing to do (UE is now active again) */
|
||||
}
|
||||
if (nr_timer_tick(&sched_ctrl->transm_timeout)) {
|
||||
nr_timer_stop(&sched_ctrl->transm_timeout);
|
||||
LOG_W(NR_MAC, "UE %04x UL failure after transmission timeout\n", UE->rnti);
|
||||
nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ void nr_mac_prepare_ra_ue(gNB_MAC_INST *nrmac, NR_UE_info_t *UE);
|
||||
bool add_new_UE_RA(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE);
|
||||
int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs);
|
||||
|
||||
int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots);
|
||||
int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots);
|
||||
int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info);
|
||||
|
||||
void clear_nr_nfapi_information(gNB_MAC_INST *gNB,
|
||||
|
||||
@@ -688,6 +688,10 @@ typedef struct {
|
||||
/// Timer for RRC processing procedures and transmission activity
|
||||
NR_timer_t transm_interrupt;
|
||||
|
||||
/// Timer for timeout before UE is set to UL failure (e.g.,
|
||||
/// "TransmissionActionIndicator" handling
|
||||
NR_timer_t transm_timeout;
|
||||
|
||||
/// sri, ul_ri and tpmi based on SRS
|
||||
nr_srs_feedback_t srs_feedback;
|
||||
|
||||
@@ -762,8 +766,6 @@ typedef struct measgap_config {
|
||||
int mgl_slots;
|
||||
} measgap_config_t;
|
||||
|
||||
typedef enum interrupt_followup_action { FOLLOW_INSYNC, FOLLOW_OUTOFSYNC } interrupt_followup_action_t;
|
||||
|
||||
/*! \brief UE list used by gNB to order UEs/CC for scheduling*/
|
||||
typedef struct {
|
||||
rnti_t rnti;
|
||||
@@ -780,7 +782,6 @@ typedef struct {
|
||||
/// in case of reestablishment, old spCellConfig to apply after
|
||||
/// reconfiguration
|
||||
NR_SpCellConfig_t *reconfigSpCellConfig;
|
||||
interrupt_followup_action_t interrupt_action;
|
||||
NR_UE_NR_Capability_t *capability;
|
||||
measgap_config_t measgap_config;
|
||||
// UE selected beam index
|
||||
|
||||
@@ -291,6 +291,32 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req)
|
||||
&security_parameters);
|
||||
}
|
||||
|
||||
// PDCP Status Information requested
|
||||
if (to_modif->pdcp_sn_status_requested) {
|
||||
modified->pdcp_status = malloc_or_fail(sizeof(*modified->pdcp_status));
|
||||
nr_pdcp_count_t dl_count = {0};
|
||||
nr_pdcp_count_t ul_count = {0};
|
||||
nr_pdcp_get_drb_count_values(req->gNB_cu_cp_ue_id, to_modif->id, &ul_count, &dl_count);
|
||||
modified->pdcp_status->dl_count.hfn = dl_count.hfn;
|
||||
modified->pdcp_status->dl_count.sn = dl_count.sn;
|
||||
modified->pdcp_status->ul_count.hfn = ul_count.hfn;
|
||||
modified->pdcp_status->ul_count.sn = ul_count.sn;
|
||||
}
|
||||
|
||||
// PDCP Status received (from source CU-UP)
|
||||
if (to_modif->pdcp_status) {
|
||||
DevAssert(to_modif->pdcp_config);
|
||||
DevAssert(to_modif->pdcp_config->pDCP_SN_Size_DL == to_modif->pdcp_config->pDCP_SN_Size_UL);
|
||||
bool is_sn_len_18 = to_modif->pdcp_config->pDCP_SN_Size_DL == NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits;
|
||||
nr_pdcp_count_t dl_count = { .hfn = to_modif->pdcp_status->dl_count.hfn, .sn = to_modif->pdcp_status->dl_count.sn};
|
||||
nr_pdcp_count_t ul_count = { .hfn = to_modif->pdcp_status->ul_count.hfn, .sn = to_modif->pdcp_status->ul_count.sn};
|
||||
nr_pdcp_count_update(req->gNB_cu_cp_ue_id,
|
||||
to_modif->id,
|
||||
dl_count,
|
||||
ul_count,
|
||||
is_sn_len_18 ? LONG_SN_SIZE : SHORT_SN_SIZE);
|
||||
}
|
||||
|
||||
if (f1inst < 0) // no F1-U?
|
||||
continue; // nothing to do
|
||||
|
||||
@@ -300,6 +326,7 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req)
|
||||
in_addr_t addr = to_modif->DlUpParamList[k].tl_info.tlAddress;
|
||||
GtpuUpdateTunnelOutgoingAddressAndTeid(f1inst, req->gNB_cu_cp_ue_id, to_modif->id, addr, to_modif->DlUpParamList[k].tl_info.teId);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -601,6 +601,94 @@ static void nr_pdcp_entity_get_stats(nr_pdcp_entity_t *entity,
|
||||
*out = entity->stats;
|
||||
}
|
||||
|
||||
/** @note: this function applies to the gNB only */
|
||||
static nr_pdcp_count_t nr_pdcp_entity_get_count_dl(nr_pdcp_entity_t *entity)
|
||||
{
|
||||
nr_pdcp_count_t count = {0};
|
||||
const int sn_bits = entity->sn_size;
|
||||
const uint32_t sn_mask = entity->sn_max;
|
||||
count.hfn = entity->rx_deliv >> sn_bits;
|
||||
count.sn = entity->rx_deliv & sn_mask;
|
||||
return count;
|
||||
}
|
||||
|
||||
/** @note: this function applies to the gNB only */
|
||||
static nr_pdcp_count_t nr_pdcp_entity_get_count_ul(nr_pdcp_entity_t *entity)
|
||||
{
|
||||
nr_pdcp_count_t count = {0};
|
||||
const int sn_bits = entity->sn_size;
|
||||
const uint32_t sn_mask = entity->sn_max;
|
||||
count.hfn = entity->tx_next >> sn_bits;
|
||||
count.sn = entity->tx_next & sn_mask;
|
||||
return count;
|
||||
}
|
||||
|
||||
/** @note: this function applies to the gNB only */
|
||||
static void nr_pdcp_entity_set_count_dl(nr_pdcp_entity_t *entity, nr_pdcp_count_t pdcp_count, int sn_size)
|
||||
{
|
||||
// SN length in bits
|
||||
const uint32_t sn_mask = (1 << sn_size) - 1;
|
||||
|
||||
// Compose COUNT values for tx_next (DL) and rx_next (UL)
|
||||
uint32_t count = (pdcp_count.hfn << sn_size) | (pdcp_count.sn & sn_mask);
|
||||
|
||||
// Apply the COUNT values
|
||||
entity->rx_next = count + 1;
|
||||
entity->rx_deliv = count;
|
||||
|
||||
LOG_I(PDCP,
|
||||
"DRB %d: Applied PDCP Count\n"
|
||||
" DL COUNT - SN: %d HFN: %d (COUNT=%u)\n"
|
||||
" SN length: %s\n"
|
||||
" PDCP entity state after update:\n"
|
||||
" rx_next = %u\n"
|
||||
" rx_deliv = %u\n"
|
||||
" sn_size = %d\n"
|
||||
" sn_max = %d\n"
|
||||
" window = %d\n",
|
||||
entity->rb_id,
|
||||
pdcp_count.sn,
|
||||
pdcp_count.hfn,
|
||||
count,
|
||||
sn_size == LONG_SN_SIZE ? "18-bit" : "12-bit",
|
||||
entity->rx_next,
|
||||
entity->rx_deliv,
|
||||
entity->sn_size,
|
||||
entity->sn_max,
|
||||
entity->window_size);
|
||||
}
|
||||
|
||||
/** @note: this function applies to the gNB only */
|
||||
static void nr_pdcp_entity_set_count_ul(nr_pdcp_entity_t *entity, nr_pdcp_count_t pdcp_count, int sn_size)
|
||||
{
|
||||
// SN length in bits
|
||||
const uint32_t sn_mask = (1 << sn_size) - 1;
|
||||
|
||||
// Compose COUNT values for tx_next (DL) and rx_next (UL)
|
||||
uint32_t count = (pdcp_count.hfn << sn_size) | (pdcp_count.sn & sn_mask);
|
||||
|
||||
// Apply the COUNT values
|
||||
entity->tx_next = count;
|
||||
|
||||
LOG_I(PDCP,
|
||||
"DRB %d: Applied PDCP Count\n"
|
||||
" UL COUNT - SN: %d HFN: %d (COUNT=%u)\n"
|
||||
" SN length: %s\n"
|
||||
" PDCP entity state after update:\n"
|
||||
" tx_next = %u\n"
|
||||
" sn_size = %d\n"
|
||||
" sn_max = %d\n"
|
||||
" window = %d\n",
|
||||
entity->rb_id,
|
||||
pdcp_count.sn,
|
||||
pdcp_count.hfn,
|
||||
count,
|
||||
sn_size == LONG_SN_SIZE ? "18-bit" : "12-bit",
|
||||
entity->tx_next,
|
||||
entity->sn_size,
|
||||
entity->sn_max,
|
||||
entity->window_size);
|
||||
}
|
||||
|
||||
nr_pdcp_entity_t *new_nr_pdcp_entity(
|
||||
nr_pdcp_entity_type_t type,
|
||||
@@ -675,5 +763,11 @@ nr_pdcp_entity_t *new_nr_pdcp_entity(
|
||||
|
||||
nr_pdcp_entity_set_security(ret, security_parameters);
|
||||
|
||||
/* PDCP Count API */
|
||||
ret->get_pdcp_count_dl = nr_pdcp_entity_get_count_dl;
|
||||
ret->get_pdcp_count_ul = nr_pdcp_entity_get_count_ul;
|
||||
ret->set_pdcp_count_dl = nr_pdcp_entity_set_count_dl;
|
||||
ret->set_pdcp_count_ul = nr_pdcp_entity_set_count_ul;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,11 @@ typedef struct {
|
||||
uint8_t ciphering_key[NR_K_KEY_SIZE];
|
||||
} nr_pdcp_entity_security_keys_and_algos_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t sn;
|
||||
uint32_t hfn;
|
||||
} nr_pdcp_count_t;
|
||||
|
||||
typedef struct {
|
||||
//nr_pdcp_entity_type_t mode;
|
||||
/* PDU stats */
|
||||
@@ -87,7 +92,6 @@ typedef struct {
|
||||
uint8_t mode; /* 0: PDCP AM, 1: PDCP UM, 2: PDCP TM */
|
||||
} nr_pdcp_statistics_t;
|
||||
|
||||
|
||||
typedef struct nr_pdcp_entity_t {
|
||||
nr_pdcp_entity_type_t type;
|
||||
|
||||
@@ -180,6 +184,13 @@ typedef struct nr_pdcp_entity_t {
|
||||
|
||||
/* Keep tracks of whether the PDCP entity was suspended or not */
|
||||
bool entity_suspended;
|
||||
|
||||
/* PDCP Count API */
|
||||
nr_pdcp_count_t (*get_pdcp_count_dl)(struct nr_pdcp_entity_t *entity);
|
||||
nr_pdcp_count_t (*get_pdcp_count_ul)(struct nr_pdcp_entity_t *entity);
|
||||
void (*set_pdcp_count_dl)(struct nr_pdcp_entity_t *entity, nr_pdcp_count_t count, int sn_size);
|
||||
void (*set_pdcp_count_ul)(struct nr_pdcp_entity_t *entity, nr_pdcp_count_t count, int sn_size);
|
||||
|
||||
} nr_pdcp_entity_t;
|
||||
|
||||
nr_pdcp_entity_t *new_nr_pdcp_entity(
|
||||
|
||||
@@ -1072,3 +1072,57 @@ int nr_pdcp_get_num_ues(ue_id_t *ue_list, int len)
|
||||
|
||||
return num_ues;
|
||||
}
|
||||
|
||||
void nr_pdcp_count_update(ue_id_t ue_id,
|
||||
rb_id_t drb_id,
|
||||
nr_pdcp_count_t dl_count,
|
||||
nr_pdcp_count_t ul_count,
|
||||
int sn_size)
|
||||
{
|
||||
nr_pdcp_manager_lock(nr_pdcp_ue_manager);
|
||||
|
||||
nr_pdcp_ue_t *ue = nr_pdcp_manager_get_ue(nr_pdcp_ue_manager, ue_id);
|
||||
if (ue == NULL) {
|
||||
LOG_E(PDCP, "UE %ld not found in PDCP manager\n", ue_id);
|
||||
nr_pdcp_manager_unlock(nr_pdcp_ue_manager);
|
||||
return;
|
||||
}
|
||||
|
||||
nr_pdcp_entity_t *entity = nr_pdcp_get_rb(ue, drb_id, false);
|
||||
if (entity == NULL) {
|
||||
LOG_E(PDCP, "No PDCP entity found for UE %ld DRB %ld\n", ue_id, drb_id);
|
||||
nr_pdcp_manager_unlock(nr_pdcp_ue_manager);
|
||||
return;
|
||||
}
|
||||
|
||||
entity->set_pdcp_count_dl(entity, dl_count, sn_size);
|
||||
entity->set_pdcp_count_ul(entity, ul_count, sn_size);
|
||||
|
||||
nr_pdcp_manager_unlock(nr_pdcp_ue_manager);
|
||||
}
|
||||
|
||||
/** * @brief Get PDCP COUNT values (PDCP-SN and HFN) for UL and DL from the PDCP entity
|
||||
* @param[in] ue_id Unique UE ID
|
||||
* @param[in] rb_id Radio Bearer ID
|
||||
* @param[out] ul_count UL PDCP count (SN, HFN)
|
||||
* @param[out] dl_count DL PDCP count (SN, HFN)
|
||||
*/
|
||||
void nr_pdcp_get_drb_count_values(ue_id_t ue_id, rb_id_t rb_id, nr_pdcp_count_t *ul_count, nr_pdcp_count_t *dl_count)
|
||||
{
|
||||
nr_pdcp_manager_lock(nr_pdcp_ue_manager);
|
||||
|
||||
nr_pdcp_ue_t *ue = nr_pdcp_manager_get_ue(nr_pdcp_ue_manager, ue_id);
|
||||
|
||||
nr_pdcp_entity_t *entity = nr_pdcp_get_rb(ue, rb_id, false);
|
||||
|
||||
if (!entity) {
|
||||
LOG_E(PDCP, "No entity found (DRB %ld)\n", rb_id);
|
||||
nr_pdcp_manager_unlock(nr_pdcp_ue_manager);
|
||||
return;
|
||||
}
|
||||
|
||||
*dl_count = entity->get_pdcp_count_dl(entity);
|
||||
*ul_count = entity->get_pdcp_count_ul(entity);
|
||||
|
||||
nr_pdcp_manager_unlock(nr_pdcp_ue_manager);
|
||||
}
|
||||
|
||||
@@ -125,4 +125,12 @@ int nr_pdcp_get_num_ues(ue_id_t *ue_list, int len);
|
||||
|
||||
bool nr_pdcp_get_statistics(ue_id_t ue_id, int srb_flag, int rb_id, nr_pdcp_statistics_t *out);
|
||||
|
||||
void nr_pdcp_count_update(ue_id_t ue_id,
|
||||
rb_id_t drb_id,
|
||||
nr_pdcp_count_t dl_count,
|
||||
nr_pdcp_count_t ul_count,
|
||||
int sn_size);
|
||||
|
||||
void nr_pdcp_get_drb_count_values(ue_id_t ue_id, rb_id_t rb_id, nr_pdcp_count_t *ul_count, nr_pdcp_count_t *dl_count);
|
||||
|
||||
#endif /* NR_PDCP_OAI_API_H */
|
||||
|
||||
@@ -64,6 +64,10 @@
|
||||
#include "NR_LogicalChannelConfig.h"
|
||||
#include "NR_PDCP-Config.h"
|
||||
#include "NR_MAC-CellGroupConfig.h"
|
||||
#include "NR_HandoverPreparationInformation.h"
|
||||
#include "NR_HandoverCommand.h"
|
||||
#include "NR_HandoverCommand-IEs.h"
|
||||
#include "NR_AS-Config.h"
|
||||
#include "NR_SecurityModeCommand.h"
|
||||
#include "NR_CipheringAlgorithm.h"
|
||||
#include "NR_RRCReconfiguration-IEs.h"
|
||||
@@ -82,6 +86,8 @@
|
||||
#include "NR_UE-CapabilityRequestFilterNR.h"
|
||||
#include "NR_HandoverPreparationInformation.h"
|
||||
#include "NR_HandoverPreparationInformation-IEs.h"
|
||||
#include "NR_HandoverCommand.h"
|
||||
#include "NR_UE-CapabilityRAT-ContainerList.h"
|
||||
#include "common/utils/nr/nr_common.h"
|
||||
#if defined(NR_Rel16)
|
||||
#include "NR_SCS-SpecificCarrier.h"
|
||||
@@ -197,6 +203,48 @@ int xer_nr_sprint(char *string, size_t string_size, asn_TYPE_descriptor_t *td, v
|
||||
return er.encoded;
|
||||
}
|
||||
|
||||
struct NR_UE_NR_Capability *get_ue_nr_capability(int rnti, uint8_t *buf, uint32_t len)
|
||||
{
|
||||
if (!buf || len == 0) return NULL;
|
||||
|
||||
NR_UE_CapabilityRAT_ContainerList_t *clist = NULL;
|
||||
asn_dec_rval_t rval = uper_decode(NULL, &asn_DEF_NR_UE_CapabilityRAT_ContainerList,
|
||||
(void **)&clist, buf, len, 0, 0);
|
||||
|
||||
if (rval.code != RC_OK) {
|
||||
LOG_W(NR_MAC, "UE RNTI %04x: Failed to decode container list\n", rnti);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NR_UE_NR_Capability_t *cap = decode_nr_ue_capability(rnti, clist);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_UE_CapabilityRAT_ContainerList, clist);
|
||||
return cap;
|
||||
}
|
||||
|
||||
NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityRAT_ContainerList_t *clist)
|
||||
{
|
||||
if (!clist) return NULL;
|
||||
|
||||
for (int i = 0; i < clist->list.count; i++) {
|
||||
const NR_UE_CapabilityRAT_Container_t *c = clist->list.array[i];
|
||||
if (c->rat_Type != NR_RAT_Type_nr)
|
||||
continue;
|
||||
|
||||
NR_UE_NR_Capability_t *cap = NULL;
|
||||
asn_dec_rval_t rval = uper_decode(NULL, &asn_DEF_NR_UE_NR_Capability,
|
||||
(void **)&cap,
|
||||
c->ue_CapabilityRAT_Container.buf,
|
||||
c->ue_CapabilityRAT_Container.size,
|
||||
0, 0);
|
||||
if (rval.code == RC_OK)
|
||||
return cap;
|
||||
|
||||
LOG_W(NR_MAC, "UE RNTI %04x: Failed to decode NR capability (%zu bytes)\n", rnti, rval.consumed);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, cap);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc)
|
||||
@@ -564,6 +612,43 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re
|
||||
return ie;
|
||||
}
|
||||
|
||||
static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params)
|
||||
{
|
||||
NR_RRCReconfiguration_IEs_t *ie = build_RRCReconfiguration_IEs(params);
|
||||
byte_array_t msg = {.buf = NULL, .len = 0};
|
||||
|
||||
// Create the standalone RRCReconfiguration message
|
||||
NR_RRCReconfiguration_t rrcReconf = {0};
|
||||
rrcReconf.rrc_TransactionIdentifier = params->transaction_id;
|
||||
rrcReconf.criticalExtensions.present = NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration;
|
||||
rrcReconf.criticalExtensions.choice.rrcReconfiguration = ie;
|
||||
|
||||
// Encode the message
|
||||
int val = uper_encode_to_new_buffer(&asn_DEF_NR_RRCReconfiguration, NULL, &rrcReconf, (void **)&msg.buf);
|
||||
if (val <= 0) {
|
||||
LOG_E(NR_RRC, "ASN1 RRCReconfiguration message encoding failed\n");
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf);
|
||||
return msg;
|
||||
}
|
||||
msg.len = val;
|
||||
LOG_D(NR_RRC, "RRCReconfiguration: Encoded (%ld bytes)\n", msg.len);
|
||||
|
||||
// don't free what we did not allocate, so set fields with pointers to NULL
|
||||
// if memory comes from outside
|
||||
ie->measConfig = NULL;
|
||||
if (ie->radioBearerConfig) {
|
||||
ie->radioBearerConfig->srb3_ToRelease = NULL;
|
||||
ie->radioBearerConfig->drb_ToReleaseList = NULL;
|
||||
ie->radioBearerConfig->drb_ToAddModList = NULL;
|
||||
ie->radioBearerConfig->srb_ToAddModList = NULL;
|
||||
ie->radioBearerConfig->securityConfig = NULL;
|
||||
}
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params)
|
||||
{
|
||||
byte_array_t msg = {.buf = NULL, .len = 0};
|
||||
@@ -1158,6 +1243,192 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
|
||||
return mc;
|
||||
}
|
||||
|
||||
/** @brief Prepares removal of measurement configurations related to the source gNB.
|
||||
* This function decodes the HandoverPreparationInformation received from the source gNB,
|
||||
* extracts the source MeasConfig, and marks those entries (measurement objects, report
|
||||
* configurations, and measurement IDs) for removal by populating the corresponding
|
||||
* ToRemoveList fields in the UE's current measConfig.
|
||||
* This ensures that obsolete or source-specific measurement configurations are removed
|
||||
* from the UE after handover and allows the target gNB to install its own valid configuration.
|
||||
* @param measConfig Target gNB's current measurement configuration to be updated.
|
||||
* @param prep_info Encoded HandoverPreparationInformation received from the source gNB.*/
|
||||
void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_array_t prep_info)
|
||||
{
|
||||
if (currentMC == NULL) {
|
||||
LOG_E(NR_RRC, "HO LOG: UE's Measurement Configuration is NULL!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decodes the HandoverPreparationInformation message
|
||||
NR_HandoverPreparationInformation_t *hpi = NULL;
|
||||
asn_dec_rval_t hoPrep_dec_rval = uper_decode_complete(NULL,
|
||||
&asn_DEF_NR_HandoverPreparationInformation,
|
||||
(void **)&hpi,
|
||||
(uint8_t *)prep_info.buf,
|
||||
prep_info.len);
|
||||
|
||||
if (hoPrep_dec_rval.code != RC_OK || hoPrep_dec_rval.consumed < 0) {
|
||||
LOG_E(NR_RRC, "Handover Prep Info decode error while removing source gnb measurement configuration!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decodes the RRCReconfiguration message within the HandoverPreparationInformation
|
||||
and extract the measConfig provided by the source gNB */
|
||||
if (!hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation->sourceConfig) {
|
||||
LOG_W(NR_RRC, "Missing sourceConfig: in source gNB rrcReconfiguration\n");
|
||||
return;
|
||||
}
|
||||
NR_RRCReconfiguration_t *rrcReconf = NULL;
|
||||
NR_AS_Config_t *sourceConfig = hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation->sourceConfig;
|
||||
asn_dec_rval_t rrcReconf_dec_rval = uper_decode_complete(NULL,
|
||||
&asn_DEF_NR_RRCReconfiguration,
|
||||
(void **)&rrcReconf,
|
||||
(uint8_t *)sourceConfig->rrcReconfiguration.buf,
|
||||
sourceConfig->rrcReconfiguration.size);
|
||||
if (rrcReconf_dec_rval.code != RC_OK || rrcReconf_dec_rval.consumed < 0) {
|
||||
LOG_E(NR_RRC, "Failed to decode source gNB rrcReconfiguration!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig == NULL)
|
||||
return;
|
||||
|
||||
NR_MeasConfig_t *sourceMC = rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig;
|
||||
|
||||
/* Add measurement objects, report configurations, and measurement IDs
|
||||
to the UE's measConfig removal lists, and update UE->measConfig */
|
||||
if (sourceMC->measObjectToAddModList->list.count > 0) {
|
||||
currentMC->measObjectToRemoveList = calloc_or_fail(1, sizeof(*currentMC->measObjectToRemoveList));
|
||||
for (int i = 0; i < sourceMC->measObjectToAddModList->list.count; i++) {
|
||||
NR_MeasObjectId_t *measObjId = calloc_or_fail(1, sizeof(NR_MeasObjectId_t));
|
||||
*measObjId = sourceMC->measObjectToAddModList->list.array[i]->measObjectId;
|
||||
asn1cSeqAdd(¤tMC->measObjectToRemoveList->list, measObjId);
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceMC->reportConfigToAddModList->list.count > 0) {
|
||||
currentMC->reportConfigToRemoveList = calloc_or_fail(1, sizeof(*currentMC->reportConfigToRemoveList));
|
||||
for (int i = 0; i < sourceMC->reportConfigToAddModList->list.count; i++) {
|
||||
NR_ReportConfigId_t *reportConfigId = calloc_or_fail(1, sizeof(NR_ReportConfigId_t));
|
||||
*reportConfigId = sourceMC->reportConfigToAddModList->list.array[i]->reportConfigId;
|
||||
asn1cSeqAdd(¤tMC->reportConfigToRemoveList->list, reportConfigId);
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceMC->measIdToAddModList->list.count > 0) {
|
||||
currentMC->measIdToRemoveList = calloc_or_fail(1, sizeof(*currentMC->measIdToRemoveList));
|
||||
for (int i = 0; i < sourceMC->measIdToAddModList->list.count; i++) {
|
||||
NR_MeasId_t *measId = calloc_or_fail(1, sizeof(NR_MeasId_t));
|
||||
*measId = sourceMC->measIdToAddModList->list.array[i]->measId;
|
||||
asn1cSeqAdd(¤tMC->measIdToRemoveList->list, measId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, const byte_array_t handoverCommand)
|
||||
{
|
||||
// Decode Handover Command
|
||||
NR_HandoverCommand_t *hoCommand = NULL;
|
||||
asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_HandoverCommand, (void **)&hoCommand, handoverCommand.buf, handoverCommand.len);
|
||||
|
||||
if (dec_rval.code != RC_OK && dec_rval.consumed == 0) {
|
||||
LOG_E(NR_RRC, "Can not decode Handover Command!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Prepare DL DCCH message for RRCReconfiguration
|
||||
NR_DL_DCCH_Message_t dl_dcch_msg = {0};
|
||||
asn_enc_rval_t enc_rval;
|
||||
dl_dcch_msg.message.present = NR_DL_DCCH_MessageType_PR_c1;
|
||||
asn1cCalloc(dl_dcch_msg.message.choice.c1, c1);
|
||||
c1->present = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration;
|
||||
asn1cCalloc(c1->choice.rrcReconfiguration, rrcReconf);
|
||||
|
||||
// Decode RRCReconfiguration from handoverCommandMessage
|
||||
OCTET_STRING_t *ho = &hoCommand->criticalExtensions.choice.c1->choice.handoverCommand->handoverCommandMessage;
|
||||
uper_decode_complete(NULL, &asn_DEF_NR_RRCReconfiguration, (void **)&rrcReconf, ho->buf, ho->size);
|
||||
if (LOG_DEBUGFLAG(DEBUG_ASN1))
|
||||
xer_fprint(stdout, &asn_DEF_NR_DL_DCCH_Message, (void *)&dl_dcch_msg);
|
||||
|
||||
// Encode DL DCCH message in buffer
|
||||
enc_rval = uper_encode_to_buffer(&asn_DEF_NR_DL_DCCH_Message, NULL, &dl_dcch_msg, ba->buf, ba->len);
|
||||
AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded);
|
||||
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_DL_DCCH_Message, &dl_dcch_msg);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_HandoverCommand, hoCommand);
|
||||
|
||||
return ((enc_rval.encoded + 7) / 8);
|
||||
}
|
||||
|
||||
byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params)
|
||||
{
|
||||
// Encode RRCReconfiguration to buffer
|
||||
byte_array_t msg = do_HO_RRCReconfiguration(params);
|
||||
|
||||
// Add RRCReconfiguration to handoverCommand
|
||||
NR_HandoverCommand_t HoCommand = {0};
|
||||
HoCommand.criticalExtensions.present = NR_HandoverCommand__criticalExtensions_PR_c1;
|
||||
asn1cCalloc(HoCommand.criticalExtensions.choice.c1, c1);
|
||||
c1->present = NR_HandoverCommand__criticalExtensions__c1_PR_handoverCommand;
|
||||
asn1cCalloc(HoCommand.criticalExtensions.choice.c1->choice.handoverCommand, hc);
|
||||
OCTET_STRING_fromBuf(&hc->handoverCommandMessage, (const char *)msg.buf, msg.len);
|
||||
free_byte_array(msg);
|
||||
|
||||
if (LOG_DEBUGFLAG(DEBUG_ASN1))
|
||||
xer_fprint(stdout, &asn_DEF_NR_HandoverCommand, (void *)&HoCommand);
|
||||
|
||||
// Encode handoverCommand to buffer
|
||||
byte_array_t buffer = {.len = -1, .buf = NULL};
|
||||
buffer.len = uper_encode_to_new_buffer(&asn_DEF_NR_HandoverCommand, NULL, (void *)&HoCommand, (void **)&buffer.buf);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_HandoverCommand, &HoCommand);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/** @brief HandoverPreparationInformation message (11.2.2 of TS 38.331)
|
||||
* 1) generate RRCReconfiguration to be transferred
|
||||
* 2) encodes UE Capabilities from UE Context
|
||||
* 3) encodes AS context
|
||||
* 4) generates HO Preparation Info */
|
||||
byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci)
|
||||
{
|
||||
// Buffer to return
|
||||
byte_array_t buffer = {.buf = NULL, .len = 0};
|
||||
|
||||
/* Prepare handoverPreparationInformation IEs */
|
||||
NR_HandoverPreparationInformation_t hoPrepInfo = {0};
|
||||
hoPrepInfo.criticalExtensions.present = NR_HandoverPreparationInformation__criticalExtensions_PR_c1;
|
||||
asn1cCalloc(hoPrepInfo.criticalExtensions.choice.c1, c1);
|
||||
c1->present = NR_HandoverPreparationInformation__criticalExtensions__c1_PR_handoverPreparationInformation;
|
||||
asn1cCalloc(c1->choice.handoverPreparationInformation, hpi);
|
||||
|
||||
/* Decode stored NR_UE_CapabilityRAT_ContainerList and add to IEs */
|
||||
NR_UE_CapabilityRAT_ContainerList_t *clist = NULL;
|
||||
asn_dec_rval_t dec_rval =
|
||||
uper_decode(NULL, &asn_DEF_NR_UE_CapabilityRAT_ContainerList, (void **)&clist, params->ue_cap.buf, params->ue_cap.len, 0, 0);
|
||||
if (dec_rval.code != RC_OK) {
|
||||
LOG_W(NR_RRC, "Failed to decode UE capability container list for HandoverPreparationInformation, ignoring capabilities\n");
|
||||
return buffer;
|
||||
}
|
||||
hpi->ue_CapabilityRAT_List = *clist;
|
||||
free(clist);
|
||||
|
||||
/* sourceConfig: Encode RRCReconfiguration as used in the source cell */
|
||||
byte_array_t msg = do_HO_RRCReconfiguration(params);
|
||||
asn1cCalloc(hpi->sourceConfig, sourceConfig);
|
||||
OCTET_STRING_fromBuf(&sourceConfig->rrcReconfiguration, (const char *)msg.buf, msg.len);
|
||||
free_byte_array(msg);
|
||||
|
||||
if (LOG_DEBUGFLAG(DEBUG_ASN1))
|
||||
xer_fprint(stdout, &asn_DEF_NR_HandoverPreparationInformation, (void **)&hoPrepInfo);
|
||||
|
||||
/* encode */
|
||||
buffer.len = uper_encode_to_new_buffer(&asn_DEF_NR_HandoverPreparationInformation, NULL, (void *)&hoPrepInfo, (void *)&buffer.buf);
|
||||
AssertFatal(buffer.len > 0, "ASN1 message encoding failed (%ld)!\n", buffer.len);
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_HandoverPreparationInformation, &hoPrepInfo);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void free_MeasConfig(NR_MeasConfig_t *mc)
|
||||
{
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_MeasConfig, mc);
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "NR_SecurityConfig.h"
|
||||
#include "NR_MeasurementReport.h"
|
||||
#include "NR_MeasurementTimingConfiguration.h"
|
||||
#include "NR_UE-CapabilityRAT-ContainerList.h"
|
||||
#include "ds/seq_arr.h"
|
||||
#include "ds/byte_array.h"
|
||||
#include "rrc_messages_types.h"
|
||||
@@ -71,6 +72,7 @@ typedef struct {
|
||||
NR_CellGroupConfig_t *cell_group_config;
|
||||
bool masterKeyUpdate;
|
||||
int nextHopChainingCount;
|
||||
byte_array_t ue_cap;
|
||||
} nr_rrc_reconfig_param_t;
|
||||
|
||||
/*
|
||||
@@ -172,4 +174,12 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
|
||||
void free_MeasConfig(NR_MeasConfig_t *mc);
|
||||
int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi);
|
||||
|
||||
byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci);
|
||||
byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params);
|
||||
void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *measConfig, byte_array_t prep_info);
|
||||
int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, byte_array_t handoverCommand);
|
||||
|
||||
struct NR_UE_NR_Capability *get_ue_nr_capability(int rnti, uint8_t *buf, uint32_t len);
|
||||
NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityRAT_ContainerList_t *clist);
|
||||
|
||||
#endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */
|
||||
|
||||
@@ -315,7 +315,7 @@ typedef struct {
|
||||
int absoluteFrequencySSB;
|
||||
int subcarrierSpacing;
|
||||
int band;
|
||||
plmn_identity_t plmn;
|
||||
plmn_id_t plmn;
|
||||
uint32_t tac;
|
||||
bool isIntraFrequencyNeighbour;
|
||||
} nr_neighbour_cell_t;
|
||||
|
||||
@@ -125,4 +125,9 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC
|
||||
pdusession_level_qos_parameter_t *get_qos_characteristics(const int qfi, rrc_pdu_session_param_t *pduSession);
|
||||
f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param);
|
||||
void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *configuration);
|
||||
NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish);
|
||||
NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish);
|
||||
void activate_srb(gNB_RRC_UE_t *UE, int srb_id);
|
||||
void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs, const ngap_drb_status_t *drb_status);
|
||||
|
||||
#endif
|
||||
|
||||
+111
-40
@@ -98,6 +98,7 @@
|
||||
#include "NR_DL-DCCH-Message.h"
|
||||
#include "ds/byte_array.h"
|
||||
#include "alg/find.h"
|
||||
#include "NR_HandoverCommand.h"
|
||||
|
||||
#ifdef E2_AGENT
|
||||
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
|
||||
@@ -307,7 +308,7 @@ unsigned int rrc_gNB_get_next_transaction_identifier(module_id_t gnb_mod_idP)
|
||||
* for the radio bearer changes, with some expections for SRB1 (i.e. when resuming
|
||||
* an RRC connection, or at the first reconfiguration after RRC connection
|
||||
* reestablishment in NR, do not re-establish PDCP) */
|
||||
static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish)
|
||||
NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish)
|
||||
{
|
||||
if (!ue->Srb[SRB1].Active) {
|
||||
LOG_E(NR_RRC, "Call SRB list while SRB1 doesn't exist\n");
|
||||
@@ -328,7 +329,7 @@ static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablis
|
||||
return list;
|
||||
}
|
||||
|
||||
static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish)
|
||||
NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish)
|
||||
{
|
||||
NR_DRB_ToAddMod_t *DRB_config = NULL;
|
||||
NR_DRB_ToAddModList_t *DRB_configList = CALLOC(sizeof(*DRB_configList), 1);
|
||||
@@ -349,12 +350,12 @@ static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish)
|
||||
return DRB_configList;
|
||||
}
|
||||
|
||||
static void freeSRBlist(NR_SRB_ToAddModList_t *l)
|
||||
void freeSRBlist(NR_SRB_ToAddModList_t *l)
|
||||
{
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_SRB_ToAddModList, l);
|
||||
}
|
||||
|
||||
static void activate_srb(gNB_RRC_UE_t *UE, int srb_id)
|
||||
void activate_srb(gNB_RRC_UE_t *UE, int srb_id)
|
||||
{
|
||||
AssertFatal(srb_id == 1 || srb_id == 2, "handling only SRB 1 or 2\n");
|
||||
if (UE->Srb[srb_id].Active == 1) {
|
||||
@@ -1383,6 +1384,7 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc,
|
||||
int servingCellRSRP = 0;
|
||||
int neighbourCellRSRP = 0;
|
||||
int scell_pci = -1;
|
||||
int best_rsrp = -10000;
|
||||
|
||||
switch (event_triggered->eventId.present) {
|
||||
case NR_EventTriggerConfig__eventId_PR_eventA2:
|
||||
@@ -1443,14 +1445,20 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc,
|
||||
// No F1 connection but static neighbour configuration is available
|
||||
const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId);
|
||||
// Additional check - This part can be modified according to additional cell specific Handover Margin
|
||||
// a3-Offset: The actual value is field value * 0.5 dB.
|
||||
if (a3_event_configuration
|
||||
&& ((a3_event_configuration->a3_offset + a3_event_configuration->hysteresis)
|
||||
&& ((a3_event_configuration->a3_offset * 0.5 + a3_event_configuration->hysteresis)
|
||||
< (neighbourCellRSRP - servingCellRSRP))) {
|
||||
if (neighbourCellRSRP > best_rsrp) {
|
||||
// UE can send multiple neighbour cells A3 event report in 1 Meas Report. So, we need to find the best neighbour
|
||||
best_rsrp = neighbourCellRSRP;
|
||||
LOG_I(NR_RRC, "HO LOG: Serving Cell RSRP: %d - Best Neighbor RSRP: %d ! Trigger N2 HO\n", servingCellRSRP, best_rsrp);
|
||||
nr_rrc_trigger_n2_ho(rrc, ue, scell_pci, neighbour);
|
||||
}
|
||||
LOG_D(NR_RRC, "HO LOG: Trigger N2 HO for the neighbour gnb: %u cell: %lu\n", neighbour->gNB_ID, neighbour->nrcell_id);
|
||||
}
|
||||
} else if (neigh_cell && neighbour) {
|
||||
/* we know the cell and are connected to the DU! */
|
||||
gNB_RRC_INST *rrc = RC.nrrrc[0];
|
||||
nr_rrc_du_container_t *source_du = get_du_by_cell_id(rrc, serving_cell->nr_cellid);
|
||||
DevAssert(source_du);
|
||||
nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid);
|
||||
@@ -1459,7 +1467,6 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc,
|
||||
LOG_W(NR_RRC, "UE %d: received A3 event for stronger neighbor PCI %d, but no such neighbour in configuration\n", ue->rrc_ue_id, neighbour_pci);
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
default:
|
||||
LOG_D(NR_RRC, "NR_EventTriggerConfig__eventId_PR_NOTHING or Other event report\n");
|
||||
@@ -1600,21 +1607,14 @@ static void handle_ueCapabilityInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE,
|
||||
UE->UE_Capability_nr = 0;
|
||||
}
|
||||
|
||||
asn_dec_rval_t dec_rval = uper_decode(NULL,
|
||||
&asn_DEF_NR_UE_NR_Capability,
|
||||
(void **)&UE->UE_Capability_nr,
|
||||
ue_cap_container->ue_CapabilityRAT_Container.buf,
|
||||
ue_cap_container->ue_CapabilityRAT_Container.size,
|
||||
0,
|
||||
0);
|
||||
if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
|
||||
xer_fprint(stdout, &asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr);
|
||||
UE->UE_Capability_nr = decode_nr_ue_capability(UE->rnti, ue_CapabilityRAT_ContainerList);
|
||||
if (!UE->UE_Capability_nr) {
|
||||
LOG_E(NR_RRC, "UE %d: NR capability decoding failed\n", UE->rrc_ue_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)) {
|
||||
LOG_E(NR_RRC, "UE %d: Failed to decode nr UE capabilities (%zu bytes)\n", UE->rrc_ue_id, dec_rval.consumed);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr);
|
||||
UE->UE_Capability_nr = 0;
|
||||
if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
|
||||
xer_fprint(stdout, &asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr);
|
||||
}
|
||||
|
||||
const NR_RedCapParameters_r17_t *redcap_p = get_redcapparam_r17(UE->UE_Capability_nr);
|
||||
@@ -2076,9 +2076,28 @@ static void store_du_f1u_tunnel(const f1ap_drb_setup_t *drbs, int n, gNB_RRC_UE_
|
||||
}
|
||||
}
|
||||
|
||||
/* \brief use list of DRBs and send the corresponding bearer update message via
|
||||
* E1 to the CU of this UE. Also updates TEID info internally */
|
||||
static void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs)
|
||||
static void fill_e1_bearer_modif_pdcp_status(gNB_RRC_UE_t *UE,
|
||||
DRB_nGRAN_to_mod_t *drb_to_mod,
|
||||
const int drb_id,
|
||||
const bool um_on_default_drb,
|
||||
const ngap_drb_status_t *drb_status)
|
||||
{
|
||||
DevAssert(drb_status);
|
||||
drb_to_mod->id = drb_id;
|
||||
drb_to_mod->pdcp_sn_status_requested = false;
|
||||
// PDCP SN Status Information
|
||||
drb_to_mod->pdcp_config = calloc_or_fail(1, sizeof(*drb_to_mod->pdcp_config));
|
||||
set_bearer_context_pdcp_config(drb_to_mod->pdcp_config, get_drb(UE, drb_id), um_on_default_drb);
|
||||
drb_to_mod->pdcp_status = calloc_or_fail(1, sizeof(*drb_to_mod->pdcp_status));
|
||||
drb_to_mod->pdcp_status->dl_count.hfn = drb_status->dl_count.hfn;
|
||||
drb_to_mod->pdcp_status->dl_count.sn = drb_status->dl_count.pdcp_sn;
|
||||
drb_to_mod->pdcp_status->ul_count.hfn = drb_status->ul_count.hfn;
|
||||
drb_to_mod->pdcp_status->ul_count.sn = drb_status->ul_count.pdcp_sn;
|
||||
}
|
||||
|
||||
/** @brief Fill and send Bearer Context Modification Request with:
|
||||
* (1) from F1 UE Context DRB to setup list, or (2) with PDCP Status Request */
|
||||
void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs, const ngap_drb_status_t *drb_status)
|
||||
{
|
||||
// Quit bearer updates if no CU-UP is associated
|
||||
if (!is_cuup_associated(rrc)) {
|
||||
@@ -2091,20 +2110,37 @@ static void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f
|
||||
.gNB_cu_up_ue_id = UE->rrc_ue_id,
|
||||
};
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
const f1ap_drb_setup_t *drb_f1 = &drbs[i];
|
||||
rrc_pdu_session_param_t *pdu_ue = find_pduSession_from_drbId(UE, drb_f1->id);
|
||||
if (pdu_ue == NULL) {
|
||||
LOG_E(RRC, "UE %d: UE Context Modif Response: no PDU session for DRB ID %d\n", UE->rrc_ue_id, drb_f1->id);
|
||||
continue;
|
||||
bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target;
|
||||
for (int i = 0; i < MAX_DRBS_PER_UE; i++) {
|
||||
int drb_id = i + 1;
|
||||
DRB_nGRAN_to_mod_t drb_to_mod = {0};
|
||||
if (n > 0) {
|
||||
/* DRBs to Setup in F1 UE Context Modifcation Response */
|
||||
if (i == n)
|
||||
break;
|
||||
const f1ap_drb_setup_t *drb_f1 = &drbs[i];
|
||||
drb_id = drb_f1->id;
|
||||
fill_e1_bearer_modif(&drb_to_mod, drb_f1);
|
||||
} else if (is_inter_cu_ho) {
|
||||
/* On-going handover: send PDCP Status Request */
|
||||
if (UE->established_drbs[i].status == DRB_INACTIVE)
|
||||
continue;
|
||||
drb_to_mod.id = drb_id;
|
||||
// PDCP SN Status Request
|
||||
drb_to_mod.pdcp_sn_status_requested = true;
|
||||
LOG_I(NR_RRC, "PDCP Status requested (drb_id=%d)\n", drb_id);
|
||||
} else if (drb_status) {
|
||||
/* On-going handover: send PDCP Status */
|
||||
fill_e1_bearer_modif_pdcp_status(UE, &drb_to_mod, drb_id, rrc->configuration.um_on_default_drb, drb_status);
|
||||
}
|
||||
pdu_session_to_mod_t *pdu_e1 = find_or_next_pdu_session(&req, pdu_ue->param.pdusession_id);
|
||||
DevAssert(pdu_e1 != NULL);
|
||||
pdu_e1->sessionId = pdu_ue->param.pdusession_id;
|
||||
DRB_nGRAN_to_mod_t *drb_e1 = &pdu_e1->DRBnGRanModList[pdu_e1->numDRB2Modify];
|
||||
/* Fill E1 bearer context modification */
|
||||
fill_e1_bearer_modif(drb_e1, drb_f1);
|
||||
pdu_e1->numDRB2Modify += 1;
|
||||
rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(UE, drb_id);
|
||||
if (!pdu)
|
||||
continue;
|
||||
pdu_session_to_mod_t *to_mod = find_or_next_pdu_session(&req, pdu->param.pdusession_id);
|
||||
DevAssert(to_mod);
|
||||
to_mod->sessionId = pdu->param.pdusession_id;
|
||||
// Fill E1 DRB to Modify item
|
||||
to_mod->DRBnGRanModList[to_mod->numDRB2Modify++] = drb_to_mod;
|
||||
}
|
||||
DevAssert(req.numPDUSessionsMod > 0);
|
||||
DevAssert(req.numPDUSessions == 0);
|
||||
@@ -2169,7 +2205,7 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance
|
||||
AssertFatal(UE->Srb[1].Active && UE->Srb[2].Active, "SRBs 1 and 2 must be active during DRB Establishment");
|
||||
store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE);
|
||||
if (num_drb == 0)
|
||||
e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs);
|
||||
e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs, NULL);
|
||||
else
|
||||
cuup_notify_reestablishment(rrc, UE);
|
||||
}
|
||||
@@ -2300,9 +2336,10 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i
|
||||
}
|
||||
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
|
||||
|
||||
if (resp->drbs_len > 0) {
|
||||
bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target;
|
||||
if (resp->drbs_len > 0 || is_inter_cu_ho) {
|
||||
store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE);
|
||||
e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs);
|
||||
e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs, NULL);
|
||||
}
|
||||
|
||||
if (resp->du_to_cu_rrc_info) {
|
||||
@@ -2550,6 +2587,13 @@ void rrc_gNB_process_e1_bearer_context_setup_resp(e1ap_bearer_setup_resp_t *resp
|
||||
}
|
||||
}
|
||||
|
||||
// If HO Preparation Info is stored, N2 handover is ongoing
|
||||
if (UE->ho_context) {
|
||||
LOG_I(NR_RRC, "Received Bearer Context Setup Response for UE %d with valid HO Context\n", UE->rrc_ue_id);
|
||||
UE->ho_context->target->ho_trigger(rrc, UE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UE->f1_ue_context_active)
|
||||
rrc_gNB_generate_UeContextSetupRequest(rrc, ue_context_p, resp);
|
||||
else
|
||||
@@ -2578,10 +2622,23 @@ void rrc_gNB_process_e1_bearer_context_modif_resp(const e1ap_bearer_modif_resp_t
|
||||
return;
|
||||
}
|
||||
|
||||
// there is not really anything to do here as of now
|
||||
int n_drb_mod = 0;
|
||||
e1_pdcp_status_info_t pdcp_status[MAX_DRBS_PER_UE] = {0};
|
||||
for (int i = 0; i < resp->numPDUSessionsMod; ++i) {
|
||||
const pdu_session_modif_t *pdu = &resp->pduSessionMod[i];
|
||||
LOG_I(RRC, "UE %d: PDU session ID %ld modified %d bearers\n", resp->gNB_cu_cp_ue_id, pdu->id, pdu->numDRBModified);
|
||||
for (int j = 0; j < pdu->numDRBModified; j++) {
|
||||
// Trigger UL RAN Status Transfer
|
||||
if (pdu->DRBnGRanModList[j].pdcp_status) {
|
||||
pdcp_status[n_drb_mod++] = *pdu->DRBnGRanModList[j].pdcp_status;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (n_drb_mod) {
|
||||
LOG_I(NR_RRC, "UE %d: received PDU Status Info - send UL RAN Status Transfer\n", resp->gNB_cu_cp_ue_id);
|
||||
gNB_RRC_UE_t *ue = &ue_context_p->ue_context;
|
||||
if (ue->ho_context && ue->ho_context->source)
|
||||
ue->ho_context->source->ho_status_transfer(rrc, ue, n_drb_mod, pdcp_status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2819,6 +2876,10 @@ void *rrc_gnb_task(void *args_p) {
|
||||
rrc_gNB_process_NGAP_PDUSESSION_RELEASE_COMMAND(msg_p, instance);
|
||||
break;
|
||||
|
||||
case NGAP_DL_RAN_STATUS_TRANSFER:
|
||||
rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(msg_p, instance);
|
||||
break;
|
||||
|
||||
/* Messages from F1AP task */
|
||||
case F1AP_SETUP_REQ:
|
||||
AssertFatal(!NODE_IS_DU(RC.nrrrc[instance]->node_type), "should not receive F1AP_SETUP_REQUEST in DU!\n");
|
||||
@@ -2938,6 +2999,16 @@ void *rrc_gnb_task(void *args_p) {
|
||||
rrc_gNB_process_PAGING_IND(msg_p, instance);
|
||||
break;
|
||||
|
||||
case NGAP_HANDOVER_REQUEST:
|
||||
rrc_gNB_process_Handover_Request(RC.nrrrc[instance], instance, &NGAP_HANDOVER_REQUEST(msg_p));
|
||||
rrc_gNB_free_Handover_Request(&NGAP_HANDOVER_REQUEST(msg_p)); // Free transfered NG message
|
||||
break;
|
||||
|
||||
case NGAP_HANDOVER_COMMAND:
|
||||
rrc_gNB_process_HandoverCommand(RC.nrrrc[instance], &NGAP_HANDOVER_COMMAND(msg_p));
|
||||
rrc_gNB_free_Handover_Command(&NGAP_HANDOVER_COMMAND(msg_p)); // Free transfered NG message
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_E(NR_RRC, "[gNB %ld] Received unexpected message %s\n", instance, msg_name_p);
|
||||
break;
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
#include <arpa/inet.h>
|
||||
#include "E1AP_ConfidentialityProtectionIndication.h"
|
||||
#include "E1AP_IntegrityProtectionIndication.h"
|
||||
#include "NR_UE-CapabilityRAT-ContainerList.h"
|
||||
#include "NR_HandoverCommand.h"
|
||||
#include "NR_HandoverCommand-IEs.h"
|
||||
#include "NGAP_CauseRadioNetwork.h"
|
||||
#include "NGAP_Dynamic5QIDescriptor.h"
|
||||
#include "NGAP_GTPTunnel.h"
|
||||
@@ -49,6 +52,7 @@
|
||||
#include "NGAP_QosFlowSetupRequestItem.h"
|
||||
#include "NGAP_asn_constant.h"
|
||||
#include "NGAP_ProtocolIE-Field.h"
|
||||
#include "NGAP_CellSize.h"
|
||||
#include "NR_UE-NR-Capability.h"
|
||||
#include "NR_UERadioAccessCapabilityInformation.h"
|
||||
#include "MAC/mac.h"
|
||||
@@ -82,6 +86,8 @@
|
||||
#include "rrc_messages_types.h"
|
||||
#include "s1ap_messages_types.h"
|
||||
#include "uper_encoder.h"
|
||||
#include "rrc_gNB_mobility.h"
|
||||
#include "rrc_gNB_du.h"
|
||||
|
||||
#ifdef E2_AGENT
|
||||
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
|
||||
@@ -162,18 +168,24 @@ static nr_guami_t get_guami(const uint32_t amf_Id, const plmn_id_t plmn)
|
||||
return guami;
|
||||
}
|
||||
|
||||
/** @brief Copy NGAP PDU Session Transfer item to RRC pdusession_t struct */
|
||||
static void cp_pdusession_transfer_to_pdusession(pdusession_t *dst, const pdusession_transfer_t *src)
|
||||
{
|
||||
dst->nb_qos = src->nb_qos;
|
||||
for (uint8_t i = 0; i < src->nb_qos && i < QOSFLOW_MAX_VALUE; ++i) {
|
||||
dst->qos[i] = src->qos[i];
|
||||
}
|
||||
dst->pdu_session_type = src->pdu_session_type;
|
||||
dst->n3_incoming = src->n3_incoming;
|
||||
}
|
||||
|
||||
/** @brief Copy NGAP PDU Session Resource item to RRC pdusession_t struct */
|
||||
static void cp_pdusession_resource_item_to_pdusession(pdusession_t *dst, const pdusession_resource_item_t *src)
|
||||
{
|
||||
dst->pdusession_id = src->pdusession_id;
|
||||
dst->nas_pdu = src->nas_pdu;
|
||||
dst->nb_qos = src->pdusessionTransfer.nb_qos;
|
||||
for (uint8_t i = 0; i < src->pdusessionTransfer.nb_qos && i < QOSFLOW_MAX_VALUE; ++i) {
|
||||
dst->qos[i] = src->pdusessionTransfer.qos[i];
|
||||
}
|
||||
dst->pdu_session_type = src->pdusessionTransfer.pdu_session_type;
|
||||
dst->n3_incoming = src->pdusessionTransfer.n3_incoming;
|
||||
dst->nssai = src->nssai;
|
||||
cp_pdusession_transfer_to_pdusession(dst, &src->pdusessionTransfer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1062,7 +1074,168 @@ int rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_REQ(MessageDef *msg_p, instance_t in
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** @brief Sends the NG Handover Failure from the Target NG-RAN to the AMF */
|
||||
void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg)
|
||||
{
|
||||
LOG_I(NR_RRC, "Send NG Handover Failure message (amf_ue_ngap_id %ld) with cause %d \n ", msg->amf_ue_ngap_id, msg->cause.value);
|
||||
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_FAILURE);
|
||||
NGAP_HANDOVER_FAILURE(msg_p) = *msg;
|
||||
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
|
||||
}
|
||||
|
||||
/** @brief Process NG Handover Request message (8.4.2.2 3GPP TS 38.413) */
|
||||
int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg)
|
||||
{
|
||||
LOG_I(NR_RRC, "Received Handover Request (on PCI=%lu) \n", msg->nr_cell_id);
|
||||
struct nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, msg->nr_cell_id);
|
||||
if (du == NULL) {
|
||||
/* Cell Not Found! Return HO Request Failure*/
|
||||
LOG_E(RRC, "Failed to process Handover Request: no DU found with PCI=%lu \n", msg->nr_cell_id);
|
||||
ngap_handover_failure_t fail = {
|
||||
.amf_ue_ngap_id = msg->amf_ue_ngap_id,
|
||||
.cause.type = NGAP_CAUSE_RADIO_NETWORK,
|
||||
.cause.value = NGAP_CAUSE_RADIO_NETWORK_RADIO_RESOURCES_NOT_AVAILABLE,
|
||||
};
|
||||
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create UE context
|
||||
sctp_assoc_t curr_assoc_id = du->assoc_id;
|
||||
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(curr_assoc_id, UINT16_MAX, rrc, UINT64_MAX, UINT32_MAX);
|
||||
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
|
||||
|
||||
// allocate context for target
|
||||
if (UE->ho_context != NULL) {
|
||||
LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", UE->rrc_ue_id);
|
||||
return -1;
|
||||
}
|
||||
UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET);
|
||||
UE->ho_context->target->ho_trigger = nr_rrc_trigger_n2_ho_target;
|
||||
|
||||
// Store IDs in UE context
|
||||
UE->amf_ue_ngap_id = msg->amf_ue_ngap_id;
|
||||
UE->ue_guami = msg->guami;
|
||||
UE->ho_context->target->ue_ho_prep_info = copy_byte_array(msg->ue_ho_prep_info);
|
||||
// store the received UE Security Capabilities in the UE context
|
||||
FREE_AND_ZERO_BYTE_ARRAY(UE->ue_cap_buffer);
|
||||
UE->ue_cap_buffer = copy_byte_array(msg->ue_cap);
|
||||
|
||||
/* store the received Security Context in the UE context
|
||||
and take it into use as defined in TS 33.501 */
|
||||
set_UE_security_algos(rrc, UE, &msg->security_capabilities);
|
||||
UE->nh_ncc = msg->security_context.next_hop_chain_count;
|
||||
memcpy(UE->nh, msg->security_context.next_hop, SECURITY_KEY_LENGTH);
|
||||
// Reset KgNB
|
||||
memset(UE->kgnb, 0, SECURITY_KEY_LENGTH);
|
||||
// Derive KgNB*
|
||||
const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info;
|
||||
uint32_t ssb_arfcn = get_ssb_arfcn(du);
|
||||
nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, UE->nh, UE->kgnb);
|
||||
UE->as_security_active = true;
|
||||
// Activate SRBs
|
||||
activate_srb(UE, SRB1);
|
||||
activate_srb(UE, SRB2);
|
||||
nr_rrc_pdcp_config_security(UE, false);
|
||||
|
||||
// Copy PDU Session Resource Setup item to RRC struct and do PDU Session Resource Setup procedure
|
||||
pdusession_t to_setup = {
|
||||
.nssai = msg->pduSessionResourceSetupList->nssai,
|
||||
.pdu_session_type = msg->pduSessionResourceSetupList->pdu_session_type ,
|
||||
.pdusession_id = msg->pduSessionResourceSetupList->pdusession_id,
|
||||
};
|
||||
cp_pdusession_transfer_to_pdusession(&to_setup, &msg->pduSessionResourceSetupList->pdusessionTransfer);
|
||||
if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, &to_setup, msg->ue_ambr.br_dl)) {
|
||||
LOG_E(NR_RRC, "Failed to establish PDU session: handover failed\n");
|
||||
ngap_handover_failure_t fail = {
|
||||
.amf_ue_ngap_id = msg->amf_ue_ngap_id,
|
||||
.cause.type = NGAP_CAUSE_RADIO_NETWORK,
|
||||
.cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM,
|
||||
};
|
||||
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg)
|
||||
{
|
||||
free_byte_array(msg->ue_cap);
|
||||
free_byte_array(msg->ue_ho_prep_info);
|
||||
free(msg->mobility_restriction);
|
||||
}
|
||||
|
||||
/** @brief Send NG Uplink RAN Status Transfer message (8.4.6 3GPP TS 38.413)
|
||||
* Direction: source NG-RAN node -> AMF */
|
||||
int rrc_gNB_send_NGAP_ul_ran_status_transfer(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status)
|
||||
{
|
||||
AssertFatal(UE != NULL, "UE context is NULL\n");
|
||||
|
||||
LOG_I(NR_RRC,
|
||||
"Sending NGAP Uplink RAN Status Transfer (AMF_UE_NGAP_ID=%" PRIu64 ", GNB_UE_NGAP_ID=%u)\n",
|
||||
UE->amf_ue_ngap_id,
|
||||
UE->rrc_ue_id);
|
||||
|
||||
ngap_ran_status_transfer_t msg = {
|
||||
.amf_ue_ngap_id = UE->amf_ue_ngap_id,
|
||||
.gnb_ue_ngap_id = UE->rrc_ue_id,
|
||||
};
|
||||
|
||||
// Loop through DRBs and extract COUNT values
|
||||
for (int i = 0; i < n_to_mod; ++i) {
|
||||
drb_t *drb = &UE->established_drbs[i];
|
||||
if (!drb->status)
|
||||
continue;
|
||||
|
||||
bool sn_length_18 = rrc->pdcp_config.drb.sn_size == 18;
|
||||
|
||||
ngap_drb_status_t *item = &msg.ran_status.drb_status_list[msg.ran_status.nb_drb++];
|
||||
item->drb_id = drb->drb_id;
|
||||
|
||||
const e1_pdcp_count_t *ul_pdcp = &pdcp_status[i].ul_count;
|
||||
const e1_pdcp_count_t *dl_pdcp = &pdcp_status[i].dl_count;
|
||||
|
||||
item->ul_count.pdcp_sn = ul_pdcp->sn;
|
||||
item->ul_count.hfn = ul_pdcp->hfn;
|
||||
item->ul_count.sn_len = sn_length_18 ? NGAP_SN_LENGTH_18 : NGAP_SN_LENGTH_12;
|
||||
|
||||
item->dl_count.pdcp_sn = dl_pdcp->sn;
|
||||
item->dl_count.hfn = dl_pdcp->hfn;
|
||||
item->dl_count.sn_len = sn_length_18 ? NGAP_SN_LENGTH_18 : NGAP_SN_LENGTH_12;
|
||||
}
|
||||
|
||||
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_UL_RAN_STATUS_TRANSFER);
|
||||
NGAP_UL_RAN_STATUS_TRANSFER(msg_p) = msg;
|
||||
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @brief Process NG Handover Command on Source gNB */
|
||||
void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg)
|
||||
{
|
||||
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, msg->gNB_ue_ngap_id);
|
||||
|
||||
if (ue_context_p == NULL) {
|
||||
LOG_W(NR_RRC, "Unknown UE context associated to gNB_ue_ngap_id (%u)\n", msg->gNB_ue_ngap_id);
|
||||
return;
|
||||
}
|
||||
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
|
||||
|
||||
uint8_t buffer[NR_RRC_BUF_SIZE];
|
||||
byte_array_t ba = {.buf = buffer, .len = sizeof(buffer)};
|
||||
int enc = doRRCReconfiguration_from_HandoverCommand(&ba, msg->handoverCommand);
|
||||
DevAssert(enc > 0);
|
||||
|
||||
rrc_gNB_trigger_reconfiguration_for_handover(rrc, UE, buffer, enc);
|
||||
LOG_A(NR_RRC, "Send reconfiguration (HO Command) to UE %u/RNTI %04x\n", UE->rrc_ue_id, UE->rnti);
|
||||
}
|
||||
|
||||
void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg)
|
||||
{
|
||||
free_byte_array(msg->handoverCommand);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the NG command NGAP_UE_CONTEXT_RELEASE_COMMAND, sent by AMF.
|
||||
* The gNB should remove all pdu session, NG context, and other context of the UE.
|
||||
@@ -1172,6 +1345,88 @@ void rrc_gNB_send_NGAP_UE_CAPABILITIES_IND(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE,
|
||||
LOG_I(NR_RRC,"Send message to ngap: NGAP_UE_CAPABILITIES_IND\n");
|
||||
}
|
||||
|
||||
void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, byte_array_t ho_command)
|
||||
{
|
||||
LOG_D(NR_RRC, "Sending Handover Request Acknowledge\n");
|
||||
|
||||
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_REQUEST_ACKNOWLEDGE);
|
||||
ngap_handover_request_ack_t *msg = &NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(msg_p);
|
||||
memset(msg, 0, sizeof(*msg));
|
||||
|
||||
// RAN UE NGAP ID
|
||||
msg->gNB_ue_ngap_id = UE->rrc_ue_id;
|
||||
// AMF UE NGAP ID
|
||||
msg->amf_ue_ngap_id = UE->amf_ue_ngap_id;
|
||||
// PDU Session Resource Admitted List
|
||||
msg->nb_of_pdusessions = UE->nb_of_pdusessions;
|
||||
for (int i = 0; i < UE->nb_of_pdusessions; i++) {
|
||||
rrc_pdu_session_param_t *session = &UE->pduSession[i];
|
||||
session->status = PDU_SESSION_STATUS_ESTABLISHED;
|
||||
// PDU Session ID
|
||||
msg->pdusessions[i].pdu_session_id = session->param.pdusession_id;
|
||||
// Handover Request Acknowledge Transfer
|
||||
ho_request_ack_transfer_t *transfer = &msg->pdusessions[i].ack_transfer;
|
||||
transfer->gtp_teid = session->param.n3_outgoing.teid;
|
||||
memcpy(transfer->gNB_addr.buffer, session->param.n3_outgoing.addr.buffer, session->param.n3_outgoing.addr.length);
|
||||
transfer->gNB_addr.length = session->param.n3_outgoing.addr.length;
|
||||
transfer->nb_of_qos_flow = session->param.nb_qos;
|
||||
for (int q = 0; q < transfer->nb_of_qos_flow; q++) {
|
||||
transfer->qos_setup_list[q].qfi = session->param.qos[q].qfi;
|
||||
transfer->qos_setup_list[q].qos_flow_mapping_ind = QOSFLOW_MAPPING_INDICATION_DL;
|
||||
}
|
||||
}
|
||||
// Target to Source Transparent Container
|
||||
msg->target2source = copy_byte_array(ho_command);
|
||||
|
||||
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
|
||||
}
|
||||
|
||||
/** @brief Prepare NG Handover Notify message and inform NGAP */
|
||||
void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
|
||||
{
|
||||
LOG_I(NR_RRC, "Triggering NGAP Handover Notify\n");
|
||||
|
||||
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_NOTIFY);
|
||||
ngap_handover_notify_t *ho_notify = &NGAP_HANDOVER_NOTIFY(msg_p);
|
||||
memset(ho_notify, 0, sizeof(*ho_notify));
|
||||
nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, rrc->nr_cellid);
|
||||
|
||||
ho_notify->gNB_ue_ngap_id = UE->rrc_ue_id;
|
||||
ho_notify->amf_ue_ngap_id = UE->amf_ue_ngap_id;
|
||||
ho_notify->user_info.nrCellIdentity = rrc->nr_cellid;
|
||||
ho_notify->user_info.target_ng_ran.tac = *du->setup_req->cell->info.tac;
|
||||
ho_notify->user_info.target_ng_ran.targetgNBId = rrc->node_id;
|
||||
ho_notify->user_info.target_ng_ran.plmn_identity.mcc = du->setup_req->cell->info.plmn.mcc;
|
||||
ho_notify->user_info.target_ng_ran.plmn_identity.mnc = du->setup_req->cell->info.plmn.mnc;
|
||||
ho_notify->user_info.target_ng_ran.plmn_identity.mnc_digit_length = du->setup_req->cell->info.plmn.mnc_digit_length;
|
||||
|
||||
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
|
||||
}
|
||||
|
||||
/** @brief Prepare NG Handover Cancel (source NG-RAN) and inform NGAP (N2-based HO only)
|
||||
* 3GPP TS 38.413 §8.4.5 (Handover Cancellation) - source NG-RAN -> AMF
|
||||
* 3GPP TS 38.413 §9.2.3.11 (HANDOVER CANCEL)
|
||||
* @param rrc CU/RRC instance
|
||||
* @param UE UE context
|
||||
* @param cause cancellation cause */
|
||||
void rrc_gNB_send_NGAP_HANDOVER_CANCEL(int module_id, gNB_RRC_UE_t *UE, ngap_cause_t cause)
|
||||
{
|
||||
DevAssert(UE != NULL);
|
||||
|
||||
LOG_I(NR_RRC, "Triggering NGAP Handover Cancel (gNB_ue_ngap_id=%d, amf_ue_ngap_id=%ld)\n", UE->rrc_ue_id, UE->amf_ue_ngap_id);
|
||||
|
||||
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_CANCEL);
|
||||
ngap_handover_cancel_t *ho_cancel = &NGAP_HANDOVER_CANCEL(msg_p);
|
||||
memset(ho_cancel, 0, sizeof(*ho_cancel));
|
||||
|
||||
/* Mandatory IEs (38.413 §9.2.3.11) */
|
||||
ho_cancel->gNB_ue_ngap_id = UE->rrc_ue_id;
|
||||
ho_cancel->amf_ue_ngap_id = UE->amf_ue_ngap_id;
|
||||
ho_cancel->cause = cause;
|
||||
|
||||
itti_send_msg_to_task(TASK_NGAP, module_id, msg_p);
|
||||
}
|
||||
|
||||
void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid)
|
||||
{
|
||||
int pdu_sessions_released = 0;
|
||||
@@ -1297,3 +1552,119 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @brief Callback for NGAP Handover Required message (3GPP TS 38.413 9.2.3.1)
|
||||
* Direction: source gNB -> AMF */
|
||||
void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc,
|
||||
gNB_RRC_UE_t *UE,
|
||||
const nr_neighbour_cell_t *neighbour,
|
||||
const byte_array_t hoPrepInfo)
|
||||
{
|
||||
LOG_I(NR_RRC, "Handover Preparation: send Handover Required (target gNB ID=%d, PCI=%d)\n", neighbour->gNB_ID, neighbour->physicalCellId);
|
||||
|
||||
const plmn_id_t plmn = {.mcc = neighbour->plmn.mnc,
|
||||
.mnc = neighbour->plmn.mnc,
|
||||
.mnc_digit_length = neighbour->plmn.mnc_digit_length};
|
||||
|
||||
const target_ran_node_id_t target = {
|
||||
.plmn_identity = plmn,
|
||||
.tac = neighbour->tac,
|
||||
.targetgNBId = neighbour->gNB_ID,
|
||||
};
|
||||
|
||||
ngap_handover_required_t msg = {
|
||||
.amf_ue_ngap_id = UE->amf_ue_ngap_id,
|
||||
.gNB_ue_ngap_id = UE->rrc_ue_id,
|
||||
.nb_of_pdusessions = UE->nb_of_pdusessions,
|
||||
.cause.type = NGAP_CAUSE_RADIO_NETWORK,
|
||||
.cause.value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_DESIRABLE_FOR_RADIO_REASON,
|
||||
.handoverType = HANDOVER_TYPE_INTRA5GS,
|
||||
.target_gnb_id = target,
|
||||
};
|
||||
|
||||
cell_id_t target_cell = {
|
||||
.nrCellIdentity = neighbour->nrcell_id,
|
||||
.plmn_identity = plmn,
|
||||
};
|
||||
|
||||
// Source to Target Transparent Container (M)
|
||||
msg.source2target = calloc_or_fail(1, sizeof(*msg.source2target));
|
||||
// Target Cell ID (M)
|
||||
msg.source2target->targetCellId = target_cell;
|
||||
// RRC Container (M)
|
||||
msg.source2target->handoverInfo = copy_byte_array(hoPrepInfo);
|
||||
// UE History Info (M)
|
||||
msg.source2target->ue_history_info.cause = malloc_or_fail(sizeof(*msg.source2target->ue_history_info.cause));
|
||||
msg.source2target->ue_history_info.cause->type = NGAP_CAUSE_RADIO_NETWORK;
|
||||
msg.source2target->ue_history_info.cause->value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_DESIRABLE_FOR_RADIO_REASON;
|
||||
msg.source2target->ue_history_info.type = NGAP_CellSize_small;
|
||||
msg.source2target->ue_history_info.time_in_cell = 500; // dummy number for now
|
||||
msg.source2target->ue_history_info.id = target_cell;
|
||||
|
||||
/* Fill both PDU Session Resource List IE (M) in Handover Required
|
||||
and PDU Session Resource Information List IE (O) in the Source NG-RAN
|
||||
Node to Target NG-RAN Node Transparent Container */
|
||||
for (int i = 0; i < UE->nb_of_pdusessions; ++i) {
|
||||
if (UE->pduSession[i].status == PDU_SESSION_STATUS_DONE || UE->pduSession[i].status == PDU_SESSION_STATUS_ESTABLISHED) {
|
||||
rrc_pdu_session_param_t *pduSession = find_pduSession(UE, UE->pduSession[i].param.pdusession_id, false);
|
||||
if (!pduSession)
|
||||
continue;
|
||||
// Handover Required Transfer (M)
|
||||
uint8_t ho_required_transfer[128] = {0};
|
||||
msg.pdusessions[i].ho_required_transfer.buf = ho_required_transfer;
|
||||
msg.pdusessions[i].ho_required_transfer.len = sizeof(ho_required_transfer);
|
||||
// PDU Session ID
|
||||
msg.pdusessions[i].pdusession_id = pduSession->param.pdusession_id;
|
||||
|
||||
// PDU Session Resource Information List (O)
|
||||
msg.source2target->pdu_session_resource[i].nb_of_qos_flow = pduSession->param.nb_qos;
|
||||
for (int j = 0; j < pduSession->param.nb_qos; ++j) {
|
||||
msg.source2target->pdu_session_resource[i].qos_flow_info[j].qfi = pduSession->param.qos[j].qfi;
|
||||
}
|
||||
msg.source2target->nb_pdu_session_resource++;
|
||||
}
|
||||
}
|
||||
|
||||
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_REQUIRED);
|
||||
NGAP_HANDOVER_REQUIRED(msg_p) = msg;
|
||||
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
|
||||
}
|
||||
|
||||
int rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(MessageDef *msg_p, instance_t instance)
|
||||
{
|
||||
const ngap_ran_status_transfer_t *cmd = &NGAP_DL_RAN_STATUS_TRANSFER(msg_p);
|
||||
gNB_RRC_INST *rrc = RC.nrrrc[instance];
|
||||
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, cmd->gnb_ue_ngap_id);
|
||||
|
||||
if (!ue_context_p) {
|
||||
LOG_E(NR_RRC, "[gNB %ld] No UE context for gNB_ue_ngap_id %u\n", instance, cmd->gnb_ue_ngap_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
|
||||
LOG_I(NR_RRC,
|
||||
"[gNB %ld] DL RAN Status Transfer for gNB_ue_ngap_id %u AMF_UE_NGAP_ID %lu\n",
|
||||
instance,
|
||||
cmd->gnb_ue_ngap_id,
|
||||
cmd->amf_ue_ngap_id);
|
||||
|
||||
for (int i = 0; i < cmd->ran_status.nb_drb; ++i) {
|
||||
const ngap_drb_status_t *s = &cmd->ran_status.drb_status_list[i];
|
||||
LOG_I(NR_RRC,
|
||||
"DL RAN Status Transfer - DRB ID %d:\n"
|
||||
" UL COUNT: PDCP SN = %u, HFN = %u (%s)\n"
|
||||
" DL COUNT: PDCP SN = %u, HFN = %u (%s)\n",
|
||||
s->drb_id,
|
||||
s->ul_count.pdcp_sn,
|
||||
s->ul_count.hfn,
|
||||
s->ul_count.sn_len == NGAP_SN_LENGTH_18 ? "18-bit" : "12-bit",
|
||||
s->dl_count.pdcp_sn,
|
||||
s->dl_count.hfn,
|
||||
s->dl_count.sn_len == NGAP_SN_LENGTH_18 ? "18-bit" : "12-bit");
|
||||
|
||||
// Send to PDCP layer
|
||||
e1_send_bearer_updates(rrc, UE, 0, NULL, s);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -89,4 +89,26 @@ void nr_rrc_pdcp_config_security(gNB_RRC_UE_t *UE, bool enable_ciphering);
|
||||
|
||||
int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance);
|
||||
|
||||
void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc,
|
||||
gNB_RRC_UE_t *UE,
|
||||
const nr_neighbour_cell_t *neighbourCellConfiguration,
|
||||
const byte_array_t hoPrepInfo);
|
||||
|
||||
void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg);
|
||||
|
||||
int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg);
|
||||
void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg);
|
||||
|
||||
void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, byte_array_t ho_command);
|
||||
|
||||
void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg);
|
||||
void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg);
|
||||
|
||||
void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE);
|
||||
void rrc_gNB_send_NGAP_HANDOVER_CANCEL(int module_id, gNB_RRC_UE_t *UE, ngap_cause_t cause);
|
||||
|
||||
int rrc_gNB_send_NGAP_ul_ran_status_transfer(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status);
|
||||
|
||||
int rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(MessageDef *msg_p, instance_t instance);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
|
||||
#endif
|
||||
|
||||
typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t;
|
||||
static nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type)
|
||||
nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type)
|
||||
{
|
||||
nr_handover_context_t *ho_ctx = calloc_or_fail(1, sizeof(*ho_ctx));
|
||||
if (type == HO_CTX_SOURCE || type == HO_CTX_BOTH) {
|
||||
@@ -58,6 +57,8 @@ static nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type)
|
||||
static void free_ho_ctx(nr_handover_context_t *ho_ctx)
|
||||
{
|
||||
free(ho_ctx->source);
|
||||
if (ho_ctx->target)
|
||||
FREE_AND_ZERO_BYTE_ARRAY(ho_ctx->target->ue_ho_prep_info);
|
||||
free(ho_ctx->target);
|
||||
free(ho_ctx);
|
||||
}
|
||||
@@ -121,18 +122,16 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc,
|
||||
byte_array_t *ho_prep_info,
|
||||
ho_req_ack_t ack,
|
||||
ho_success_t success,
|
||||
ho_cancel_t cancel)
|
||||
ho_cancel_t cancel,
|
||||
ho_failure_t failure)
|
||||
{
|
||||
DevAssert(rrc != NULL);
|
||||
DevAssert(ue != NULL);
|
||||
DevAssert(target_du != NULL);
|
||||
// source_du might be NULL -> inter-CU handover
|
||||
DevAssert(ho_prep_info->buf != NULL && ho_prep_info->len > 0);
|
||||
DevAssert(ue->ho_context);
|
||||
|
||||
if (ue->ho_context != NULL) {
|
||||
LOG_E(NR_RRC, "handover for UE %u ongoing, cannot trigger new\n", ue->rrc_ue_id);
|
||||
return;
|
||||
}
|
||||
// if any reconfiguration is ongoing, abort handover request
|
||||
for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) {
|
||||
if (ue->xids[i] != RRC_ACTION_NONE) {
|
||||
@@ -141,15 +140,13 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc,
|
||||
}
|
||||
}
|
||||
|
||||
// if we know the source DU (F1 handover), allocate the context for it,
|
||||
// otherwise only for the target DU (N2, Xn)
|
||||
ho_ctx_type_t ctx_type = source_du != NULL ? HO_CTX_BOTH : HO_CTX_TARGET;
|
||||
nr_handover_context_t *ho_ctx = alloc_ho_ctx(ctx_type);
|
||||
nr_handover_context_t *ho_ctx = ue->ho_context;
|
||||
ho_ctx->target->du = target_du;
|
||||
// we will know target->{du_ue_id,new_rnti} once we have UE ctxt setup
|
||||
// response
|
||||
ho_ctx->target->ho_req_ack = ack;
|
||||
ho_ctx->target->ho_success = success;
|
||||
ho_ctx->target->ho_failure = failure;
|
||||
|
||||
const f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id);
|
||||
if (source_du != NULL) {
|
||||
@@ -164,7 +161,7 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc,
|
||||
int result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&ho_ctx->source->old_cellGroupConfig, ue->masterCellGroup);
|
||||
AssertFatal(result == 0, "error during asn_copy() of CellGroupConfig\n");
|
||||
}
|
||||
ue->ho_context = ho_ctx;
|
||||
|
||||
LOG_A(NR_RRC,
|
||||
"Handover triggered for UE %u/RNTI %04x towards DU %ld/assoc_id %d/PCI %d\n",
|
||||
ue->rrc_ue_id,
|
||||
@@ -222,27 +219,12 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc,
|
||||
free_ue_context_setup_req(&ue_context_setup_req);
|
||||
}
|
||||
|
||||
/*
|
||||
void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, int nr_cgi, uint8_t *ho_prep_info, uint32_t *ho_prep_len, void *pdu_session)
|
||||
{
|
||||
// call from outside (via NGAP message), with the request for handover for a
|
||||
// (any) UE. This handler should, in order:
|
||||
//
|
||||
// 1. look up the target DU =du= via get_du_by_cell_id(nr_cgi)
|
||||
// (and reject the request if it can't find the DU)
|
||||
// 2. create a context for a new UE =UE=, based on pdu_session information
|
||||
// 3. call nr_initiate_handover(rrc, UE, NULL, du, ho_prep_info, ho_prep_len);
|
||||
//
|
||||
// after success of the DU, send the result via =success_ptr= to the source
|
||||
// CU.
|
||||
}
|
||||
*/
|
||||
|
||||
typedef struct deliver_ue_ctxt_modification_data_t {
|
||||
gNB_RRC_INST *rrc;
|
||||
f1ap_ue_context_mod_req_t *modification_req;
|
||||
sctp_assoc_t assoc_id;
|
||||
} deliver_ue_ctxt_modification_data_t;
|
||||
|
||||
static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id)
|
||||
{
|
||||
DevAssert(deliver_pdu_data != NULL);
|
||||
@@ -251,7 +233,8 @@ static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id,
|
||||
data->modification_req->rrc_container = &ba;
|
||||
data->rrc->mac_rrc.ue_context_modification_request(data->assoc_id, data->modification_req);
|
||||
}
|
||||
static void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len)
|
||||
|
||||
void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len)
|
||||
{
|
||||
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id);
|
||||
|
||||
@@ -355,6 +338,13 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain
|
||||
uint8_t buf[NR_RRC_BUF_SIZE];
|
||||
int size = do_NR_HandoverPreparationInformation(ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len, buf, sizeof buf);
|
||||
|
||||
// Allocate handover context (both CU and DU)
|
||||
if (ue->ho_context != NULL) {
|
||||
LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", ue->rrc_ue_id);
|
||||
return;
|
||||
}
|
||||
ue->ho_context = alloc_ho_ctx(HO_CTX_BOTH);
|
||||
|
||||
// corresponds to a "handover request", 38.300 Sec 9.3.2.3
|
||||
// see also 38.413 Sec 9.3.1.29 for information on source-CU to target-CU
|
||||
// information (Source NG-RAN Node to Target NG-RAN Node Transparent Container)
|
||||
@@ -365,7 +355,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain
|
||||
ho_success_t success = nr_rrc_f1_ho_complete;
|
||||
ho_cancel_t cancel = nr_rrc_cancel_f1_ho;
|
||||
byte_array_t hpi = {.buf = buf, .len = size};
|
||||
nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel);
|
||||
nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel, NULL);
|
||||
}
|
||||
|
||||
void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue)
|
||||
@@ -399,3 +389,224 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id)
|
||||
|
||||
nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du);
|
||||
}
|
||||
|
||||
/** @brief Generate the HandoverPreparationInformation to be carried
|
||||
* in the RRC Container (9.3.1.29 of 3GPP TS 38.413) of the Source
|
||||
* NG-RAN Node to Target NG-RAN Node Transparent Container IE */
|
||||
static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, int serving_pci)
|
||||
{
|
||||
nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue, 0, false);
|
||||
params.ue_cap = ue->ue_cap_buffer;
|
||||
|
||||
byte_array_t hoPrepInfo = get_HandoverPreparationInformation(¶ms, serving_pci);
|
||||
free_RRCReconfiguration_params(params);
|
||||
|
||||
if (hoPrepInfo.len < 0) {
|
||||
LOG_E(NR_RRC, "HandoverPreparationInformation generation failed for UE %d\n", ue->rrc_ue_id);
|
||||
return hoPrepInfo;
|
||||
}
|
||||
|
||||
LOG_D(NR_RRC, "HO LOG: Handover Preparation for UE %lu Encoded (%zd bytes)\n", ue->amf_ue_ngap_id, hoPrepInfo.len);
|
||||
|
||||
return hoPrepInfo;
|
||||
}
|
||||
|
||||
static byte_array_t rrc_gNB_encode_HandoverCommand(gNB_RRC_UE_t *UE, gNB_RRC_INST *rrc)
|
||||
{
|
||||
DevAssert(UE->ho_context);
|
||||
DevAssert(UE->ho_context->target);
|
||||
|
||||
NR_SecurityConfig_t *sec = calloc_or_fail(1, sizeof(*sec));
|
||||
sec->keyToUse = calloc_or_fail(1, sizeof(*sec->keyToUse));
|
||||
*sec->keyToUse = NR_SecurityConfig__keyToUse_master;
|
||||
sec->securityAlgorithmConfig = calloc_or_fail(1, sizeof(*sec->securityAlgorithmConfig));
|
||||
struct NR_SecurityAlgorithmConfig *alg = sec->securityAlgorithmConfig;
|
||||
alg->cipheringAlgorithm = UE->ciphering_algorithm;
|
||||
alg->integrityProtAlgorithm = calloc_or_fail(1, sizeof(*alg->integrityProtAlgorithm));
|
||||
*alg->integrityProtAlgorithm = UE->integrity_algorithm;
|
||||
|
||||
// Mark source gNB's measurement configuration for removal in UE->measConfig
|
||||
if (UE->ho_context->target->ue_ho_prep_info.len) {
|
||||
fill_removal_lists_from_source_measConfig(UE->measConfig, UE->ho_context->target->ue_ho_prep_info);
|
||||
}
|
||||
|
||||
/* 3GPP TS 38.331 RadioBearerConfig: re-establish PDCP whenever
|
||||
the security key used for this radio bearer changes, e.g. for SRB2
|
||||
when receiving reconfiguration with sync [...]. Similarly, for DRBs
|
||||
PDCP re-establishment is necessary with derivation of new UP keys */
|
||||
nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, UE, (1 << 1) | (1 << 2), true);
|
||||
UE->xids[params.transaction_id] = RRC_DEDICATED_RECONF;
|
||||
|
||||
params.security_config = sec;
|
||||
params.masterKeyUpdate = true;
|
||||
params.nextHopChainingCount = UE->nh_ncc;
|
||||
byte_array_t out = get_HandoverCommandMessage(¶ms);
|
||||
|
||||
// Free remove lists in UE->measConfig
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_MeasIdToRemoveList, UE->measConfig->measIdToRemoveList);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_ReportConfigToRemoveList, UE->measConfig->reportConfigToRemoveList);
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_MeasObjectToRemoveList, UE->measConfig->measObjectToRemoveList);
|
||||
|
||||
free_RRCReconfiguration_params(params);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** @brief This callback is used by the target gNB
|
||||
* to trigger the Handover Request Acknowledge towards the AMF */
|
||||
static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
|
||||
{
|
||||
AssertFatal(cu_exists_f1_ue_data(UE->rrc_ue_id), "No CU found for rrc_ue_id %d\n", UE->rrc_ue_id);
|
||||
|
||||
f1_ue_data_t previous_data = cu_get_f1_ue_data(UE->rrc_ue_id);
|
||||
/* this is the callback for N2 handover after F1 UE context setup response in
|
||||
* the handover case. Check that there was no associated DU, then set it */
|
||||
AssertFatal(previous_data.secondary_ue == -1, "there was already a DU present\n");
|
||||
previous_data.secondary_ue = UE->ho_context->target->du_ue_id;
|
||||
bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &previous_data);
|
||||
DevAssert(success);
|
||||
LOG_I(NR_RRC, "Updated CU F1AP Context for UE %d, DU Id : %u\n", UE->rrc_ue_id, UE->ho_context->target->du_ue_id);
|
||||
|
||||
byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc);
|
||||
if (hoCommand.len < 0) {
|
||||
LOG_E(NR_RRC, "ASN1 message encoding failed: failed to generate Handover Command Message\n");
|
||||
ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id,
|
||||
.cause.type = NGAP_CAUSE_RADIO_NETWORK,
|
||||
.cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM};
|
||||
UE->ho_context->target->ho_failure(rrc, UE->rrc_ue_id, &fail);
|
||||
return;
|
||||
} else {
|
||||
LOG_D(NR_RRC, "HO LOG: Handover Command for UE %u Encoded (%ld bytes)\n", UE->rrc_ue_id, hoCommand.len);
|
||||
}
|
||||
|
||||
rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(rrc, UE, hoCommand);
|
||||
free_byte_array(hoCommand);
|
||||
}
|
||||
|
||||
/** @brief This callback is used by the target gNB
|
||||
* to trigger the Handover Notify towards the AMF */
|
||||
static void nr_rrc_n2_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
|
||||
{
|
||||
rrc_gNB_send_NGAP_HANDOVER_NOTIFY(rrc, UE);
|
||||
}
|
||||
|
||||
/** @brief This callback is used by the source gNB to inform the AMF
|
||||
* about the cancellation of an ongoing handover */
|
||||
static void nr_rrc_n2_ho_cancel(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
|
||||
{
|
||||
DevAssert(UE->ho_context);
|
||||
DevAssert(UE->ho_context->source);
|
||||
ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_CANCELLED};
|
||||
rrc_gNB_send_NGAP_HANDOVER_CANCEL(rrc->module_id, UE, cause);
|
||||
}
|
||||
|
||||
/** @brief Callback function to trigger NG Handover Failure on the target gNB, to inform the AMF
|
||||
* that the preparation of resources has failed (e.g. unsatisfied criteria, gNB is already loaded).
|
||||
* This message represents an Unsuccessful Outcome of the Handover Resource Allocation */
|
||||
void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg)
|
||||
{
|
||||
LOG_I(NR_RRC, "Triggering N2 Handover Failure\n");
|
||||
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, msg);
|
||||
LOG_I(NR_RRC, "Send UE Context Release for gnb_ue_id %d\n", gnb_ue_id);
|
||||
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, gnb_ue_id);
|
||||
rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(rrc->module_id, ue_context_p, msg->cause);
|
||||
return;
|
||||
}
|
||||
|
||||
/** @brief Trigger N2 Handover on the target gNB:
|
||||
* 1) set up callbacks
|
||||
* 2) initiate N2 handover on target NG-RAN, which triggers the set up
|
||||
* of HO context and UE Context Setup procedures */
|
||||
void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue)
|
||||
{
|
||||
ho_req_ack_t ack = nr_rrc_n2_ho_acknowledge;
|
||||
ho_success_t success = nr_rrc_n2_ho_complete;
|
||||
ho_failure_t failure = nr_rrc_n2_ho_failure;
|
||||
|
||||
const nr_rrc_du_container_t *target_du = get_du_for_ue(rrc, ue->rrc_ue_id);
|
||||
nr_initiate_handover(rrc, ue, NULL, target_du, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure);
|
||||
FREE_AND_ZERO_BYTE_ARRAY(ue->ho_context->target->ue_ho_prep_info);
|
||||
|
||||
NR_UE_NR_Capability_t *ue_cap = get_ue_nr_capability(ue->rnti, ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len);
|
||||
ue->UE_Capability_nr = ue_cap;
|
||||
ue->UE_Capability_size = ue->ue_cap_buffer.len;
|
||||
|
||||
}
|
||||
|
||||
/** @brief Trigger N2 handover on source gNB:
|
||||
* 1) Prepare RRC Container with HandoverPreparationInformation message
|
||||
* 2) send NGAP Handover Required message */
|
||||
void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc,
|
||||
gNB_RRC_UE_t *ue,
|
||||
int serving_pci,
|
||||
const nr_neighbour_cell_t *neighbour_config)
|
||||
{
|
||||
byte_array_t hoPrepInfo = rrc_gNB_generate_HandoverPreparationInformation(rrc, ue, serving_pci);
|
||||
if (hoPrepInfo.len < 0) {
|
||||
free_byte_array(hoPrepInfo);
|
||||
LOG_E(NR_RRC, "Failed to trigger N2 handover on source gNB for UE %x\n", ue->rrc_ue_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate context for source
|
||||
if (ue->ho_context != NULL) {
|
||||
LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", ue->rrc_ue_id);
|
||||
return;
|
||||
}
|
||||
|
||||
ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE);
|
||||
ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id);
|
||||
ue->ho_context->source->ho_status_transfer = rrc_gNB_send_NGAP_ul_ran_status_transfer;
|
||||
ue->ho_context->source->ho_cancel = nr_rrc_n2_ho_cancel;
|
||||
|
||||
rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo);
|
||||
free_byte_array(hoPrepInfo);
|
||||
}
|
||||
|
||||
extern const nr_neighbour_cell_t *get_neighbour_cell_by_pci(const neighbour_cell_configuration_t *cell, int pci);
|
||||
extern const neighbour_cell_configuration_t *get_neighbour_cell_config(const gNB_RRC_INST *rrc, int cell_id);
|
||||
|
||||
void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t rrc_ue_id)
|
||||
{
|
||||
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, rrc_ue_id);
|
||||
if (ue_context_p == NULL) {
|
||||
LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: cannot find UE context\n", rrc_ue_id);
|
||||
return;
|
||||
}
|
||||
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
|
||||
|
||||
struct nr_rrc_du_container_t *du = get_du_for_ue(rrc, rrc_ue_id);
|
||||
if (du == NULL) {
|
||||
LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown DU\n", rrc_ue_id);
|
||||
return;
|
||||
}
|
||||
uint16_t scell_pci = du->setup_req->cell[0].info.nr_pci;
|
||||
|
||||
// Simulate handover on the same cell (testing purposes)
|
||||
if (neighbour_pci == scell_pci) {
|
||||
LOG_I(NR_RRC, "UE %d: trigger handover on the same cell PCI=%d\n", rrc_ue_id, neighbour_pci);
|
||||
nr_neighbour_cell_t neighbourConfig = {
|
||||
.isIntraFrequencyNeighbour = true,
|
||||
.gNB_ID = du->setup_req->gNB_DU_id,
|
||||
.nrcell_id = du->setup_req->cell[0].info.nr_cellid,
|
||||
.physicalCellId = du->setup_req->cell[0].info.nr_pci,
|
||||
.plmn = du->setup_req->cell[0].info.plmn,
|
||||
.subcarrierSpacing = du->setup_req->cell[0].info.tdd.tbw.scs,
|
||||
};
|
||||
nr_rrc_trigger_n2_ho(rrc, UE, neighbour_pci, &neighbourConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
const f1ap_served_cell_info_t *scell_du = get_cell_information_by_phycellId(scell_pci);
|
||||
DevAssert(scell_du);
|
||||
LOG_I(NR_RRC, "UE %d: triggered N2 HO, source PCI=%d to target PCI=%d\n", rrc_ue_id, scell_pci, neighbour_pci);
|
||||
|
||||
const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, scell_du->nr_cellid);
|
||||
const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci);
|
||||
|
||||
if (neighbour == NULL) {
|
||||
LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: could not find neighbour cell with PCI=%d\n", rrc_ue_id, neighbour_pci);
|
||||
return;
|
||||
}
|
||||
|
||||
nr_rrc_trigger_n2_ho(rrc, UE, scell_du->nr_pci, neighbour);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "common/utils/ds/byte_array.h"
|
||||
#include "nr_rrc_defs.h"
|
||||
|
||||
/* forward declarations */
|
||||
typedef struct gNB_RRC_INST_s gNB_RRC_INST;
|
||||
@@ -32,6 +33,8 @@ typedef struct nr_rrc_du_container_t nr_rrc_du_container_t;
|
||||
typedef struct NR_CellGroupConfig NR_CellGroupConfig_t;
|
||||
|
||||
typedef void (*ho_cancel_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
|
||||
typedef int (*ho_status_transfer_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status);
|
||||
|
||||
typedef struct nr_ho_source_cu {
|
||||
/// pointer to the (source) DU structure
|
||||
const nr_rrc_du_container_t *du;
|
||||
@@ -47,11 +50,16 @@ typedef struct nr_ho_source_cu {
|
||||
/// function pointer to announce the handover cancellation, e.g.,
|
||||
/// reestablishment
|
||||
ho_cancel_t ho_cancel;
|
||||
/// status transfer
|
||||
ho_status_transfer_t ho_status_transfer;
|
||||
} nr_ho_source_cu_t;
|
||||
|
||||
/* acknowledgement of handover request. buf+len is the RRC Reconfiguration */
|
||||
typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
|
||||
typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
|
||||
typedef void (*ho_failure_t)(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg);
|
||||
typedef void (*ho_trigger_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
|
||||
|
||||
typedef struct nr_ho_target_cu {
|
||||
/// pointer to the (target) DU structure
|
||||
const nr_rrc_du_container_t *du;
|
||||
@@ -61,10 +69,16 @@ typedef struct nr_ho_target_cu {
|
||||
uint32_t du_ue_id;
|
||||
/// new (target) RNTI (as for du_ue_id)
|
||||
rnti_t new_rnti;
|
||||
/// Handover Preparation Buffer
|
||||
byte_array_t ue_ho_prep_info;
|
||||
/// function pointer to trigger handover on target gNB
|
||||
ho_trigger_t ho_trigger;
|
||||
/// function pointer to announce handover request acknowledgment
|
||||
ho_req_ack_t ho_req_ack;
|
||||
/// function pointer to announce handover success
|
||||
ho_success_t ho_success;
|
||||
/// function pointer to announce the handover failure
|
||||
ho_failure_t ho_failure;
|
||||
} nr_ho_target_cu_t;
|
||||
|
||||
typedef struct nr_handover_context_s {
|
||||
@@ -72,7 +86,20 @@ typedef struct nr_handover_context_s {
|
||||
nr_ho_target_cu_t *target;
|
||||
} nr_handover_context_t;
|
||||
|
||||
typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t;
|
||||
nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type);
|
||||
|
||||
void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du);
|
||||
void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue);
|
||||
void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg);
|
||||
|
||||
void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc,
|
||||
gNB_RRC_UE_t *ue,
|
||||
int serving_pci,
|
||||
const nr_neighbour_cell_t *neighbour_config);
|
||||
|
||||
void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len);
|
||||
|
||||
void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
|
||||
|
||||
#endif /* RRC_GNB_MOBILITY_H_ */
|
||||
|
||||
+89
-19
@@ -532,19 +532,74 @@ static void nr_rrc_process_sib1(NR_UE_RRC_INST_t *rrc, NR_UE_RRC_SI_INFO *SI_inf
|
||||
nr_rrc_send_msg_to_mac(rrc, &rrc_msg);
|
||||
}
|
||||
|
||||
static void nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCReconfiguration_v1530_IEs_t *rec_1530, int gNB_index)
|
||||
static void nr_rrc_nh_update(NR_UE_RRC_INST_t *rrc, uint8_t *kamf, uint8_t *sync_input)
|
||||
{
|
||||
if (rec_1530->fullConfig) {
|
||||
// TODO perform the full configuration procedure as specified in 5.3.5.11 of 331
|
||||
LOG_E(NR_RRC, "RRCReconfiguration includes fullConfig but this is not implemented yet\n");
|
||||
uint8_t nh[SECURITY_KEY_LEN] = {0};
|
||||
nr_derive_nh(kamf, sync_input, nh);
|
||||
log_hex_buffer("Sync input = stored NH", rrc->nh, SECURITY_KEY_LEN);
|
||||
memcpy(rrc->nh, nh, SECURITY_KEY_LEN);
|
||||
rrc->nhcc++; // Increase stored nextHopChainingCount
|
||||
}
|
||||
|
||||
/** @brief AS security key update procedure (5.3.5.7 3GPP TS 38.331) */
|
||||
void as_security_key_update(NR_UE_RRC_INST_t *rrc, NR_MasterKeyUpdate_t *mku)
|
||||
{
|
||||
if (mku->nas_Container) {
|
||||
LOG_E(NR_RRC, "forward the nas-Container to the upper layers: not implemented yet\n");
|
||||
}
|
||||
if (rec_1530->masterCellGroup)
|
||||
nr_rrc_ue_process_masterCellGroup(rrc, rec_1530->masterCellGroup, rec_1530->fullConfig, gNB_index);
|
||||
if (rec_1530->masterKeyUpdate) {
|
||||
// TODO perform AS security key update procedure as specified in 5.3.5.7
|
||||
LOG_E(NR_RRC, "RRCReconfiguration includes masterKeyUpdate but this is not implemented yet\n");
|
||||
if (mku->keySetChangeIndicator) {
|
||||
LOG_E(NR_RRC, "derive or update the K gNB key based on the K AMF key, as specified in TS 33.501: not implemented yet\n");
|
||||
} else {
|
||||
/* derive or update the K gNB key based on the current K gNB key or the NH, using the nextHopChainingCount
|
||||
value indicated in the received masterKeyUpdate, as specified in 6.9.2.3.3 3GPP TS 33.501 */
|
||||
if (mku->nextHopChainingCount != rrc->nhcc) {
|
||||
// - If the UE received an NCC value that was different from the NCC associated with the currently active
|
||||
// K gNB/K eNB, the UE shall first synchronize the locally kept NH parameter by computing the function defined in
|
||||
// Annex A.10 iteratively (and increasing the NCC value until it matches the NCC value received from the source
|
||||
// ng-eNB/gNB via the HO command message.
|
||||
LOG_A(NR_RRC, "Received masterKeyUpdate (nextHopChainingCount %ld): update security keys\n", mku->nextHopChainingCount);
|
||||
/** @todo: The KAMF should be obtained from NAS. This exchange over ITTI must be synchronized
|
||||
* with the rest of the RRCReconfiguration procedure, in particular, the RadioBearerConfig
|
||||
* processing that triggers bearer modifications. Security configueration of bearers must
|
||||
* complete using the newly derived keys. As a workaround NAS is directly accessed here. */
|
||||
nr_ue_nas_t *nas = get_ue_nas_info(rrc->ue_id);
|
||||
uint8_t *kamf = nas->security.kamf;
|
||||
log_hex_buffer("Stored kamf", kamf, SECURITY_KEY_LEN);
|
||||
if (rrc->nhcc == 0) { // First derivation
|
||||
derive_kgnb(kamf, 0, rrc->kgnb);
|
||||
log_hex_buffer("Sync input = derived kgnb", rrc->kgnb, SECURITY_KEY_LEN);
|
||||
nr_rrc_nh_update(rrc, kamf, rrc->kgnb);
|
||||
}
|
||||
for (int i = rrc->nhcc; i < mku->nextHopChainingCount; i++) { // Following derivations
|
||||
LOG_D(NR_RRC, "Derive keys for ChainingCount = %d\n", i);
|
||||
nr_rrc_nh_update(rrc, kamf, rrc->nh);
|
||||
}
|
||||
nr_derive_key_ng_ran_star(rrc->phyCellID, rrc->arfcn_ssb, rrc->nh, rrc->kgnb);
|
||||
// When the NCC values match, the UE shall compute the K NG-RAN *
|
||||
// from the synchronized NH parameter and the target PCI and its frequency ARFCN-DL/EARFCN-DL using the
|
||||
// function defined in Annex A.11 and A.12.
|
||||
// The UE shall use the KNG-RAN * as the K gNB when communicating with the target gNB and as the KeNB when
|
||||
// communicating with the target ng-eNB.
|
||||
} else {
|
||||
nr_derive_key_ng_ran_star(rrc->phyCellID, rrc->arfcn_ssb, rrc->kgnb, rrc->kgnb);
|
||||
}
|
||||
log_hex_buffer("Derived kgnb", rrc->kgnb, SECURITY_KEY_LEN);
|
||||
}
|
||||
/* Check if there is dedicated NAS information to forward to NAS */
|
||||
}
|
||||
|
||||
static nr_pdcp_entity_security_keys_and_algos_t get_security_rrc_parameters(NR_UE_RRC_INST_t *ue, bool cp)
|
||||
{
|
||||
nr_pdcp_entity_security_keys_and_algos_t out = {0};
|
||||
out.ciphering_algorithm = ue->cipheringAlgorithm;
|
||||
out.integrity_algorithm = ue->integrityProtAlgorithm;
|
||||
nr_derive_key(cp ? RRC_ENC_ALG : UP_ENC_ALG, ue->cipheringAlgorithm, ue->kgnb, out.ciphering_key);
|
||||
nr_derive_key(cp ? RRC_INT_ALG : UP_INT_ALG, ue->integrityProtAlgorithm, ue->kgnb, out.integrity_key);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** @brief Check if there is dedicated NAS information to forward to NAS */
|
||||
static void nr_rrc_process_dedicatedNAS_MessageList(NR_UE_RRC_INST_t *rrc, NR_RRCReconfiguration_v1530_IEs_t *rec_1530)
|
||||
{
|
||||
if (rec_1530->dedicatedNAS_MessageList) {
|
||||
struct NR_RRCReconfiguration_v1530_IEs__dedicatedNAS_MessageList *tmp = rec_1530->dedicatedNAS_MessageList;
|
||||
for (int i = 0; i < tmp->list.count; i++) {
|
||||
@@ -557,6 +612,21 @@ static void nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCRe
|
||||
}
|
||||
tmp->list.count = 0; // to prevent the automatic free by ASN1_FREE
|
||||
}
|
||||
}
|
||||
|
||||
static void nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCReconfiguration_v1530_IEs_t *rec_1530, int gNB_index)
|
||||
{
|
||||
if (rec_1530->fullConfig) {
|
||||
// TODO perform the full configuration procedure as specified in 5.3.5.11 of 331
|
||||
LOG_E(NR_RRC, "RRCReconfiguration includes fullConfig but this is not implemented yet\n");
|
||||
}
|
||||
if (rec_1530->masterCellGroup)
|
||||
nr_rrc_ue_process_masterCellGroup(rrc, rec_1530->masterCellGroup, rec_1530->fullConfig, gNB_index);
|
||||
if (rec_1530->masterKeyUpdate) {
|
||||
as_security_key_update(rrc, rec_1530->masterKeyUpdate);
|
||||
nr_pdcp_entity_security_keys_and_algos_t sp = get_security_rrc_parameters(rrc, true);
|
||||
nr_pdcp_config_set_security(rrc->ue_id, 1, true, &sp);
|
||||
}
|
||||
NR_UE_RRC_SI_INFO *SI_info = &rrc->perNB[gNB_index].SInfo;
|
||||
if (rec_1530->dedicatedSIB1_Delivery) {
|
||||
NR_SIB1_t *sib1 = NULL;
|
||||
@@ -633,6 +703,9 @@ static void nr_rrc_ue_process_rrcReconfiguration(NR_UE_RRC_INST_t *rrc, int gNB_
|
||||
case NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration: {
|
||||
NR_RRCReconfiguration_IEs_t *ie = reconfiguration->criticalExtensions.choice.rrcReconfiguration;
|
||||
|
||||
if (ie->nonCriticalExtension)
|
||||
nr_rrc_process_reconfiguration_v1530(rrc, ie->nonCriticalExtension, gNB_index);
|
||||
|
||||
if (ie->radioBearerConfig) {
|
||||
LOG_I(NR_RRC, "RRCReconfiguration includes radio Bearer Configuration\n");
|
||||
nr_rrc_ue_process_RadioBearerConfig(rrc, ie->radioBearerConfig);
|
||||
@@ -640,8 +713,11 @@ static void nr_rrc_ue_process_rrcReconfiguration(NR_UE_RRC_INST_t *rrc, int gNB_
|
||||
xer_fprint(stdout, &asn_DEF_NR_RadioBearerConfig, (const void *)ie->radioBearerConfig);
|
||||
}
|
||||
|
||||
/** @note This triggers PDU Session Establishment Accept which sets up the TUN interface.
|
||||
* SDAP entity is a pre-requisite, therefore the radioBearerConfig has to be processed
|
||||
* (in nr_rrc_ue_process_RadioBearerConfig, add_drb) early enough, or it may cause a race condition in SDAP. */
|
||||
if (ie->nonCriticalExtension)
|
||||
nr_rrc_process_reconfiguration_v1530(rrc, ie->nonCriticalExtension, gNB_index);
|
||||
nr_rrc_process_dedicatedNAS_MessageList(rrc, ie->nonCriticalExtension);
|
||||
|
||||
if (ie->secondaryCellGroup) {
|
||||
NR_CellGroupConfig_t *cellGroupConfig = NULL;
|
||||
@@ -1959,14 +2035,8 @@ static void nr_rrc_ue_process_RadioBearerConfig(NR_UE_RRC_INST_t *ue_rrc,
|
||||
ue_rrc->integrityProtAlgorithm = *radioBearerConfig->securityConfig->securityAlgorithmConfig->integrityProtAlgorithm;
|
||||
}
|
||||
}
|
||||
security_rrc_parameters.ciphering_algorithm = ue_rrc->cipheringAlgorithm;
|
||||
security_rrc_parameters.integrity_algorithm = ue_rrc->integrityProtAlgorithm;
|
||||
nr_derive_key(RRC_ENC_ALG, ue_rrc->cipheringAlgorithm, ue_rrc->kgnb, security_rrc_parameters.ciphering_key);
|
||||
nr_derive_key(RRC_INT_ALG, ue_rrc->integrityProtAlgorithm, ue_rrc->kgnb, security_rrc_parameters.integrity_key);
|
||||
security_up_parameters.ciphering_algorithm = ue_rrc->cipheringAlgorithm;
|
||||
security_up_parameters.integrity_algorithm = ue_rrc->integrityProtAlgorithm;
|
||||
nr_derive_key(UP_ENC_ALG, ue_rrc->cipheringAlgorithm, ue_rrc->kgnb, security_up_parameters.ciphering_key);
|
||||
nr_derive_key(UP_INT_ALG, ue_rrc->integrityProtAlgorithm, ue_rrc->kgnb, security_up_parameters.integrity_key);
|
||||
security_rrc_parameters = get_security_rrc_parameters(ue_rrc, true);
|
||||
security_up_parameters = get_security_rrc_parameters(ue_rrc, false);
|
||||
}
|
||||
|
||||
if (radioBearerConfig->srb_ToAddModList != NULL) {
|
||||
|
||||
@@ -248,6 +248,10 @@ typedef struct NR_UE_RRC_INST_s {
|
||||
e_NR_IntegrityProtAlgorithm integrityProtAlgorithm;
|
||||
long keyToUse;
|
||||
bool as_security_activated;
|
||||
/// Next Hop Chaining Count
|
||||
uint8_t nh[32];
|
||||
uint64_t nhcc;
|
||||
|
||||
bool detach_after_release;
|
||||
NR_timer_t release_timer;
|
||||
NR_RRCRelease_t *RRCRelease;
|
||||
|
||||
Reference in New Issue
Block a user