diff --git a/CMakeLists.txt b/CMakeLists.txt
index fa2d497589..058bfd7a39 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -363,6 +363,7 @@ add_library(ngap
${NGAP_DIR}/ngap_gNB.c
${NGAP_DIR}/ngap_common.c
${NGAP_DIR}/ngap_gNB_context_management_procedures.c
+ ${NGAP_DIR}/ngap_gNB_mobility_management.c
${NGAP_DIR}/ngap_gNB_decoder.c
${NGAP_DIR}/ngap_gNB_encoder.c
${NGAP_DIR}/ngap_gNB_handlers.c
diff --git a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf
index 051ebd04fc..4294f920ad 100644
--- a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf
+++ b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf
@@ -15,7 +15,7 @@ gNBs =
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
-
+ @include "neighbour-config.conf"
nr_cellid = 12345678L;
diff --git a/ci-scripts/conf_files/gnb-cucp.sa.e1-ho-n2.conf b/ci-scripts/conf_files/gnb-cucp.sa.e1-ho-n2.conf
new file mode 100644
index 0000000000..ad4cca969e
--- /dev/null
+++ b/ci-scripts/conf_files/gnb-cucp.sa.e1-ho-n2.conf
@@ -0,0 +1,82 @@
+Active_gNBs = ( "cu-cp-0");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xe00;
+
+# cell_type = "CELL_MACRO_GNB";
+
+ gNB_name = "cu-cp-0";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
+ @include "neighbour-config.conf"
+
+ nr_cellid = 12345678L;
+
+ tr_s_preference = "f1";
+
+ local_s_address = "192.168.71.150";
+ remote_s_address = "192.168.71.171";
+ local_s_portd = 2153;
+ remote_s_portd = 2153;
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+
+
+ ////////// AMF parameters:
+ amf_ip_address = ({ ipv4 = "192.168.71.132"; });
+
+ E1_INTERFACE =
+ (
+ {
+ type = "cp";
+ ipv4_cucp = "192.168.71.150";
+ port_cucp = 38462;
+ ipv4_cuup = "0.0.0.0"; # multiple CU-UPs
+ port_cuup = 38462;
+ }
+ )
+
+ NETWORK_INTERFACES :
+ {
+ GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.150";
+ };
+ }
+);
+
+security = {
+ # preferred ciphering algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nea0, nea1, nea2, nea3
+ ciphering_algorithms = ( "nea0" );
+
+ # preferred integrity algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nia0, nia1, nia2, nia3
+ integrity_algorithms = ( "nia2", "nia0" );
+
+ # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter
+ # what 'ciphering_algorithms' configures; same thing for 'drb_integrity'
+ drb_ciphering = "yes";
+ drb_integrity = "no";
+};
+
+log_config : {
+ global_log_level = "info";
+ pdcp_log_level = "info";
+ rrc_log_level = "info";
+ f1ap_log_level = "info";
+ ngap_log_level = "info";
+};
diff --git a/ci-scripts/conf_files/neighbour-config.conf b/ci-scripts/conf_files/neighbour-config.conf
index 875129365e..a498dbf157 100644
--- a/ci-scripts/conf_files/neighbour-config.conf
+++ b/ci-scripts/conf_files/neighbour-config.conf
@@ -1,52 +1,51 @@
-neighbour_list = (
- {
- nr_cellid = 1;
- neighbour_cell_configuration = (
- {
- gNB_ID = 0xb00;
- nr_cellid = 2;
- physical_cellId = 0x002;
- absoluteFrequencySSB = 641280 ;
- subcarrierSpacing = 1; #30 KHz
- plmn = { mcc = 216; mnc = 03; mnc_length = 2};
- tracking_area_code = 2;
- },
- {
- gNB_ID = 0xa00;
- nr_cellid = 3;
- physical_cellId = 0x003;
- absoluteFrequencySSB = 641280 ;
- subcarrierSpacing = 1; #30 KHz
- plmn = { mcc = 999; mnc = 99; mnc_length = 2};
- tracking_area_code = 3;
- },
- {
- gNB_ID = 0xc00;
- nr_cellid = 4;
- physical_cellId = 0x004;
- absoluteFrequencySSB = 641280 ;
- subcarrierSpacing = 1; #30 KHz
- plmn = { mcc = 999; mnc = 99; mnc_length = 2};
- tracking_area_code = 4;
- }
- )
- },
- {
- nr_cellid = 2;
- neighbour_cell_configuration = (
- {
- gNB_ID = 0xe00;
- nr_cellid = 1;
- physical_cellId = 0x000;
- absoluteFrequencySSB = 641280 ;
- subcarrierSpacing = 1; #30 KHz
- plmn = { mcc = 001; mnc = 01; mnc_length = 2};
- tracking_area_code = 1;
- }
- )
- }
- );
+############################################################
+# gNB-to-gNB neighbour list + measurement configuration #
+# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) #
+############################################################
+neighbour_list = (
+ ##########################################################
+ # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 1) #
+ ##########################################################
+ {
+ nr_cellid = 0; # Serving cell of gNB 0xe00
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xb00;
+ nr_cellid = 1; # Cell served by gNB 0xb00
+ physical_cellId = 1;
+ absoluteFrequencySSB= 621312;
+ subcarrierSpacing = 1; # 30 kHz
+ band = 78;
+ plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
+ tracking_area_code = 1;
+ }
+ );
+ },
+
+ ##########################################################
+ # Entry USED BY gNB_ID = 0xb00 (nr_cellid = 2) #
+ ##########################################################
+ {
+ nr_cellid = 1; # Serving cell of gNB 0xb00
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xe00;
+ nr_cellid = 0; # Cell served by gNB 0xe00
+ physical_cellId = 0;
+ absoluteFrequencySSB= 641280;
+ subcarrierSpacing = 1; # 30 kHz
+ band = 78;
+ plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
+ tracking_area_code = 1;
+ }
+ );
+ }
+);
+
+############################################################
+# Common NR measurement-event configuration #
+############################################################
nr_measurement_configuration = {
Periodical = {
diff --git a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml
new file mode 100644
index 0000000000..1b2e84046c
--- /dev/null
+++ b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml
@@ -0,0 +1,207 @@
+
+
+ rfsim-5gnr-f1-n2-ho
+ F1 SA TDD N2 Handover
+ wrench
+
+ 111111
+ 800813
+ 000001
+ 000002
+ 000003
+ 020001
+ 020002
+ 030001
+ 030002
+ 000004
+ 040001
+ 050001
+ 020001
+ 020002
+ 050002
+ 030001
+ 030002
+ 050003
+ 020001
+ 020002
+ 050004
+ 050001
+ 020001
+ 020002
+ 050002
+ 100001
+ 222222
+
+
+
+
+ Pull_Local_Registry
+ Pull Images from Local Registry
+ 0
+ oai-gnb-asan oai-nr-cuup-asan oai-nr-ue-asan
+
+
+
+ Create_Workspace
+ Create new Workspace
+ 0
+ 0
+
+
+
+ Deploy_Object
+ Deploy OAI 5G CoreNetwork
+ ci-scripts/yaml_files/5g_rfsimulator_n2_ho
+ mysql oai-amf oai-smf oai-upf oai-ext-dn
+ 0
+ 0
+
+
+
+ Deploy_Object
+ Deploy OAI 5G CU-CP/CU-UP/DU 0 + UE RFsim SA
+ ci-scripts/yaml_files/5g_rfsimulator_n2_ho
+ oai-cucp-0 oai-cuup-0 oai-du-0 oai-nr-ue
+ 0
+ 0
+
+
+
+ Attach_UE
+ Attach OAI UE (Wait for IP)
+ rfsim5g_ue
+ acamas
+
+
+
+ Deploy_Object
+ Deploy OAI 5G CU-CP/CU-UP/DU 1 RFsim SA
+ ci-scripts/yaml_files/5g_rfsimulator_n2_ho
+ oai-cucp-1 oai-cuup-1 oai-du-1
+ 0
+ 0
+
+
+
+ Ping
+ Ping NR-UE from ext-dn
+ rfsim5g_ext_dn
+ acamas
+ rfsim5g_ue
+ acamas
+ -c 100 -i 0.25
+ 5
+
+
+
+ Ping
+ Ping ext-dn from NR-UE
+ rfsim5g_ue
+ acamas
+ rfsim5g_ext_dn
+ acamas
+ -c 100 -i 0.25
+ 10
+
+
+
+ Iperf
+ Iperf (DL/10Mbps/UDP)(30 sec)
+ -u -b 3M -t 20 -R
+ rfsim5g_ue
+ acamas
+ rfsim5g_ext_dn
+ acamas
+ 5
+ 90
+
+
+
+ Iperf
+ Iperf (UL/4Mbps/UDP)(30 sec)
+ -u -b 1M -t 20
+ rfsim5g_ue
+ acamas
+ rfsim5g_ext_dn
+ acamas
+ 5
+ 90
+
+
+
+ IdleSleep
+ Sleep
+ 5
+
+
+
+ Custom_Command
+ Trigger N2 Handover
+ acamas
+ echo ci trigger_n2_ho 1,1 | ncat 192.168.71.150 9090
+ yes
+
+
+
+ Custom_Command
+ UE (1) connected to DU-PCI 1?
+ acamas
+ echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234"
+
+
+
+ Custom_Command
+ Trigger N2 Handover
+ acamas
+ echo ci trigger_n2_ho 0,1 | ncat 192.168.71.180 9090
+ yes
+
+
+
+ Custom_Command
+ UE (1) connected to DU-PCI 0?
+ acamas
+ echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584"
+
+
+
+ Undeploy_Object
+ true
+ Undeploy all OAI 5G stack
+ ci-scripts/yaml_files/5g_rfsimulator_n2_ho
+ 0
+ 0
+ 10,100,100,100
+ 10,100,100,100
+
+
+
+ Clean_Test_Server_Images
+ true
+ Clean Test Images on Test Server
+ 0
+ oai-gnb-asan oai-nr-cuup-asan oai-nr-ue-asan
+
+
+
diff --git a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml
index 94ef3e7868..73d2505e92 100644
--- a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml
+++ b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml
@@ -110,6 +110,7 @@ services:
public_net:
ipv4_address: 192.168.71.150
volumes:
+ - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf
- ../../conf_files/gnb-cu.sa.band78.106prb.conf:/opt/oai-gnb/etc/gnb.conf
healthcheck:
test: /bin/bash -c "pgrep nr-softmodem"
diff --git a/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml
new file mode 100644
index 0000000000..1149d4f156
--- /dev/null
+++ b/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml
@@ -0,0 +1,308 @@
+services:
+ mysql:
+ container_name: "rfsim5g-mysql"
+ image: mysql:8.0
+ volumes:
+ - ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
+ - ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
+ environment:
+ - TZ=Europe/Paris
+ - MYSQL_DATABASE=oai_db
+ - MYSQL_USER=test
+ - MYSQL_PASSWORD=test
+ - MYSQL_ROOT_PASSWORD=linux
+ healthcheck:
+ test: /bin/bash -c "/tmp/mysql-healthcheck.sh"
+ interval: 10s
+ timeout: 5s
+ retries: 30
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.131
+ oai-amf:
+ container_name: "rfsim5g-oai-amf"
+ image: oaisoftwarealliance/oai-amf:ci-tmp-pr-372-26c14bcd
+ environment:
+ - TZ=Europe/paris
+ volumes:
+ - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
+ depends_on:
+ - mysql
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.132
+ oai-smf:
+ container_name: "rfsim5g-oai-smf"
+ image: oaisoftwarealliance/oai-smf:v2.1.9
+ environment:
+ - TZ=Europe/Paris
+ volumes:
+ - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
+ depends_on:
+ - oai-amf
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.133
+ oai-upf:
+ container_name: "rfsim5g-oai-upf"
+ image: oaisoftwarealliance/oai-upf:v2.1.9
+ environment:
+ - TZ=Europe/Paris
+ volumes:
+ - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
+ depends_on:
+ - oai-smf
+ cap_add:
+ - NET_ADMIN
+ - SYS_ADMIN
+ cap_drop:
+ - ALL
+ privileged: true
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.134
+ interface_name: eth0
+ traffic_net:
+ ipv4_address: 192.168.72.134
+ interface_name: eth1
+ oai-ext-dn:
+ privileged: true
+ container_name: rfsim5g-oai-ext-dn
+ image: oaisoftwarealliance/trf-gen-cn5g:focal
+ entrypoint: /bin/bash -c \
+ "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
+ "ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity"
+ depends_on:
+ - oai-upf
+ networks:
+ traffic_net:
+ ipv4_address: 192.168.72.135
+ healthcheck:
+ test: /bin/bash -c "ping -c 2 192.168.72.134"
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ oai-cucp-0:
+ image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
+ container_name: rfsim5g-oai-cucp-0
+ cap_drop:
+ - ALL
+ environment:
+ USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr 192.168.71.190
+ --telnetsrv --telnetsrv.shrmod ci
+ --telnetsrv.listenaddr 192.168.71.150
+ --gNBs.[0].nr_cellid 0
+ --log_config.global_log_options level,nocolor,time
+ ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0
+ depends_on:
+ - oai-amf
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.150
+ volumes:
+ - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf
+ - ../../conf_files/gnb-cucp.sa.e1-ho-n2.conf:/opt/oai-gnb/etc/gnb.conf
+ healthcheck:
+ test: /bin/bash -c "pgrep nr-softmodem"
+ start_period: 10s
+ start_interval: 500ms
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ oai-cuup-0:
+ image: ${REGISTRY:-oaisoftwarealliance}/${NRCUUP_IMG:-oai-nr-cuup}:${TAG:-develop}
+ container_name: rfsim5g-oai-cuup-0
+ cap_drop:
+ - ALL
+ environment:
+ USE_ADDITIONAL_OPTIONS: --log_config.global_log_options level,nocolor,time
+ --gNBs.[0].E1_INTERFACE.[0].ipv4_cucp 192.168.71.150
+ --gNBs.[0].E1_INTERFACE.[0].ipv4_cuup 192.168.71.151
+ --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.151
+ --gNBs.[0].local_s_address 192.168.71.151
+ ASAN_OPTIONS: detect_leaks=0
+ depends_on:
+ - oai-cucp-0
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.151
+ volumes:
+ - ../../conf_files/gnb-cuup.sa.f1.conf:/opt/oai-gnb/etc/gnb.conf
+ healthcheck:
+ test: /bin/bash -c "pgrep nr-cuup"
+ start_period: 10s
+ start_interval: 500ms
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+
+ oai-du-0:
+ image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
+ container_name: rfsim5g-oai-du-0
+ cap_drop:
+ - ALL
+ environment:
+ USE_ADDITIONAL_OPTIONS: --rfsim
+ --log_config.global_log_options level,nocolor,time
+ --gNBs.[0].nr_cellid 0
+ --telnetsrv --telnetsrv.listenaddr 192.168.71.171
+ --telnetsrv.shrmod ci
+ --rfsimulator.serveraddr 192.168.71.190
+ --gNBs.[0].servingCellConfigCommon.[0].ra_ResponseWindow 5
+ ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0
+ depends_on:
+ - oai-cucp-0
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.171
+ volumes:
+ - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf
+ - ../../conf_files/gnb-du.sa.band78.106prb.rfsim.conf:/opt/oai-gnb/etc/gnb.conf
+ healthcheck:
+ test: /bin/bash -c "pgrep nr-softmodem"
+ start_period: 10s
+ start_interval: 500ms
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ oai-nr-ue:
+ image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
+ container_name: rfsim5g-oai-nr-ue
+ cap_drop:
+ - ALL
+ cap_add:
+ - NET_ADMIN # for interface bringup
+ - NET_RAW # for ping
+ environment:
+ USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr server
+ --band 78 -C 3619200000 -r 106 --numerology 1 --ssb 516
+ --uicc0.imsi 208990100001100
+ --log_config.global_log_options level,nocolor,time
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.190
+ devices:
+ - /dev/net/tun:/dev/net/tun
+ volumes:
+ - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
+ healthcheck:
+ test: /bin/bash -c "pgrep nr-uesoftmodem"
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ oai-cucp-1:
+ image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
+ container_name: rfsim5g-oai-cucp-1
+ cap_drop:
+ - ALL
+ environment:
+ USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr 192.168.71.190
+ --telnetsrv --telnetsrv.shrmod ci
+ --telnetsrv.listenaddr 192.168.71.180
+ --gNBs.[0].E1_INTERFACE.[0].ipv4_cucp 192.168.71.180
+ --gNBs.[0].E1_INTERFACE.[0].ipv4_cuup 192.168.71.181
+ --gNBs.[0].local_s_address 192.168.71.180
+ --gNBs.[0].remote_s_address 192.168.71.181
+ --gNBs.[0].gNB_ID 0xb00
+ --gNBs.[0].nr_cellid 1
+ --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NG_AMF 192.168.71.180
+ --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.180
+ --log_config.global_log_options level,nocolor,time
+ ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.180
+ volumes:
+ - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf
+ - ../../conf_files/gnb-cucp.sa.e1-ho-n2.conf:/opt/oai-gnb/etc/gnb.conf
+ healthcheck:
+ test: /bin/bash -c "pgrep nr-softmodem"
+ start_period: 10s
+ start_interval: 500ms
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ oai-cuup-1:
+ image: ${REGISTRY:-oaisoftwarealliance}/${NRCUUP_IMG:-oai-nr-cuup}:${TAG:-develop}
+ container_name: rfsim5g-oai-cuup-1
+ cap_drop:
+ - ALL
+ environment:
+ USE_ADDITIONAL_OPTIONS: --log_config.global_log_options level,nocolor,time
+ --gNBs.[0].E1_INTERFACE.[0].ipv4_cucp 192.168.71.180
+ --gNBs.[0].E1_INTERFACE.[0].ipv4_cuup 192.168.71.181
+ --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.181
+ --gNBs.[0].local_s_address 192.168.71.181
+ ASAN_OPTIONS: detect_leaks=0
+ depends_on:
+ - oai-cucp-0
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.181
+ volumes:
+ - ../../conf_files/gnb-cuup.sa.f1.conf:/opt/oai-gnb/etc/gnb.conf
+ healthcheck:
+ test: /bin/bash -c "pgrep nr-cuup"
+ start_period: 10s
+ start_interval: 500ms
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ oai-du-1:
+ image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
+ container_name: rfsim5g-oai-du-1
+ cap_drop:
+ - ALL
+ environment:
+ USE_ADDITIONAL_OPTIONS: --rfsim
+ --rfsimulator.serveraddr 192.168.71.190
+ --log_config.global_log_options level,nocolor,time
+ --gNBs.[0].gNB_DU_ID 1234
+ --gNBs.[0].gNB_ID 0xb00
+ --gNBs.[0].nr_cellid 1
+ --gNBs.[0].servingCellConfigCommon.[0].physCellId 1
+ --gNBs.[0].servingCellConfigCommon.[0].absoluteFrequencySSB 621312
+ --gNBs.[0].servingCellConfigCommon.[0].dl_absoluteFrequencyPointA 620040
+ --MACRLCs.[0].local_n_address 192.168.71.182
+ --MACRLCs.[0].remote_n_address 192.168.71.180
+ ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0
+ depends_on:
+ - oai-cucp-1
+ networks:
+ public_net:
+ ipv4_address: 192.168.71.182
+ volumes:
+ - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf
+ - ../../conf_files/gnb-du.sa.band78.106prb.rfsim.conf:/opt/oai-gnb/etc/gnb.conf
+ healthcheck:
+ test: /bin/bash -c "pgrep nr-softmodem"
+ start_period: 10s
+ start_interval: 500ms
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+networks:
+ public_net:
+ driver: bridge
+ name: rfsim5g-oai-public-net
+ ipam:
+ config:
+ - subnet: 192.168.71.128/26
+ driver_opts:
+ com.docker.network.bridge.name: "rfsim5g-public"
+ traffic_net:
+ driver: bridge
+ name: rfsim5g-oai-traffic-net
+ ipam:
+ config:
+ - subnet: 192.168.72.128/26
+ driver_opts:
+ com.docker.network.bridge.name: "rfsim5g-traffic"
diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c
index 24dba02191..291f1f37a2 100644
--- a/common/utils/telnetsrv/telnetsrv_ci.c
+++ b/common/utils/telnetsrv/telnetsrv_ci.c
@@ -38,6 +38,7 @@
#include "openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h"
#include "openair2/LAYER2/NR_MAC_gNB/mac_proto.h"
+#include "openair2/RRC/NR/rrc_gNB_mobility.h"
#define TELNETSERVERCODE
#include "telnetsrv.h"
@@ -196,6 +197,53 @@ int rrc_gNB_trigger_f1_ho(char *buf, int debug, telnet_printfunc_t prnt)
return 0;
}
+extern void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t rrc_ue_id);
+
+/** @brief Trigger N2 handover for UE
+ * @param buf: Neighbour PCI, SCell PCI, RRC UE ID
+ * @param debug: Debug flag
+ * @param prnt: Print function
+ * @return 0 on success, -1 on failure */
+int rrc_gNB_trigger_n2_ho(char *buf, int debug, telnet_printfunc_t prnt)
+{
+ if (!RC.nrrrc)
+ ERROR_MSG_RET("no RRC present, cannot list counts\n");
+
+ if (!buf) {
+ ERROR_MSG_RET("Please provide neighbour cell id and ue id\n");
+ } else {
+ // Parse neighbour cell PCI
+ char *token = strtok(buf, ",");
+ if (!token) {
+ ERROR_MSG_RET("Invalid input. Expected format: Neighbour PCI, ueId\n");
+ }
+ uint32_t neighbour_pci = strtol(token, NULL, 10);
+
+ // Parse ueId
+ token = strtok(NULL, ",");
+ if (!token) {
+ ERROR_MSG_RET("Missing UE ID\n");
+ }
+ uint32_t ueId = strtol(token, NULL, 10);
+
+ // Retrieve UE context
+ rrc_gNB_ue_context_t *ue_p = rrc_gNB_get_ue_context(RC.nrrrc[0], ueId);
+ if (!ue_p) {
+ ERROR_MSG_RET("UE with id %u not found\n", ueId);
+ }
+ gNB_RRC_UE_t *UE = &ue_p->ue_context;
+
+ // Trigger N2 handover
+ nr_HO_N2_trigger_telnet(RC.nrrrc[0], neighbour_pci, UE->rrc_ue_id);
+
+ // Print success message
+ prnt("RRC N2 handover triggered for UE %u with neighbour cell id %u\n",
+ ueId,
+ neighbour_pci);
+ }
+ return 0;
+}
+
int force_ul_failure(char *buf, int debug, telnet_printfunc_t prnt)
{
if (!RC.nrmac)
@@ -245,6 +293,7 @@ static telnetshell_cmddef_t cicmds[] = {
{"trigger_f1_ho", "[rrc_ue_id(int,opt)]", rrc_gNB_trigger_f1_ho},
{"fetch_du_by_ue_id", "[rrc_ue_id(int,opt)]", fetch_du_by_ue_id},
{"get_current_bwp", "[rnti(hex,opt)]", get_current_bwp},
+ {"trigger_n2_ho", "[neighbour_pci(uint32_t),ueId(uint32_t)]", rrc_gNB_trigger_n2_ho},
{"", "", NULL},
};
diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md
index e6eb3f97e0..43f0c7ed93 100644
--- a/doc/RRC/rrc-dev.md
+++ b/doc/RRC/rrc-dev.md
@@ -210,7 +210,7 @@ sequenceDiagram
end
```
-## Handover
+## Inter-DU Handover (F1)
The basic handover (HO) structure is as follows. In order to support various
handover "message passing implementation" (F1AP, NGAP, XnAP), RRC employs
@@ -264,6 +264,94 @@ sequenceDiagram
sdu->>cucp: F1AP UE Context Release Complete
Note over ue,tdu: UE active on target DU
```
+## Inter-gNB Handover (N2)
+
+This is an inter-NG-RAN procedure. The N2 handover specification is defined in the following documents:
+
+* 3GPP TS 23.502, 4.9.1.3 Inter NG-RAN node N2 based handover:
+ - Outlines detailed handover signaling flows for N2-based handovers.
+ - Covers both intra-system (between 5G gNBs) and inter-system (between 5G and LTE eNBs) handovers.
+* 3GPP TS 38.300, section 9 Mobility and State Transitions:
+ - describes mobility procedures at the NG-RAN level, depending on the RRC state.
+* 3GPP TS 38.413 (NGAP), section 8.4 UE Mobility Management Procedures:
+ - Specifies the signaling procedures over the N2 interface.
+ - Includes messages like Handover Request, Handover Command, and Handover Preparation.
+* 3GPP TS 38.331 (RRC): details the UE-level RRC procedures involved during handovers
+
+### End-to-end flow
+
+```mermaid
+sequenceDiagram
+ participant ue as UE
+ participant sdu as source DU
+ participant scucp as source CU-CP
+ participant scuup as source CU-UP
+ participant tdu as target DU
+ participant tcucp as target CU-CP
+ participant tcuup as target CU-UP
+ participant amf as AMF
+
+ Note over ue,sdu: UE active on source DU
+ alt HO triggered through A3 event
+ ue->>sdu: RRC Measurement Report
+ sdu->>scucp: F1AP UL RRC Msg Transfer (RRC Measurement Report)
+ Note over scucp: Handover decision (A3 event trigger)
+ else Manual Trigger
+ Note over scucp: Handover decision (e.g., telnet)
+ end
+ Note over scucp: nr_rrc_trigger_n2_ho() ("on source CU")
+ scucp->>amf: HANDOVER REQUIRED
+ amf->>tcucp: HANDOVER REQUEST
+ Note over tcucp: rrc_gNB_process_Handover_Request
+ Note over tcucp: trigger_bearer_setup
+ tcucp->>tcuup: Bearer Context Setup Request
+ tcuup->>tcucp: Bearer Context Setup Response
+ Note over tcucp: rrc_gNB_process_e1_bearer_context_setup_resp
+ Note over tcucp: nr_rrc_trigger_n2_ho_target() ("on target CU")
+ Note over tcucp: nr_initiate_handover()
+ tcucp->>tdu: F1AP UE Context Setup Req
+ Note over tdu: Create UE context
+ tdu->>tcucp: F1AP UE Context Setup Resp (incl. CellGroupConfig)
+ Note over tcucp: rrc_CU_process_ue_context_setup_response() ("on target CU")
+ Note over tcucp: e1_send_bearer_updates()
+ tcucp->>tcuup: E1AP Bearer Context Modification Req
+ tcuup->>tcucp: E1AP Bearer Context Modification Resp
+ tcucp-->>tcucp: callback: ho_req_ack()
+ Note over tcucp: nr_rrc_n2_ho_acknowledge() ("on target CU")
+ tcucp->>amf: HANDOVER REQUEST ACKNOWLEDGE (data forwarding info)
+ amf->>scucp: HANDOVER COMMAND
+ scucp->>sdu: F1AP UE Context Modification Req (RRC Reconfiguration)
+ sdu->>ue: RRC Reconfiguration
+ sdu->>scucp: F1AP UE Context Modification Resp
+ Note over sdu: Stop scheduling UE
+ Note over scucp: rrc_CU_process_ue_context_modification_response()
+ Note over scucp: e1_send_bearer_updates()
+ scucp->>scuup: E1 Bearer Context Modification Req (PDCP Status Request)
+ scuup->>scucp: E1 Bearer Context Modification Resp (PDCP Status Info)
+ scucp->>amf: NG Uplink RAN Status Transfer
+ amf->>tcucp: NG Downlink RAN Status Transfer
+ tcucp->>tcuup: E1 Bearer Context Modification Req (PDCP Status Info)
+ tcuup->>tcucp: E1 Bearer Context Modification Resp
+ Note over ue: UE attachment to target DU
+ Note over ue,tdu: RA (Msg1 + Msg2)
+ ue->>tdu: RRC Reconfiguration Complete
+ tdu->>tcucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete)
+ tcucp-->>tcucp: callback: ho_success()
+ Note over tcucp: nr_rrc_n2_ho_complete() ("on target CU")
+ Note over tcucp: handle_rrcReconfigurationComplete() ("on target CU")
+ tcucp->>amf: HANDOVER NOTIFY
+ amf->>scucp: UE Context Release Command
+ note over scucp: ngap_gNB_handle_ue_context_release_command
+ note over scucp: rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_COMMAND
+ scucp->>scuup: E1 Bearer Context Release Command
+ scuup->>scucp: E1 Bearer Context Release Complete
+ Note over scucp: rrc_gNB_generate_RRCRelease
+ scucp->>sdu: F1 UE Context Release Command
+ sdu->>scucp: F1 UE Context Release Complete
+ note over scucp: rrc_CU_process_ue_context_release_complete
+ note over scucp: rrc_remove_ue
+ Note over ue,tdu: UE active on target DU
+```
# Structures
@@ -308,7 +396,8 @@ corresponding CU.
`nr_ho_source_cu_t` contains notably a function pointer `ho_cancel` for
handover cancel. `nr_ho_target_cu_t` contains function pointers `ho_req_ack`
-for handover request acknowledge and `ho_success` for handover success. As can
+for handover request acknowledge, `ho_success` for handover success,
+`ho_failure` for handover failure (N2 only).
be seen in the sequence diagram above, either the "target CU" or "source CU"
needs to do an operation, and a "switch" from target to source CU is done using
these function pointers. For instance, in F1, the handover request acknowledge
diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md
index 09c8952889..0eb7bc7195 100644
--- a/doc/RRC/rrc-usage.md
+++ b/doc/RRC/rrc-usage.md
@@ -99,6 +99,26 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration.
### Neighbor-gNB configuration
- TBD
+#### What is a gNB neighbor?
+
+Network continuity is a key aspect of 5G. In the 5G architecture, gNB neighbors play a central role in maintaining service continuity through mechanisms such as handover and load balancing. By definition, a gNB neighbor is another gNB that can be measured and linked by the UE. If the current serving gNB is no longer optimal, the UE may connect to a neighbor gNB.
+
+To support this behavior, the network configuration specifies additional frequencies and cells that the UE should measure. The UE reports these measurements to the network, which then decides whether or not to initiate a handover.
+
+Neighbor types include:
+- **Intra-gNB neighbors** - cells belonging to the same gNB
+- **Inter-gNB neighbors** - cells belonging to different gNBs
+- **Inter-RAT neighbors** - cells belonging to another RAT (e.g., LTE)
+
+#### Required configuration parameters
+
+To define a neighbor cell in the configuration file, the following parameters are typically needed:
+- **gNB ID** - unique identifier of the gNB
+- **Cell ID** - identifier of the cell within the gNB
+- **Physical Cell ID** - identifier of the cell’s synchronization signal (PCI)
+- **Absolute Frequency** - frequency used by the SSB (absoluteFrequencySSB)
+- **Subcarrier Spacing** - numerology (e.g., 15 kHz, 30 kHz)
+- **PLMN configuration** - MCC, MNC, and MNC length
+- **Tracking Area Code (TAC)** - identifier of the tracking area
Refer to the [handover tutorial](../handover-tutorial.md) for more information.
diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md
index 87a45ed1e5..67bf99b3c2 100644
--- a/doc/handover-tutorial.md
+++ b/doc/handover-tutorial.md
@@ -207,6 +207,7 @@ neighbour_list = (
physical_cellId = 1;
absoluteFrequencySSB = 643296;
subcarrierSpacing = 1; #30 KHz
+ band = 78;
plmn = { mcc = 001; mnc = 01; mnc_length = 2};
tracking_area_code = 1;
}
@@ -221,6 +222,7 @@ neighbour_list = (
physical_cellId = 0;
absoluteFrequencySSB = 643296;
subcarrierSpacing = 1; #30 KHz
+ band = 78;
plmn = { mcc = 001; mnc = 01; mnc_length = 2};
tracking_area_code = 1;
}
@@ -340,3 +342,147 @@ while true; do
sleep 15
done
```
+
+# N2 Handover
+
+## Run the setup
+
+An N2 handover involves the transfer of a UE from one gNB to another via the 5G core network. Unlike F1 handover, where the CU handles the process internally between its DUs, N2 handover requires signaling through the AMF, making it a core-network-based handover.
+
+We assume:
+
+* Two independent gNBs connected to the same 5GC via N2 interface.
+* A UE initially connected to gNB-PCI0, which will be handed over to gNB-PCI1.
+* Handover is triggered by either by decision based measurement event (e.g. A3) or telnet command.
+
+## Steps to run N2 handover with OAI UE
+
+1. Similarly to F1 handover, UE does not support any measurement reporting and handover is triggered by
+telnet command. Therefore, ensure that both gNBs and UE are built with telnet support:
+
+```sh
+./build_oai --ninja --nrUE --gNB --build-lib telnetsrv
+```
+
+Run the 5G Core Network if not already running. See [OAI CN5G tutorial](./NR_SA_Tutorial_OAI_CN5G.md).
+
+2. Start the source gNB (gNB-PCI0) e.g.
+
+```sh
+sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf --telnetsrv --telnetsrv.shrmod ci --gNBs.[0].min_rxtxtime 6 --rfsim --rfsimulator.serveraddr 127.0.0.1
+```
+
+3. Start the UE e.g.
+
+```sh
+sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --rfsim --uicc0.imsi 001010000000001 -O ../../../ci-scripts/conf_files/nrue.uicc.conf --rfsimulator.serveraddr server
+```
+
+Ensure the UE successfully registers with the network.
+
+4. Start the target gNB (gNB PCI1) e.g.
+
+```sh
+sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf --sa --rfsim --telnetsrv --telnetsrv.shrmod ci --gNBs.[0].min_rxtxtime 6 --rfsimulator.serveraddr 127.0.0.1
+```
+
+5. Trigger the N2 handover, e.g.
+
+From gNB-PCI0, trigger handover on target gNB with PCI 1 for UE ID 1:
+
+```sh
+echo ci trigger_n2_ho 1,1 | nc 127.0.0.1 9090 && echo
+```
+where the input parameters correspond to the PCI of the neighbor call and the RRC ID of the UE.
+
+This will initiate the N2 handover on the source gNB.
+
+## Neighbour list and measurement configuration
+
+Make sure the configuration file contains a neighbour list and measurement configuration, e.g. [neighbour-config-rfsim.conf](../../ci-scripts/conf_files/neighbour-config.conf). This configuration can also be present in a different file and included in the gNB configuration file with `@include "neighbour-config-rfsim.conf"`.
+
+For each gNB there is a `neighbour_cell_configuration` linked to its serving cell ID.
+
+The measurement configuration is based on A2 and A3 measurement events in 5G NR. These events are used by the UE to report radio conditions to the gNB. The A2 Measurement Event indicates that the serving cell’s signal quality has degraded below a defined threshold and the UE shall initiate measurement of neighboring cells. The A3 Measurement Event indicates that a neighboring cell’s signal quality is better than that of the serving cell by a certain offset and the UE shall trigger handover to a stronger neighboring cell
+
+This is an example with comments on how to use the configuration file:
+
+```
+############################################################
+# gNB-to-gNB neighbour list + measurement configuration #
+# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) #
+############################################################
+
+neighbour_list = (
+ ##########################################################
+ # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 12345678L) #
+ ##########################################################
+ {
+ nr_cellid = 12345678L; # Serving cell of gNB 0xe00
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xb00;
+ nr_cellid = 720898; # Cell served by gNB 0xb00
+ physical_cellId = 1;
+ absoluteFrequencySSB= 621312;
+ subcarrierSpacing = 1; # 30 kHz
+ band = 78;
+ plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
+ tracking_area_code = 1;
+ }
+ );
+ },
+
+ ##########################################################
+ # Entry USED BY gNB_ID = 0xb00 (nr_cellid = 720898) #
+ ##########################################################
+ {
+ nr_cellid = 720898; # Serving cell of gNB 0xb00
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xe00;
+ nr_cellid = 12345678L; # Cell served by gNB 0xe00
+ physical_cellId = 0;
+ absoluteFrequencySSB= 641280;
+ subcarrierSpacing = 1; # 30 kHz
+ band = 78;
+ plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
+ tracking_area_code = 1;
+ }
+ );
+ }
+);
+
+############################################################
+# Common NR measurement-event configuration #
+############################################################
+
+nr_measurement_configuration = {
+ Periodical = {
+ enable = 1;
+ includeBeamMeasurements = 1;
+ maxNrofRS_IndexesToReport = 4;
+ };
+
+ A2 = {
+ enable = 1;
+ threshold = 60;
+ timeToTrigger = 1;
+ };
+
+ A3 = (
+ {
+ cell_id = 720898; # neighbour of gNB 0xe00
+ offset = 10;
+ hysteresis = 0;
+ timeToTrigger = 1;
+ },
+ {
+ cell_id = 12345678; # neighbour of gNB 0xb00
+ offset = 5;
+ hysteresis = 1;
+ timeToTrigger = 2;
+ }
+ );
+};
+```
diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h
index f4e9090f66..788236ca05 100644
--- a/openair2/COMMON/e1ap_messages_types.h
+++ b/openair2/COMMON/e1ap_messages_types.h
@@ -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 {
diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h
index 4f05a564c4..98c1f82260 100644
--- a/openair2/COMMON/ngap_messages_def.h
+++ b/openair2/COMMON/ngap_messages_def.h
@@ -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)
diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h
index 3ee858ecca..ef25f6997d 100644
--- a/openair2/COMMON/ngap_messages_types.h
+++ b/openair2/COMMON/ngap_messages_types.h
@@ -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_ */
diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c
index 2c4f938a24..0f2e5972e2 100644
--- a/openair2/E1AP/lib/e1ap_bearer_context_management.c
+++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c
@@ -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);
+ }
}
}
diff --git a/openair2/E1AP/tests/e1ap_lib_test.c b/openair2/E1AP/tests/e1ap_lib_test.c
index a9fd7aa390..b168ff9e82 100644
--- a/openair2/E1AP/tests/e1ap_lib_test.c
+++ b/openair2/E1AP/tests/e1ap_lib_test.c
@@ -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 = {
diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c
index dcd17efe22..55110fc449 100644
--- a/openair2/GNB_APP/gnb_config.c
+++ b/openair2/GNB_APP/gnb_config.c
@@ -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));
}
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
index ae5305d77c..7d8367ff6b 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c
@@ -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);
}
}
}
diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
index be5d9d37b9..f04b28b8da 100644
--- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
+++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h
@@ -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,
diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
index af62663de7..50f77b681c 100644
--- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
+++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
@@ -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
diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c
index 99c427cc17..b19ccceb22 100644
--- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c
+++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c
@@ -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);
}
+
}
}
diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c
index 41d41cf922..25081891d3 100644
--- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c
+++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c
@@ -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;
}
diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h
index 7f1297ac3e..84ecce8997 100644
--- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h
+++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h
@@ -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(
diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c
index 4d1c0b5ba0..ce885b6b33 100644
--- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c
+++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c
@@ -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);
+}
diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h
index 8d6a65cad6..375bd27458 100644
--- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h
+++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h
@@ -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 */
diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c
index 21ccb1e40f..51618d1f3e 100644
--- a/openair2/RRC/NR/MESSAGES/asn1_msg.c
+++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c
@@ -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);
diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h
index 4f13ca2fad..607fa95994 100644
--- a/openair2/RRC/NR/MESSAGES/asn1_msg.h
+++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h
@@ -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__ */
diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h
index f1d9acba7b..f670dc4f86 100644
--- a/openair2/RRC/NR/nr_rrc_defs.h
+++ b/openair2/RRC/NR/nr_rrc_defs.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;
diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h
index 0d675ff525..0c545575ba 100644
--- a/openair2/RRC/NR/nr_rrc_proto.h
+++ b/openair2/RRC/NR/nr_rrc_proto.h
@@ -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
diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c
index 0e5912aafe..797aa19d88 100644
--- a/openair2/RRC/NR/rrc_gNB.c
+++ b/openair2/RRC/NR/rrc_gNB.c
@@ -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;
diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c
index 407a0d03b7..be7dce5ca1 100644
--- a/openair2/RRC/NR/rrc_gNB_NGAP.c
+++ b/openair2/RRC/NR/rrc_gNB_NGAP.c
@@ -39,6 +39,9 @@
#include
#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;
+}
diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h
index 22fff8705f..b90d9a1493 100644
--- a/openair2/RRC/NR/rrc_gNB_NGAP.h
+++ b/openair2/RRC/NR/rrc_gNB_NGAP.h
@@ -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
diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c
index fe3214da2d..07b742e16c 100644
--- a/openair2/RRC/NR/rrc_gNB_mobility.c
+++ b/openair2/RRC/NR/rrc_gNB_mobility.c
@@ -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);
+}
diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h
index 6e9bcbcfe6..40072abb2d 100644
--- a/openair2/RRC/NR/rrc_gNB_mobility.h
+++ b/openair2/RRC/NR/rrc_gNB_mobility.h
@@ -23,6 +23,7 @@
#include
#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_ */
diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c
index 7ace2c7a91..c167982859 100644
--- a/openair2/RRC/NR_UE/rrc_UE.c
+++ b/openair2/RRC/NR_UE/rrc_UE.c
@@ -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) {
diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h
index d1435526b3..ae8837bff2 100644
--- a/openair2/RRC/NR_UE/rrc_defs.h
+++ b/openair2/RRC/NR_UE/rrc_defs.h
@@ -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;
diff --git a/openair3/NGAP/ngap_common.c b/openair3/NGAP/ngap_common.c
index 82c2ccaf2a..9e3e2e9d13 100644
--- a/openair3/NGAP/ngap_common.c
+++ b/openair3/NGAP/ngap_common.c
@@ -66,6 +66,43 @@ void encode_ngap_cause(NGAP_Cause_t *out, const ngap_cause_t *in)
}
}
+ngap_cause_t decode_ngap_cause(const NGAP_Cause_t *in)
+{
+ ngap_cause_t out = {0};
+ switch (in->present) {
+ case NGAP_Cause_PR_radioNetwork:
+ out.type = NGAP_CAUSE_RADIO_NETWORK;
+ out.value = in->choice.radioNetwork;
+ break;
+
+ case NGAP_Cause_PR_transport:
+ out.type = NGAP_CAUSE_TRANSPORT;
+ out.value = in->choice.transport;
+ break;
+
+ case NGAP_Cause_PR_nas:
+ out.type = NGAP_CAUSE_NAS;
+ out.value = in->choice.nas;
+ break;
+
+ case NGAP_Cause_PR_protocol:
+ out.type = NGAP_CAUSE_PROTOCOL;
+ out.value = in->choice.protocol;
+ break;
+
+ case NGAP_Cause_PR_misc:
+ out.type = NGAP_CAUSE_MISC;
+ out.value = in->choice.misc;
+ break;
+
+ default:
+ out.type = NGAP_CAUSE_NOTHING;
+ NGAP_ERROR("Unknown failure cause %d\n", in->present);
+ break;
+ }
+ return out;
+}
+
nr_guami_t decode_ngap_guami(const NGAP_GUAMI_t *in)
{
nr_guami_t out = {0};
@@ -112,3 +149,125 @@ ngap_mobility_restriction_t decode_ngap_mobility_restriction(const NGAP_Mobility
TBCD_TO_MCC_MNC(&in->servingPLMN, out.serving_plmn.mcc, out.serving_plmn.mnc, out.serving_plmn.mnc_digit_length);
return out;
}
+
+void encode_ngap_target_id(NGAP_HandoverRequiredIEs_t *out, const target_ran_node_id_t *in)
+{
+ out->id = NGAP_ProtocolIE_ID_id_TargetID;
+ out->criticality = NGAP_Criticality_reject;
+ out->value.present = NGAP_HandoverRequiredIEs__value_PR_TargetID;
+ // CHOICE Target ID: NG-RAN (M)
+ out->value.choice.TargetID.present = NGAP_TargetID_PR_targetRANNodeID;
+ asn1cCalloc(out->value.choice.TargetID.choice.targetRANNodeID, targetRan);
+ // Global RAN Node ID (M)
+ targetRan->globalRANNodeID.present = NGAP_GlobalRANNodeID_PR_globalGNB_ID;
+ asn1cCalloc(targetRan->globalRANNodeID.choice.globalGNB_ID, globalGnbId);
+ globalGnbId->gNB_ID.present = NGAP_GNB_ID_PR_gNB_ID;
+ MACRO_GNB_ID_TO_BIT_STRING(in->targetgNBId, &globalGnbId->gNB_ID.choice.gNB_ID);
+ MCC_MNC_TO_PLMNID(in->plmn_identity.mcc, in->plmn_identity.mnc, in->plmn_identity.mnc_digit_length, &globalGnbId->pLMNIdentity);
+ // Selected TAI (M)
+ INT24_TO_OCTET_STRING(in->tac, &targetRan->selectedTAI.tAC);
+ MCC_MNC_TO_PLMNID(in->plmn_identity.mcc,
+ in->plmn_identity.mnc,
+ in->plmn_identity.mnc_digit_length,
+ &targetRan->selectedTAI.pLMNIdentity);
+}
+
+void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_t cell_id)
+{
+ out->iE_Extensions = NULL;
+ MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &out->pLMNIdentity);
+ NR_CELL_ID_TO_BIT_STRING(cell_id, &out->nRCellIdentity);
+}
+
+pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params)
+{
+ pdusession_level_qos_parameter_t out = {0};
+ // QFI
+ out.qfi = qfi;
+ AssertFatal(params != NULL, "QoS parameters are NULL\n");
+ // QosCharacteristics
+ const NGAP_QosCharacteristics_t *qosChar = ¶ms->qosCharacteristics;
+ AssertFatal(qosChar != NULL, "QoS characteristics are NULL\n");
+ if (qosChar->present == NGAP_QosCharacteristics_PR_nonDynamic5QI) {
+ AssertFatal(qosChar->choice.nonDynamic5QI != NULL, "nonDynamic5QI is NULL\n");
+ out.fiveQI_type = NON_DYNAMIC;
+ out.fiveQI = qosChar->choice.nonDynamic5QI->fiveQI;
+ } else if (qosChar->present == NGAP_QosCharacteristics_PR_dynamic5QI) {
+ AssertFatal(qosChar->choice.dynamic5QI != NULL, "dynamic5QI is NULL\n");
+ out.fiveQI_type = DYNAMIC;
+ out.fiveQI = *qosChar->choice.dynamic5QI->fiveQI;
+ } else {
+ AssertFatal(0, "Unsupported QoS Characteristics present value: %d\n", qosChar->present);
+ }
+ // Allocation and Retention Priority
+ const NGAP_AllocationAndRetentionPriority_t *arp = ¶ms->allocationAndRetentionPriority;
+ out.allocation_retention_priority.priority_level = arp->priorityLevelARP;
+ out.allocation_retention_priority.pre_emp_capability = arp->pre_emptionCapability;
+ out.allocation_retention_priority.pre_emp_vulnerability = arp->pre_emptionVulnerability;
+
+ return out;
+}
+
+static gtpu_tunnel_t decode_TNLInformation(const NGAP_GTPTunnel_t *gTPTunnel_p)
+{
+ gtpu_tunnel_t out = {0};
+ // Transport layer address
+ memcpy(out.addr.buffer, gTPTunnel_p->transportLayerAddress.buf, gTPTunnel_p->transportLayerAddress.size);
+ out.addr.length = gTPTunnel_p->transportLayerAddress.size - gTPTunnel_p->transportLayerAddress.bits_unused;
+ // GTP tunnel endpoint ID
+ OCTET_STRING_TO_INT32(&gTPTunnel_p->gTP_TEID, out.teid);
+ return out;
+}
+
+void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_STRING_t buf)
+{
+ void *decoded = NULL;
+ asn_codec_ctx_t ctx = {.max_stack_size = 100 * 1000};
+ asn_dec_rval_t rval = aper_decode(&ctx, td, &decoded, buf.buf, buf.size, 0, 0);
+ if (rval.code != RC_OK) {
+ NGAP_ERROR("Decoding failed for %s\n", td->name);
+ return NULL;
+ }
+ return decoded;
+}
+
+/** @brief Decode PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */
+bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in)
+{
+ void *decoded = decode_pdusession_transfer(&asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, in);
+ if (!decoded) {
+ LOG_E(NR_RRC, "Failed to decode PDUSessionResourceSetupRequestTransfer\n");
+ return false;
+ }
+
+ NGAP_PDUSessionResourceSetupRequestTransfer_t *pdusessionTransfer = (NGAP_PDUSessionResourceSetupRequestTransfer_t *)decoded;
+ for (int i = 0; i < pdusessionTransfer->protocolIEs.list.count; i++) {
+ NGAP_PDUSessionResourceSetupRequestTransferIEs_t *pdusessionTransfer_ies = pdusessionTransfer->protocolIEs.list.array[i];
+ switch (pdusessionTransfer_ies->id) {
+ // UL NG-U UP TNL Information (Mandatory)
+ case NGAP_ProtocolIE_ID_id_UL_NGU_UP_TNLInformation:
+ out->n3_incoming = decode_TNLInformation(pdusessionTransfer_ies->value.choice.UPTransportLayerInformation.choice.gTPTunnel);
+ break;
+
+ // PDU Session Type (Mandatory)
+ case NGAP_ProtocolIE_ID_id_PDUSessionType:
+ out->pdu_session_type = pdusessionTransfer_ies->value.choice.PDUSessionType;
+ break;
+
+ // QoS Flow Setup Request List (Mandatory)
+ case NGAP_ProtocolIE_ID_id_QosFlowSetupRequestList:
+ out->nb_qos = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.count;
+ for (int i = 0; i < out->nb_qos; i++) {
+ NGAP_QosFlowSetupRequestItem_t *item = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.array[i];
+ out->qos[i] = fill_qos(item->qosFlowIdentifier, &item->qosFlowLevelQosParameters);
+ }
+ break;
+
+ default:
+ LOG_D(NR_RRC, "Unhandled optional IE %ld\n", pdusessionTransfer_ies->id);
+ }
+ }
+ ASN_STRUCT_FREE(asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, pdusessionTransfer);
+
+ return true;
+}
diff --git a/openair3/NGAP/ngap_common.h b/openair3/NGAP/ngap_common.h
index dd7fdebc54..9afaf47c4b 100644
--- a/openair3/NGAP/ngap_common.h
+++ b/openair3/NGAP/ngap_common.h
@@ -83,10 +83,16 @@ typedef int (*ngap_message_decoded_callback)(sctp_assoc_t assoc_id, uint32_t str
void encode_ngap_cause(NGAP_Cause_t *out, const ngap_cause_t *in);
nr_guami_t decode_ngap_guami(const NGAP_GUAMI_t *in);
+ngap_cause_t decode_ngap_cause(const NGAP_Cause_t *in);
ngap_ambr_t decode_ngap_UEAggregateMaximumBitRate(const NGAP_UEAggregateMaximumBitRate_t *in);
nssai_t decode_ngap_nssai(const NGAP_S_NSSAI_t *in);
ngap_security_capabilities_t decode_ngap_security_capabilities(const NGAP_UESecurityCapabilities_t *in);
ngap_mobility_restriction_t decode_ngap_mobility_restriction(const NGAP_MobilityRestrictionList_t *in);
+void encode_ngap_target_id(NGAP_HandoverRequiredIEs_t *out, const target_ran_node_id_t *in);
+void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_t cell_id);
+pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params);
+void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_STRING_t buf);
+bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in);
/** @}*/
diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c
index 8edb5c3aa3..8c9ac6a973 100644
--- a/openair3/NGAP/ngap_gNB.c
+++ b/openair3/NGAP/ngap_gNB.c
@@ -55,6 +55,8 @@
#include "ngap_gNB_management_procedures.h"
#include "ngap_gNB_nas_procedures.h"
#include "ngap_messages_types.h"
+#include "ngap_gNB_mobility_management.h"
+#include "ngap_gNB_ue_context.h"
#include "oai_asn1.h"
#include "openair3/SECU/kdf.h"
#include "queue.h"
@@ -275,12 +277,273 @@ void ngap_gNB_handle_sctp_data_ind(sctp_data_ind_t *sctp_data_ind) {
AssertFatal (result == EXIT_SUCCESS, "Failed to free memory (%d)!\n", result);
}
+ /** @brief UE Mobility Management: callback for Handover Required */
+int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg)
+{
+ DevAssert(msg != NULL);
+ NGAP_DEBUG("Triggered Handover Required\n");
+ /* Retrieve the NGAP gNB instance associated with Mod_id */
+ ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance);
+ DevAssert(ngap_gNB_instance_p != NULL);
+
+ ngap_gNB_ue_context_t *ue_context_p = NULL;
+ if ((ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id)) == NULL) {
+ /* The context for this gNB ue ngap id doesn't exist in the map of gNB UEs */
+ NGAP_WARN("Failed to find ue context associated with gNB ue ngap id: 0x%08x\n", msg->gNB_ue_ngap_id);
+ return -1;
+ }
+
+ if ((ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id)) {
+ NGAP_ERROR("ue_context_p->gNB_ue_ngap_id %d does not match msg->gNB_ue_ngap_id %d\n",
+ ue_context_p->gNB_ue_ngap_id,
+ msg->gNB_ue_ngap_id);
+ return -1;
+ }
+
+ NGAP_NGAP_PDU_t *pdu = encode_ng_handover_required(msg);
+ if (!pdu) {
+ NGAP_ERROR("Failed to encode Handover Required\n");
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, &pdu);
+
+ byte_array_t out = { .buf = NULL, .len = 0 };
+ if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) {
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ NGAP_ERROR("Failed to encode Handover Required message\n");
+ return -1;
+ }
+ free(pdu);
+
+ /* UE associated signalling -> use the allocated stream */
+ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance,
+ ue_context_p->amf_ref->assoc_id,
+ out.buf,
+ out.len,
+ ue_context_p->tx_stream);
+
+ return 0;
+}
+
void ngap_gNB_init(void) {
NGAP_DEBUG("Starting NGAP layer\n");
ngap_gNB_prepare_internal_data();
itti_mark_task_ready(TASK_NGAP);
}
+static int ngap_gNB_handover_failure(instance_t instance, const ngap_handover_failure_t *msg)
+{
+ ngap_gNB_ue_context_t *ue_context_p = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t length;
+
+ ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance);
+ DevAssert(msg != NULL);
+ DevAssert(ngap_gNB_instance_p != NULL);
+
+ NGAP_NGAP_PDU_t *pdu = encode_ng_handover_failure(msg);
+ if (!pdu) {
+ NGAP_ERROR("Failed to encode NG Handover Failure\n");
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+
+ if (ngap_gNB_encode_pdu(pdu, &buffer, &length) < 0) {
+ NGAP_ERROR("Failed to encode HANDOVER FAILURE MESSAGE\n");
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+
+ ngap_gNB_amf_data_t *amf = ngap_gNB_get_AMF_from_instance(ngap_gNB_instance_p);
+ if ((ue_context_p = ngap_get_ue_context_from_amf_ue_ngap_id(msg->amf_ue_ngap_id)) == NULL) {
+ //The context for this gNB ue ngap id doesn't exist in the map of gNB UEs
+ NGAP_WARN("Failed to find UE context associated with amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id);
+ /*In this case there is no ue context since HO REQ might be failed at preprocessing stage itself.
+ * so sctp message send to AMF based on its assoc_id */
+ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, amf->assoc_id, buffer, length, 0);
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+
+ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, amf->assoc_id, buffer, length, amf->nextstream);
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, &pdu);
+
+ return 0;
+}
+
+static int ngap_gNB_handover_request_acknowledge(instance_t instance, ngap_handover_request_ack_t *msg)
+{
+ byte_array_t ba = { .buf = NULL, .len = 0 };
+
+ /* Retrieve the NGAP gNB instance associated with Mod_id */
+ ngap_gNB_instance_t *ngap = ngap_gNB_get_instance(instance);
+ DevAssert(msg != NULL);
+ DevAssert(ngap != NULL);
+
+ // First, create and store NGAP UE context
+ ngap_gNB_ue_context_t ue_context_p = {
+ .amf_ref = ngap_gNB_get_AMF_from_instance(ngap),
+ .gNB_ue_ngap_id = msg->gNB_ue_ngap_id,
+ .amf_ue_ngap_id = msg->amf_ue_ngap_id,
+ .gNB_instance = ngap,
+ .ue_state = NGAP_UE_CONNECTED,
+ };
+ if (ue_context_p.amf_ref == NULL) {
+ NGAP_ERROR("Failed to fetch AMF for current NGAP instance\n");
+ return -1;
+ }
+ ngap_store_ue_context(&ue_context_p);
+
+ NGAP_NGAP_PDU_t *pdu = encode_ng_handover_request_ack(msg);
+ if (!pdu) {
+ NGAP_ERROR("Failed to encode NG Handover Request Acknowledge\n");
+ return -1;
+ }
+
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, pdu);
+
+ if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) {
+ NGAP_ERROR("Failed to encode Handover Request Acknowledge message\n");
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+
+ /* UE associated signalling -> use the allocated stream */
+ ngap_gNB_itti_send_sctp_data_req(ngap->instance, ue_context_p.amf_ref->assoc_id, ba.buf, ba.len, ue_context_p.tx_stream);
+ NGAP_INFO("Sent Handover Request Acknowledge to AMF\n");
+
+ return 0;
+}
+
+static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_notify_t *msg)
+{
+ LOG_D(NGAP, "Encode NGAP Handover Notify\n");
+ ngap_gNB_ue_context_t *ue_context_p = NULL;
+ byte_array_t ba = { .buf = NULL, .len = 0};
+
+ /* Retrieve the NGAP gNB instance associated with Mod_id */
+ ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance);
+ DevAssert(msg != NULL);
+ DevAssert(ngap_gNB_instance_p != NULL);
+
+ if ((ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id)) == NULL) {
+ NGAP_ERROR("Failed to encode Handover Notify: no ue context associated with gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id);
+ return -1;
+ }
+
+ if (ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id) {
+ NGAP_ERROR("Failed to encode Handover Notify: unknown gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id);
+ return -1;
+ }
+
+ if (ue_context_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) {
+ NGAP_ERROR("Failed to encode Handover Notify: unknown amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id);
+ return -1;
+ }
+
+ NGAP_NGAP_PDU_t *pdu = encode_ng_handover_notify(msg);
+
+ if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) {
+ NGAP_ERROR("Failed to encode NG Handover Notify message\n");
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+
+ /* UE associated signalling -> use the allocated stream */
+ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance,
+ ue_context_p->amf_ref->assoc_id,
+ ba.buf,
+ ba.len,
+ ue_context_p->tx_stream);
+
+ return 0;
+}
+
+/** @brief NGAP: Handover Cancel callback */
+static int ngap_gNB_handover_cancel(instance_t instance, const ngap_handover_cancel_t *msg)
+{
+ DevAssert(msg != NULL);
+ byte_array_t ba = {.buf = NULL, .len = 0};
+
+ // Retrieve NGAP gNB instance
+ ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance);
+ DevAssert(ngap_gNB_instance_p != NULL);
+
+ // we must have the UE context by RAN UE NGAP ID
+ ngap_gNB_ue_context_t *ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id);
+ if (ue_context_p == NULL) {
+ NGAP_ERROR("Failed to encode Handover Cancel: no UE context for gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id);
+ return -1;
+ }
+
+ if (ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id) {
+ NGAP_ERROR("Failed to encode Handover Cancel: mismatched gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id);
+ return -1;
+ }
+
+ if (ue_context_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) {
+ NGAP_ERROR("Failed to encode Handover Cancel: mismatched amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id);
+ return -1;
+ }
+
+ /* Encode NGAP PDU */
+ NGAP_NGAP_PDU_t *pdu = encode_ng_handover_cancel(msg);
+ if (pdu == NULL) {
+ NGAP_ERROR("Failed to encode NG Handover Cancel\n");
+ return -1;
+ }
+
+ if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) {
+ NGAP_ERROR("Failed to encode NG Handover Cancel message PDU\n");
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+
+ /* UE-associated signalling -> use the UE's allocated SCTP stream */
+ ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance,
+ ue_context_p->amf_ref->assoc_id,
+ ba.buf,
+ ba.len,
+ ue_context_p->tx_stream);
+
+ return 0;
+}
+
+int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_status_transfer_t *msg)
+{
+ NGAP_DEBUG("Triggered UL RAN Status Transfer\n");
+
+ ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance);
+ DevAssert(ngap_gNB_instance_p != NULL);
+
+ ngap_gNB_ue_context_t *ue_context_p = ngap_get_ue_context(msg->gnb_ue_ngap_id);
+ if (!ue_context_p || ue_context_p->gNB_ue_ngap_id != msg->gnb_ue_ngap_id) {
+ NGAP_ERROR("Could not find UE context for gNB_ue_ngap_id %d\n", msg->gnb_ue_ngap_id);
+ return -1;
+ }
+
+ NGAP_NGAP_PDU_t *pdu = encode_ng_ul_ran_status_transfer(msg);
+ if (!pdu) {
+ NGAP_ERROR("Failed to encode UL RAN Status Transfer\n");
+ return -1;
+ }
+
+ byte_array_t out = {.buf = NULL, .len = 0};
+ if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) {
+ NGAP_ERROR("UL RAN Status Transfer encoding failure\n");
+ ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
+ return -1;
+ }
+
+ ngap_gNB_itti_send_sctp_data_req(instance, ue_context_p->amf_ref->assoc_id, out.buf, out.len, ue_context_p->tx_stream);
+ return 0;
+}
+
void *ngap_gNB_process_itti_msg(void *notUsed) {
MessageDef *received_msg = NULL;
int result;
@@ -356,6 +619,33 @@ void *ngap_gNB_process_itti_msg(void *notUsed) {
ngap_gNB_pdusession_release_resp(instance, &NGAP_PDUSESSION_RELEASE_RESPONSE(received_msg));
break;
+ case NGAP_HANDOVER_REQUIRED:
+ if (ngap_handover_required(instance, &NGAP_HANDOVER_REQUIRED(received_msg)) < 0) {
+ NGAP_ERROR("Handover Required failure: indication to RRC is not sent!\n");
+ }
+ break;
+
+ case NGAP_HANDOVER_FAILURE:
+ ngap_gNB_handover_failure(instance, &NGAP_HANDOVER_FAILURE(received_msg));
+ break;
+
+ case NGAP_HANDOVER_REQUEST_ACKNOWLEDGE:
+ ngap_gNB_handover_request_acknowledge(instance, &NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(received_msg));
+ free_ng_handover_req_ack(&NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(received_msg));
+ break;
+
+ case NGAP_HANDOVER_NOTIFY:
+ ngap_gNB_handover_notify(instance, &NGAP_HANDOVER_NOTIFY(received_msg));
+ break;
+
+ case NGAP_HANDOVER_CANCEL:
+ ngap_gNB_handover_cancel(instance, &NGAP_HANDOVER_CANCEL(received_msg));
+ break;
+
+ case NGAP_UL_RAN_STATUS_TRANSFER:
+ ngap_gNB_handle_ul_ran_status_transfer(instance, &NGAP_UL_RAN_STATUS_TRANSFER(received_msg));
+ break;
+
default:
NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg));
break;
diff --git a/openair3/NGAP/ngap_gNB_decoder.c b/openair3/NGAP/ngap_gNB_decoder.c
index 4b76abecbb..481cccf876 100644
--- a/openair3/NGAP/ngap_gNB_decoder.c
+++ b/openair3/NGAP/ngap_gNB_decoder.c
@@ -70,6 +70,12 @@ static int ngap_gNB_decode_initiating_message(NGAP_NGAP_PDU_t *pdu) {
NGAP_INFO("PDUSESSIONSetup initiating message\n");
break;
+ case NGAP_ProcedureCode_id_HandoverPreparation:
+ res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu);
+ free(res.buffer);
+ NGAP_INFO("Handover Preparation initiating message\n");
+ break;
+
case NGAP_ProcedureCode_id_PDUSessionResourceModify:
res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu);
free(res.buffer);
@@ -88,6 +94,18 @@ static int ngap_gNB_decode_initiating_message(NGAP_NGAP_PDU_t *pdu) {
NGAP_INFO("TODO ErrorIndication initiating message\n");
break;
+ case NGAP_ProcedureCode_id_HandoverResourceAllocation:
+ res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu);
+ free(res.buffer);
+ NGAP_INFO("Handover Resource Allocation initiating message\n");
+ break;
+
+ case NGAP_ProcedureCode_id_DownlinkRANStatusTransfer:
+ res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu);
+ free(res.buffer);
+ NGAP_INFO("DL RAN Status Transfer initiating message\n");
+ break;
+
default:
NGAP_ERROR("Unknown procedure ID (%d) for initiating message\n",
(int)pdu->choice.initiatingMessage->procedureCode);
@@ -119,6 +137,10 @@ static int ngap_gNB_decode_successful_outcome(NGAP_NGAP_PDU_t *pdu) {
free(res.buffer);
break;
+ case NGAP_ProcedureCode_id_HandoverPreparation:
+ res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu);
+ free(res.buffer);
+ break;
default:
NGAP_ERROR("Unknown procedure ID (%d) for successfull outcome message\n",
diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c
index c3c095541d..7a7ce758e1 100644
--- a/openair3/NGAP/ngap_gNB_encoder.c
+++ b/openair3/NGAP/ngap_gNB_encoder.c
@@ -52,7 +52,10 @@ static inline int ngap_gNB_encode_initiating(NGAP_NGAP_PDU_t *pdu, uint8_t **buf
NGAP_ProcedureCode_id_NASNonDeliveryIndication,
NGAP_ProcedureCode_id_UEContextReleaseRequest,
NGAP_ProcedureCode_id_PathSwitchRequest,
- NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication};
+ NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication,
+ NGAP_ProcedureCode_id_UplinkRANStatusTransfer,
+ NGAP_ProcedureCode_id_HandoverPreparation,
+ NGAP_ProcedureCode_id_HandoverNotification};
int i;
for (i = 0; i < sizeofArray(tmp); i++)
if (pdu->choice.initiatingMessage->procedureCode == tmp[i])
@@ -76,7 +79,8 @@ static inline int ngap_gNB_encode_successfull_outcome(NGAP_NGAP_PDU_t *pdu, uint
NGAP_ProcedureCode_id_UEContextRelease,
NGAP_ProcedureCode_id_PDUSessionResourceSetup,
NGAP_ProcedureCode_id_PDUSessionResourceModify,
- NGAP_ProcedureCode_id_PDUSessionResourceRelease};
+ NGAP_ProcedureCode_id_PDUSessionResourceRelease,
+ NGAP_ProcedureCode_id_HandoverResourceAllocation};
int i;
for (i = 0; i < sizeofArray(tmp); i++)
if (pdu->choice.successfulOutcome->procedureCode == tmp[i])
@@ -97,8 +101,17 @@ static inline int ngap_gNB_encode_unsuccessfull_outcome(NGAP_NGAP_PDU_t *pdu, ui
{
DevAssert(pdu != NULL);
- if (pdu->choice.unsuccessfulOutcome->procedureCode != NGAP_ProcedureCode_id_InitialContextSetup) {
- NGAP_DEBUG("Unknown procedure ID (%d) for unsuccessfull outcome message\n", (int)pdu->choice.unsuccessfulOutcome->procedureCode);
+ const NGAP_ProcedureCode_t id[] = {NGAP_ProcedureCode_id_InitialContextSetup, NGAP_ProcedureCode_id_HandoverResourceAllocation};
+
+ int i;
+ for (i = 0; i < sizeofArray(id); i++) {
+ if (pdu->choice.unsuccessfulOutcome->procedureCode == id[i]) {
+ break;
+ }
+ }
+
+ if (i == sizeofArray(id)) {
+ NGAP_WARN("Unknown procedure ID (%ld) for successful outcome message\n", pdu->choice.successfulOutcome->procedureCode);
return -1;
}
diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c
index 6a92be9b79..95c2dd45a7 100644
--- a/openair3/NGAP/ngap_gNB_handlers.c
+++ b/openair3/NGAP/ngap_gNB_handlers.c
@@ -44,7 +44,9 @@
#include "ngap_common.h"
#include "ngap_gNB_decoder.h"
#include "ngap_gNB_defs.h"
+#include "ngap_gNB_nnsf.h"
#include "ngap_gNB_management_procedures.h"
+#include "ngap_gNB_mobility_management.h"
#include "ngap_gNB_nas_procedures.h"
#include "ngap_gNB_trace.h"
#include "ngap_gNB_ue_context.h"
@@ -693,97 +695,40 @@ static int ngap_gNB_handle_error_indication(sctp_assoc_t assoc_id, uint32_t stre
return 0;
}
-static void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_STRING_t buf)
+/** @brief Handler for NGAP Handover Request message (AMF -> target NG-RAN)
+ * Decode the NGAP message and transfer to RRC */
+static int ngap_gNB_handle_handover_request(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
{
- void *decoded = NULL;
- asn_codec_ctx_t ctx = {.max_stack_size = 100 * 1000};
- asn_dec_rval_t rval = aper_decode(&ctx, td, &decoded, buf.buf, buf.size, 0, 0);
- if (rval.code != RC_OK) {
- NGAP_ERROR("Decoding failed for %s\n", td->name);
- return NULL;
- }
- return decoded;
-}
+ DevAssert(pdu != NULL);
-static pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params)
-{
- pdusession_level_qos_parameter_t out = {0};
- // QFI
- out.qfi = qfi;
- AssertFatal(params != NULL, "QoS parameters are NULL\n");
- // QosCharacteristics
- const NGAP_QosCharacteristics_t *qosChar = ¶ms->qosCharacteristics;
- AssertFatal(qosChar != NULL, "QoS characteristics are NULL\n");
- if (qosChar->present == NGAP_QosCharacteristics_PR_nonDynamic5QI) {
- AssertFatal(qosChar->choice.nonDynamic5QI != NULL, "nonDynamic5QI is NULL\n");
- out.fiveQI_type = NON_DYNAMIC;
- out.fiveQI = qosChar->choice.nonDynamic5QI->fiveQI;
- } else if (qosChar->present == NGAP_QosCharacteristics_PR_dynamic5QI) {
- AssertFatal(qosChar->choice.dynamic5QI != NULL, "dynamic5QI is NULL\n");
- out.fiveQI_type = DYNAMIC;
- out.fiveQI = *qosChar->choice.dynamic5QI->fiveQI;
- } else {
- AssertFatal(0, "Unsupported QoS Characteristics present value: %d\n", qosChar->present);
- }
- // Allocation and Retention Priority
- const NGAP_AllocationAndRetentionPriority_t *arp = ¶ms->allocationAndRetentionPriority;
- out.allocation_retention_priority.priority_level = arp->priorityLevelARP;
- out.allocation_retention_priority.pre_emp_capability = arp->pre_emptionCapability;
- out.allocation_retention_priority.pre_emp_vulnerability = arp->pre_emptionVulnerability;
-
- return out;
-}
-
-static gtpu_tunnel_t decode_TNLInformation(const NGAP_GTPTunnel_t *gTPTunnel_p)
-{
- gtpu_tunnel_t out = {0};
- // Transport layer address
- memcpy(out.addr.buffer, gTPTunnel_p->transportLayerAddress.buf, gTPTunnel_p->transportLayerAddress.size);
- out.addr.length = gTPTunnel_p->transportLayerAddress.size - gTPTunnel_p->transportLayerAddress.bits_unused;
- // GTP tunnel endpoint ID
- OCTET_STRING_TO_INT32(&gTPTunnel_p->gTP_TEID, out.teid);
- return out;
-}
-
-/** @brief Decode PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */
-static bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in)
-{
- void *decoded = decode_pdusession_transfer(&asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, in);
- if (!decoded) {
- LOG_E(NR_RRC, "Failed to decode PDUSessionResourceSetupRequestTransfer\n");
- return false;
+ ngap_gNB_amf_data_t *amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0);
+ if (amf_desc_p == NULL) {
+ NGAP_ERROR("Received Handover Request for non existing AMF context\n");
+ return -1;
}
- NGAP_PDUSessionResourceSetupRequestTransfer_t *pdusessionTransfer = (NGAP_PDUSessionResourceSetupRequestTransfer_t *)decoded;
- for (int i = 0; i < pdusessionTransfer->protocolIEs.list.count; i++) {
- NGAP_PDUSessionResourceSetupRequestTransferIEs_t *pdusessionTransfer_ies = pdusessionTransfer->protocolIEs.list.array[i];
- switch (pdusessionTransfer_ies->id) {
- // UL NG-U UP TNL Information (Mandatory)
- case NGAP_ProtocolIE_ID_id_UL_NGU_UP_TNLInformation:
- out->n3_incoming = decode_TNLInformation(pdusessionTransfer_ies->value.choice.UPTransportLayerInformation.choice.gTPTunnel);
- break;
+ MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_REQUEST);
+ ngap_handover_request_t *msg = &NGAP_HANDOVER_REQUEST(message_p);
+ memset(msg, 0, sizeof(*msg));
- // PDU Session Type (Mandatory)
- case NGAP_ProtocolIE_ID_id_PDUSessionType:
- out->pdu_session_type = pdusessionTransfer_ies->value.choice.PDUSessionType;
- break;
-
- // QoS Flow Setup Request List (Mandatory)
- case NGAP_ProtocolIE_ID_id_QosFlowSetupRequestList:
- out->nb_qos = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.count;
- for (int i = 0; i < out->nb_qos; i++) {
- NGAP_QosFlowSetupRequestItem_t *item = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.array[i];
- out->qos[i] = fill_qos(item->qosFlowIdentifier, &item->qosFlowLevelQosParameters);
- }
- break;
-
- default:
- LOG_D(NR_RRC, "Unhandled optional IE %ld\n", pdusessionTransfer_ies->id);
- }
+ if (decode_ng_handover_request(msg, pdu) < 0) {
+ NGAP_ERROR("Failed to decode NG Handover Request\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,
+ };
+ NGAP_INFO("Send NG Handover Failure message (amf_ue_ngap_id %ld) with cause %d \n ", fail.amf_ue_ngap_id, fail.cause.value);
+ MessageDef *msg_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_FAILURE);
+ NGAP_HANDOVER_FAILURE(msg_p) = fail;
+ itti_send_msg_to_task(TASK_NGAP, amf_desc_p->ngap_gNB_instance->instance, msg_p);
+ return -1;
}
- ASN_STRUCT_FREE(asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, pdusessionTransfer);
- return true;
+ NGAP_INFO("Received NG Handover Request from AMF %s (ID=%lu)\n", amf_desc_p->amf_name, msg->amf_ue_ngap_id);
+ itti_send_msg_to_task(TASK_RRC_GNB, amf_desc_p->ngap_gNB_instance->instance, message_p);
+
+ return 0;
}
static int ngap_gNB_handle_initial_context_request(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
@@ -1052,6 +997,100 @@ static int ngap_gNB_handle_pdusession_setup_request(sctp_assoc_t assoc_id, uint3
return 0;
}
+/** @brief Handler for the NG Handover Command
+ * AMF -> source NG-RAN */
+static int ngap_gNB_handle_handover_command(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
+{
+ NGAP_INFO("Received NG Handover Command\n");
+ ngap_gNB_amf_data_t *amf_desc_p = NULL;
+ DevAssert(pdu != NULL);
+
+ if ((amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0)) == NULL) {
+ NGAP_ERROR(
+ "[SCTP %u] Received Handover Command for non "
+ "existing AMF context\n",
+ assoc_id);
+ return -1;
+ }
+
+ MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_COMMAND);
+ ngap_handover_command_t *msg = &NGAP_HANDOVER_COMMAND(message_p);
+ memset(msg, 0, sizeof(*msg));
+ if (decode_ng_handover_command(msg, pdu) < 0) {
+ NGAP_ERROR("Failed to decode NG Handover Command");
+ free_ng_handover_command(msg);
+ itti_free(TASK_NGAP, message_p);
+ return -1;
+ }
+
+ ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(msg->gNB_ue_ngap_id);
+ if (!ue_desc_p) {
+ NGAP_ERROR(
+ "[SCTP %u] Received HO Command Msg for non existing UE context (gNB_ue_ngap_id %d) \n",
+ assoc_id,
+ msg->gNB_ue_ngap_id);
+ free_ng_handover_command(msg);
+ itti_free(TASK_NGAP, message_p);
+ return -1;
+ }
+
+ ue_desc_p->rx_stream = stream;
+
+ if (ue_desc_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) {
+ NGAP_ERROR("UE context amf_ue_ngap_id is different form that of the message (%ld != %ld)",
+ ue_desc_p->amf_ue_ngap_id,
+ msg->amf_ue_ngap_id);
+ free_ng_handover_command(msg);
+ itti_free(TASK_NGAP, message_p);
+ return -1;
+ }
+
+ NGAP_DEBUG("Handover Command is parsed with a size of: %ld. Sending to RRC GNB!\n", msg->handoverCommand.len);
+ itti_send_msg_to_task(TASK_RRC_GNB, amf_desc_p->ngap_gNB_instance->instance, message_p);
+
+ return 0;
+}
+
+/** @brief Handler for NG Handover Cancel Acknowledge */
+static int ngap_gNB_handle_handover_cancel_ack(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
+{
+ NGAP_INFO("Received NG Handover Cancel Acknowledge\n");
+ ngap_gNB_amf_data_t *amf_desc_p = NULL;
+ DevAssert(pdu != NULL);
+
+ amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0);
+ if (amf_desc_p == NULL) {
+ NGAP_ERROR("[SCTP %u] Received Handover Cancel Ack for non-existing AMF context\n", assoc_id);
+ return -1;
+ }
+
+ MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_CANCEL_ACK);
+ ngap_handover_cancel_ack_t *msg = &NGAP_HANDOVER_CANCEL_ACK(message_p);
+
+ if (decode_ng_handover_cancel_ack(msg, pdu) < 0) {
+ NGAP_ERROR("Failed to decode NG Handover Cancel Acknowledge\n");
+ itti_free(TASK_NGAP, message_p);
+ return -1;
+ }
+
+ ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(msg->gNB_ue_ngap_id);
+ if (!ue_desc_p) {
+ NGAP_ERROR("[SCTP %u] Handover Cancel Ack for unknown UE context (gNB_ue_ngap_id %d)\n", assoc_id, msg->gNB_ue_ngap_id);
+ itti_free(TASK_NGAP, message_p);
+ return -1;
+ }
+
+ if (ue_desc_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) {
+ NGAP_WARN("AMF UE NGAP ID mismatch in HO Cancel Ack (ctx %ld != msg %ld)", ue_desc_p->amf_ue_ngap_id, msg->amf_ue_ngap_id);
+ }
+
+ ue_desc_p->rx_stream = stream;
+
+ // cancel was already handled at RRC level, do not forward
+
+ return 0;
+}
+
static int ngap_gNB_handle_paging(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
{
ngap_gNB_amf_data_t *amf_desc_p = NULL;
@@ -1379,6 +1418,100 @@ static int ngap_gNB_handle_ng_ENDC_pdusession_modification_confirm(sctp_assoc_t
return 0;
}
+static int ngap_gNB_handle_dl_ran_status_transfer(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
+{
+ DevAssert(pdu != NULL);
+
+ NGAP_DownlinkRANStatusTransfer_t *container = &pdu->choice.initiatingMessage->value.choice.DownlinkRANStatusTransfer;
+ NGAP_DownlinkRANStatusTransferIEs_t *ie = NULL;
+
+ ngap_gNB_amf_data_t *amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0);
+ if (!amf_desc_p) {
+ NGAP_ERROR("[SCTP %u] Received DL RAN Status Transfer for unknown AMF\n", assoc_id);
+ return -1;
+ }
+
+ // AMF UE NGAP ID
+ uint64_t amf_ue_ngap_id = 0;
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_DownlinkRANStatusTransferIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true);
+ asn_INTEGER2ulong(&ie->value.choice.AMF_UE_NGAP_ID, &amf_ue_ngap_id);
+
+ // RAN UE NGAP ID
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_DownlinkRANStatusTransferIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true);
+ NGAP_RAN_UE_NGAP_ID_t gnb_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID;
+
+ ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(gnb_ue_ngap_id);
+ if (!ue_desc_p) {
+ NGAP_ERROR("No UE context for gNB_ue_ngap_id %lu\n", gnb_ue_ngap_id);
+ return -1;
+ }
+
+ ue_desc_p->rx_stream = stream;
+ if (ue_desc_p->amf_ue_ngap_id != amf_ue_ngap_id)
+ NGAP_WARN("UE context mismatch: amf_ue_ngap_id (%lu != %lu)\n", ue_desc_p->amf_ue_ngap_id, amf_ue_ngap_id);
+
+ // Transparent container
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_DownlinkRANStatusTransferIEs_t,
+ ie,
+ container,
+ NGAP_ProtocolIE_ID_id_RANStatusTransfer_TransparentContainer,
+ true);
+ NGAP_RANStatusTransfer_TransparentContainer_t *tcont = &ie->value.choice.RANStatusTransfer_TransparentContainer;
+
+ MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_DL_RAN_STATUS_TRANSFER);
+ ngap_ran_status_transfer_t *msg = &NGAP_DL_RAN_STATUS_TRANSFER(message_p);
+ memset(msg, 0, sizeof(*msg));
+
+ msg->amf_ue_ngap_id = amf_ue_ngap_id;
+ msg->gnb_ue_ngap_id = gnb_ue_ngap_id;
+
+ int nb = tcont->dRBsSubjectToStatusTransferList.list.count;
+ for (int i = 0; i < nb; ++i) {
+ NGAP_DRBsSubjectToStatusTransferItem_t *item = tcont->dRBsSubjectToStatusTransferList.list.array[i];
+ ngap_drb_status_t *s = &msg->ran_status.drb_status_list[i];
+ s->drb_id = item->dRB_ID;
+
+ // UL COUNT
+ switch (item->dRBStatusUL.present) {
+ case NGAP_DRBStatusUL_PR_dRBStatusUL18:
+ s->ul_count.sn_len = NGAP_SN_LENGTH_18;
+ s->ul_count.pdcp_sn = item->dRBStatusUL.choice.dRBStatusUL18->uL_COUNTValue.pDCP_SN18;
+ s->ul_count.hfn = item->dRBStatusUL.choice.dRBStatusUL18->uL_COUNTValue.hFN_PDCP_SN18;
+ break;
+ case NGAP_DRBStatusUL_PR_dRBStatusUL12:
+ s->ul_count.sn_len = NGAP_SN_LENGTH_12;
+ s->ul_count.pdcp_sn = item->dRBStatusUL.choice.dRBStatusUL12->uL_COUNTValue.pDCP_SN12;
+ s->ul_count.hfn = item->dRBStatusUL.choice.dRBStatusUL12->uL_COUNTValue.hFN_PDCP_SN12;
+ break;
+ default:
+ NGAP_ERROR("Unknown dRBStatusUL.present=%d\n", item->dRBStatusUL.present);
+ break;
+ }
+
+ // DL COUNT
+ switch (item->dRBStatusDL.present) {
+ case NGAP_DRBStatusDL_PR_dRBStatusDL18:
+ s->dl_count.sn_len = NGAP_SN_LENGTH_18;
+ s->dl_count.pdcp_sn = item->dRBStatusDL.choice.dRBStatusDL18->dL_COUNTValue.pDCP_SN18;
+ s->dl_count.hfn = item->dRBStatusDL.choice.dRBStatusDL18->dL_COUNTValue.hFN_PDCP_SN18;
+ break;
+ case NGAP_DRBStatusDL_PR_dRBStatusDL12:
+ s->dl_count.sn_len = NGAP_SN_LENGTH_12;
+ s->dl_count.pdcp_sn = item->dRBStatusDL.choice.dRBStatusDL12->dL_COUNTValue.pDCP_SN12;
+ s->dl_count.hfn = item->dRBStatusDL.choice.dRBStatusDL12->dL_COUNTValue.hFN_PDCP_SN12;
+ break;
+ default:
+ NGAP_ERROR("Unknown dRBStatusDL.present=%d\n", item->dRBStatusDL.present);
+ break;
+ }
+
+ msg->ran_status.nb_drb++;
+ }
+
+ itti_send_msg_to_task(TASK_RRC_GNB, ue_desc_p->gNB_instance->instance, message_p);
+ return 0;
+}
+
/* Handlers matrix. Only gNB related procedure present here */
const ngap_message_decoded_callback ngap_messages_callback[][3] = {
{0, 0, 0}, /* AMFConfigurationUpdate */
@@ -1388,13 +1521,13 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = {
{ngap_gNB_handle_nas_downlink, 0, 0}, /* DownlinkNASTransport */
{0, 0, 0}, /* DownlinkNonUEAssociatedNRPPaTransport */
{0, 0, 0}, /* DownlinkRANConfigurationTransfer */
- {0, 0, 0}, /* DownlinkRANStatusTransfer */
+ {ngap_gNB_handle_dl_ran_status_transfer, 0, 0}, /* DownlinkRANStatusTransfer */
{0, 0, 0}, /* DownlinkUEAssociatedNRPPaTransport */
{ngap_gNB_handle_error_indication, 0, 0}, /* ErrorIndication */
- {0, 0, 0}, /* HandoverCancel */
+ {0, ngap_gNB_handle_handover_cancel_ack, 0}, /* HandoverCancel */
{0, 0, 0}, /* HandoverNotification */
- {0, 0, 0}, /* HandoverPreparation */
- {0, 0, 0}, /* HandoverResourceAllocation */
+ {0, ngap_gNB_handle_handover_command, 0}, /* HandoverPreparation */
+ {ngap_gNB_handle_handover_request, 0, 0}, /* HandoverResourceAllocation */
{ngap_gNB_handle_initial_context_request, 0, 0}, /* InitialContextSetup */
{0, 0, 0}, /* InitialUEMessage */
{0, 0, 0}, /* LocationReportingControl */
diff --git a/openair3/NGAP/ngap_gNB_management_procedures.c b/openair3/NGAP/ngap_gNB_management_procedures.c
index 6ffce99d9c..9d8748a88b 100644
--- a/openair3/NGAP/ngap_gNB_management_procedures.c
+++ b/openair3/NGAP/ngap_gNB_management_procedures.c
@@ -200,3 +200,11 @@ void tnl_to_bitstring(BIT_STRING_t *out, const transport_layer_addr_t in)
out->bits_unused = 0;
}
}
+
+void bitstring_to_tnl(transport_layer_addr_t *out, const BIT_STRING_t in)
+{
+ if (in.size) {
+ memcpy(out->buffer, in.buf, in.size);
+ out->length = in.size;
+ }
+}
diff --git a/openair3/NGAP/ngap_gNB_management_procedures.h b/openair3/NGAP/ngap_gNB_management_procedures.h
index 365fd0dd9d..28f4b24e7e 100644
--- a/openair3/NGAP/ngap_gNB_management_procedures.h
+++ b/openair3/NGAP/ngap_gNB_management_procedures.h
@@ -50,4 +50,6 @@ void ngap_gNB_prepare_internal_data(void);
void tnl_to_bitstring(BIT_STRING_t *out, const transport_layer_addr_t in);
+void bitstring_to_tnl(transport_layer_addr_t *out, const BIT_STRING_t in);
+
#endif /* NGAP_GNB_MANAGEMENT_PROCEDURES_H_ */
diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c
new file mode 100644
index 0000000000..3d281be7c9
--- /dev/null
+++ b/openair3/NGAP/ngap_gNB_mobility_management.c
@@ -0,0 +1,748 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ * contact@openairinterface.org
+ */
+
+#include
+#include
+#include
+#include "conversions.h"
+#include "ngap_common.h"
+#include "ngap_msg_includes.h"
+#include "ngap_gNB_defs.h"
+#include "ngap_gNB_ue_context.h"
+#include "oai_asn1.h"
+#include "ngap_gNB_management_procedures.h"
+#include "ngap_gNB_encoder.h"
+#include "ngap_gNB_itti_messaging.h"
+#include "conversions.h"
+
+/** @brief UE Mobility Management: encode Handover Required
+ * (9.2.3.1 of 3GPP TS 38.413) NG-RAN node → AMF */
+NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg)
+{
+ NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu));
+
+ /* Prepare the NGAP message to encode */
+ pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage;
+ asn1cCalloc(pdu->choice.initiatingMessage, head);
+ head->procedureCode = NGAP_ProcedureCode_id_HandoverPreparation;
+ head->criticality = NGAP_Criticality_reject;
+ head->value.present = NGAP_InitiatingMessage__value_PR_HandoverRequired;
+ NGAP_HandoverRequired_t *out = &head->value.choice.HandoverRequired;
+
+ // AMF UE NGAP ID (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie1);
+ ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
+ ie1->criticality = NGAP_Criticality_reject;
+ ie1->value.present = NGAP_HandoverRequiredIEs__value_PR_AMF_UE_NGAP_ID;
+ asn_uint642INTEGER(&ie1->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
+
+ // RAN UE NGAP ID (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie2);
+ ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID;
+ ie2->criticality = NGAP_Criticality_reject;
+ ie2->value.present = NGAP_HandoverRequiredIEs__value_PR_RAN_UE_NGAP_ID;
+ ie2->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id;
+
+ // Handover Type (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie3);
+ ie3->id = NGAP_ProtocolIE_ID_id_HandoverType;
+ ie3->criticality = NGAP_Criticality_reject;
+ ie3->value.present = NGAP_HandoverRequiredIEs__value_PR_HandoverType;
+ ie3->value.choice.HandoverType = msg->handoverType;
+
+ // Cause (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie4);
+ ie4->id = NGAP_ProtocolIE_ID_id_Cause;
+ ie4->criticality = NGAP_Criticality_ignore;
+ ie4->value.present = NGAP_HandoverRequiredIEs__value_PR_Cause;
+ encode_ngap_cause(&ie4->value.choice.Cause, &msg->cause);
+
+ // Target ID (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie5);
+ encode_ngap_target_id(ie5, &msg->target_gnb_id);
+
+ // PDU Session Resource List (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie6);
+ ie6->id = NGAP_ProtocolIE_ID_id_PDUSessionResourceListHORqd;
+ ie6->criticality = NGAP_Criticality_reject;
+ ie6->value.present = NGAP_HandoverRequiredIEs__value_PR_PDUSessionResourceListHORqd;
+ for (int i = 0; i < msg->nb_of_pdusessions; ++i) {
+ asn1cSequenceAdd(ie6->value.choice.PDUSessionResourceListHORqd.list, NGAP_PDUSessionResourceItemHORqd_t, hoRequiredPduSession);
+ // PDU Session ID (M)
+ hoRequiredPduSession->pDUSessionID = msg->pdusessions[i].pdusession_id;
+ // Handover Required Transfer (M)
+ NGAP_HandoverRequiredTransfer_t hoRequiredTransfer = {0};
+ uint8_t ho_req_transfer_transparent_container_buffer[128] = {0};
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NGAP_HandoverRequiredTransfer, &hoRequiredTransfer);
+ asn_enc_rval_t enc_rval = aper_encode_to_buffer(&asn_DEF_NGAP_HandoverRequiredTransfer,
+ NULL,
+ &hoRequiredTransfer,
+ ho_req_transfer_transparent_container_buffer,
+ 128);
+ AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded);
+ hoRequiredPduSession->handoverRequiredTransfer.buf = CALLOC(1, (enc_rval.encoded + 7) / 8);
+ memcpy(hoRequiredPduSession->handoverRequiredTransfer.buf,
+ ho_req_transfer_transparent_container_buffer,
+ (enc_rval.encoded + 7) / 8);
+ hoRequiredPduSession->handoverRequiredTransfer.size = (enc_rval.encoded + 7) / 8;
+
+ ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_HandoverRequiredTransfer, &hoRequiredTransfer);
+ }
+
+ // Source NG-RAN Node to Target NG-RAN Node Transparent Container (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_SourceToTarget_TransparentContainer;
+ ie->criticality = NGAP_Criticality_reject;
+ ie->value.present = NGAP_HandoverRequiredIEs__value_PR_SourceToTarget_TransparentContainer;
+
+ NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer_t *source2target = calloc_or_fail(1, sizeof(*source2target));
+
+ // RRC Container (M) (HandoverPreparationInformation)
+ source2target->rRCContainer.size = msg->source2target->handoverInfo.len;
+ source2target->rRCContainer.buf = malloc_or_fail(msg->source2target->handoverInfo.len);
+ memcpy(source2target->rRCContainer.buf, msg->source2target->handoverInfo.buf, source2target->rRCContainer.size);
+
+ // PDU Session Resource Information List (O)
+ asn1cCalloc(source2target->pDUSessionResourceInformationList, pduSessionList);
+ for (uint8_t i = 0; i < msg->nb_of_pdusessions; ++i) {
+ const pdusession_resource_t *pduSession = &msg->pdusessions[i];
+ NGAP_DEBUG("Handover Required: preparing PDU Session Resource Information List for PDU Session ID %d\n",
+ pduSession->pdusession_id);
+ asn1cSequenceAdd(pduSessionList->list, NGAP_PDUSessionResourceInformationItem_t, item);
+ // PDU Session ID (M)
+ item->pDUSessionID = msg->source2target->pdu_session_resource[i].pdusession_id;
+ // QoS Flow Information List (M)
+ for (int q = 0; q < msg->source2target->pdu_session_resource[i].nb_of_qos_flow; ++q) {
+ asn1cSequenceAdd(item->qosFlowInformationList.list, NGAP_QosFlowInformationItem_t, qosFlowInfo);
+ qosFlowInfo->qosFlowIdentifier = msg->source2target->pdu_session_resource[i].qos_flow_info[q].qfi;
+ }
+ }
+
+ // Target Cell ID (NG-RAN CGI) (M)
+ source2target->targetCell_ID.present = NGAP_NGRAN_CGI_PR_nR_CGI;
+ asn1cCalloc(source2target->targetCell_ID.choice.nR_CGI, tNrCGI);
+ encode_ngap_nr_cgi(tNrCGI, &msg->target_gnb_id.plmn_identity, msg->source2target->targetCellId.nrCellIdentity);
+
+ // UE history Information (M)
+ asn1cSequenceAdd(source2target->uEHistoryInformation.list, NGAP_LastVisitedCellItem_t, lastVisitedCell);
+ lastVisitedCell->iE_Extensions = NULL;
+ // Last Visited Cell Information (M)
+ lastVisitedCell->lastVisitedCellInformation.present = NGAP_LastVisitedCellInformation_PR_nGRANCell;
+ // CHOICE (M): NG-RAN Cell
+ asn1cCalloc(lastVisitedCell->lastVisitedCellInformation.choice.nGRANCell, lastVisitedNR);
+ // Cell Type (M)
+ lastVisitedNR->cellType.cellSize = msg->source2target->ue_history_info.type;
+ // Global Cell ID (M)
+ lastVisitedNR->globalCellID.present = NGAP_NGRAN_CGI_PR_nR_CGI;
+ asn1cCalloc(lastVisitedNR->globalCellID.choice.nR_CGI, lastVisitedNrCGI);
+ cell_id_t *cell = &msg->source2target->ue_history_info.id;
+ encode_ngap_nr_cgi(lastVisitedNrCGI, &cell->plmn_identity, cell->nrCellIdentity);
+ // HO Cause Value (O)
+ if (msg->source2target->ue_history_info.cause) {
+ asn1cCalloc(lastVisitedNR->hOCauseValue, lastVisitedCause);
+ encode_ngap_cause(lastVisitedCause, msg->source2target->ue_history_info.cause);
+ }
+ // Time UE Stayed in Cell (M)
+ lastVisitedNR->timeUEStayedInCell = msg->source2target->ue_history_info.time_in_cell;
+
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target);
+
+ uint8_t source_to_target_transparent_container_buf[16384] = {0};
+ asn_enc_rval_t enc_rval = aper_encode_to_buffer(&asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer,
+ NULL,
+ (void *)source2target,
+ (void *)&source_to_target_transparent_container_buf,
+ 16384);
+ ASN_STRUCT_FREE(asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target);
+ if (enc_rval.encoded < 0) {
+ AssertFatal(enc_rval.encoded > 0,
+ "HO LOG: Source to Transparent ASN1 message encoding failed (%s, %lu)!\n",
+ enc_rval.failed_type->name,
+ enc_rval.encoded);
+ return NULL;
+ }
+
+ int total_bytes = (enc_rval.encoded + 7) / 8;
+ int ret = OCTET_STRING_fromBuf(&ie->value.choice.SourceToTarget_TransparentContainer,
+ (const char *)&source_to_target_transparent_container_buf,
+ total_bytes);
+ if (ret != 0) {
+ LOG_E(NR_RRC, "HO LOG: Can not perform OCTET_STRING_fromBuf for the SourceToTarget_TransparentContainer");
+ return NULL;
+ }
+
+ return pdu;
+}
+
+NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg)
+{
+ NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu));
+
+ /* Prepare the NGAP message to encode */
+ pdu->present = NGAP_NGAP_PDU_PR_unsuccessfulOutcome;
+ asn1cCalloc(pdu->choice.unsuccessfulOutcome, head);
+ head->procedureCode = NGAP_ProcedureCode_id_HandoverResourceAllocation;
+ head->criticality = NGAP_Criticality_reject;
+ head->value.present = NGAP_UnsuccessfulOutcome__value_PR_HandoverFailure;
+ NGAP_HandoverFailure_t *out = &head->value.choice.HandoverFailure;
+
+ // AMF_UE_NGAP_ID (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverFailureIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
+ ie->criticality = NGAP_Criticality_reject;
+ ie->value.present = NGAP_HandoverFailureIEs__value_PR_AMF_UE_NGAP_ID;
+ asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
+ }
+
+ // Cause (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverFailureIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_Cause;
+ ie->criticality = NGAP_Criticality_ignore;
+ ie->value.present = NGAP_HandoverFailureIEs__value_PR_Cause;
+ encode_ngap_cause(&ie->value.choice.Cause, &msg->cause);
+ }
+
+ return pdu;
+}
+
+static void free_ng_handover_request(ngap_handover_request_t *msg)
+{
+ free_byte_array(msg->ue_ho_prep_info);
+ free_byte_array(msg->ue_cap);
+ free(msg->mobility_restriction);
+}
+
+int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU_t *pdu)
+{
+ DevAssert(pdu != NULL);
+ NGAP_HandoverRequest_t *container = &pdu->choice.initiatingMessage->value.choice.HandoverRequest;
+ NGAP_HandoverRequestIEs_t *ie;
+
+ // Handover Type (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, true);
+ out->ho_type = ie->value.choice.HandoverType;
+ if (out->ho_type != HANDOVER_TYPE_INTRA5GS) {
+ NGAP_ERROR("Only Intra5GS Handover is supported at the moment!\n");
+ return -1;
+ }
+
+ // AMF UE NGAP ID (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true);
+ asn_INTEGER2ulong(&(ie->value.choice.AMF_UE_NGAP_ID), &out->amf_ue_ngap_id);
+
+ // GUAMI (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_GUAMI, true);
+ out->guami = decode_ngap_guami(&ie->value.choice.GUAMI);
+
+ // UE Aggregate Maximum Bit Rate (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_UEAggregateMaximumBitRate, true);
+ out->ue_ambr = decode_ngap_UEAggregateMaximumBitRate(&ie->value.choice.UEAggregateMaximumBitRate);
+
+ // Allowed NSSAI (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AllowedNSSAI, true);
+ NGAP_DEBUG("AllowedNSSAI.list.count %d\n", ie->value.choice.AllowedNSSAI.list.count);
+ out->nb_allowed_nssais = ie->value.choice.AllowedNSSAI.list.count;
+ for (int i = 0; i < out->nb_allowed_nssais; i++) {
+ out->allowed_nssai[i] = decode_ngap_nssai(&ie->value.choice.AllowedNSSAI.list.array[i]->s_NSSAI);
+ }
+
+ // UE Security Capabilities (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_UESecurityCapabilities, true);
+ out->security_capabilities = decode_ngap_security_capabilities(&ie->value.choice.UESecurityCapabilities);
+
+ // Security Context (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_SecurityContext, true);
+ NGAP_SecurityContext_t *sc = &ie->value.choice.SecurityContext;
+ memcpy(&out->security_context.next_hop, sc->nextHopNH.buf, sc->nextHopNH.size);
+ out->security_context.next_hop_chain_count = sc->nextHopChainingCount;
+
+ // Mobility Restriction List (O)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_MobilityRestrictionList, false);
+ if (ie != NULL) {
+ out->mobility_restriction = malloc_or_fail(sizeof(*out->mobility_restriction));
+ *out->mobility_restriction = decode_ngap_mobility_restriction(&ie->value.choice.MobilityRestrictionList);
+ }
+
+ // Cause (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_Cause, true);
+ out->cause = decode_ngap_cause(&ie->value.choice.Cause);
+
+ // Source to Target Transparent Container
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t,
+ ie,
+ container,
+ NGAP_ProtocolIE_ID_id_SourceToTarget_TransparentContainer,
+ true);
+ NGAP_SourceToTarget_TransparentContainer_t *choice = &ie->value.choice.SourceToTarget_TransparentContainer;
+ byte_array_t sourceToTargetTransparentContainer = create_byte_array(choice->size, choice->buf);
+ NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer_t *source2target = NULL;
+ asn_dec_rval_t dec_rval = aper_decode_complete(NULL,
+ &asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer,
+ (void **)&source2target,
+ sourceToTargetTransparentContainer.buf,
+ sourceToTargetTransparentContainer.len);
+
+ free_byte_array(sourceToTargetTransparentContainer);
+ if (dec_rval.code != RC_OK) {
+ free_ng_handover_request(out);
+ NGAP_ERROR("Failed to decode sourceToTargetTransparentContainer\n");
+ return -1;
+ }
+
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target);
+
+ // Extract Cell Identity
+ BIT_STRING_TO_NR_CELL_IDENTITY(&source2target->targetCell_ID.choice.nR_CGI->nRCellIdentity, out->nr_cell_id);
+
+ // Handover Preparation Information: store and decode
+ out->ue_ho_prep_info = create_byte_array(source2target->rRCContainer.size, source2target->rRCContainer.buf);
+ NR_HandoverPreparationInformation_t *hoPrepInformation = NULL;
+ asn_dec_rval_t hoPrep_dec_rval = uper_decode_complete(NULL,
+ &asn_DEF_NR_HandoverPreparationInformation,
+ (void **)&hoPrepInformation,
+ source2target->rRCContainer.buf,
+ source2target->rRCContainer.size);
+ AssertFatal(hoPrep_dec_rval.code == RC_OK && hoPrep_dec_rval.consumed > 0, "Handover Prep Info decode error\n");
+ if (hoPrep_dec_rval.code != RC_OK && !hoPrep_dec_rval.consumed) {
+ NGAP_ERROR("Failed to decode HandoverPreparationInformation, abort Handover Request decoding\n");
+ free_ng_handover_request(out);
+ ASN_STRUCT_FREE(asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target);
+ return -1;
+ }
+
+ ASN_STRUCT_FREE(asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target);
+
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NR_HandoverPreparationInformation, hoPrepInformation);
+
+ // Decode UE capabilities and store
+ NR_HandoverPreparationInformation_IEs_t *hpi =
+ hoPrepInformation->criticalExtensions.choice.c1->choice.handoverPreparationInformation;
+ const NR_UE_CapabilityRAT_ContainerList_t *ue_CapabilityRAT_ContainerList = &hpi->ue_CapabilityRAT_List;
+ out->ue_cap.len = uper_encode_to_new_buffer(&asn_DEF_NR_UE_CapabilityRAT_ContainerList,
+ NULL,
+ ue_CapabilityRAT_ContainerList,
+ (void **)&out->ue_cap.buf);
+
+ ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hoPrepInformation);
+
+ if (out->ue_cap.len <= 0) {
+ free_ng_handover_request(out);
+ NGAP_ERROR("could not encode UE-CapabilityRAT-ContainerList\n");
+ return -1;
+ }
+
+ // PDU Session Resource Setup List (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t,
+ ie,
+ container,
+ NGAP_ProtocolIE_ID_id_PDUSessionResourceSetupListHOReq,
+ true);
+ out->nb_of_pdusessions = ie->value.choice.PDUSessionResourceSetupListHOReq.list.count;
+ for (int pduSesIdx = 0; pduSesIdx < out->nb_of_pdusessions; ++pduSesIdx) {
+ NGAP_PDUSessionResourceSetupItemHOReq_t *item_p = ie->value.choice.PDUSessionResourceSetupListHOReq.list.array[pduSesIdx];
+ // PDU Session ID (M)
+ ho_request_pdusession_t *setup = &out->pduSessionResourceSetupList[pduSesIdx];
+ setup->pdusession_id = item_p->pDUSessionID;
+ setup->pdu_session_type = PDUSessionType_ipv4;
+ // S-NSSAI (M)
+ setup->nssai = decode_ngap_nssai(&item_p->s_NSSAI);
+ // Handover Request Transfer (M)
+ bool ret = decodePDUSessionResourceSetup(&setup->pdusessionTransfer, item_p->handoverRequestTransfer);
+ if (!ret) {
+ free_ng_handover_request(out);
+ NGAP_ERROR("Failed to decode pDUSessionResourceSetupRequestTransfer in NG Initial Context Setup Request\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg)
+{
+ NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu));
+
+ pdu->present = NGAP_NGAP_PDU_PR_successfulOutcome;
+ asn1cCalloc(pdu->choice.successfulOutcome, head);
+ head->procedureCode = NGAP_ProcedureCode_id_HandoverResourceAllocation;
+ head->criticality = NGAP_Criticality_reject;
+ head->value.present = NGAP_SuccessfulOutcome__value_PR_HandoverRequestAcknowledge;
+ NGAP_HandoverRequestAcknowledge_t *out = &head->value.choice.HandoverRequestAcknowledge;
+
+ // AMF UE NGAP ID (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
+ ie->criticality = NGAP_Criticality_ignore;
+ ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_AMF_UE_NGAP_ID;
+ asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
+ }
+
+ // RAN UE NGAP ID (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID;
+ ie->criticality = NGAP_Criticality_ignore;
+ ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_RAN_UE_NGAP_ID;
+ ie->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id;
+ }
+
+ // Target to Source Transparent Container (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_TargetToSource_TransparentContainer;
+ ie->criticality = NGAP_Criticality_reject;
+ ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_TargetToSource_TransparentContainer;
+ NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer_t *t2s = calloc_or_fail(1, sizeof(*t2s));
+ // RRC Container (M)
+ {
+ // Encode Handover Command
+ t2s->rRCContainer.size = msg->target2source.len;
+ t2s->rRCContainer.buf = malloc_or_fail(msg->target2source.len);
+ memcpy(t2s->rRCContainer.buf, msg->target2source.buf, msg->target2source.len);
+ byte_array_t ba = { .buf = NULL, .len = 0 };
+ ba.len = aper_encode_to_new_buffer(&asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, NULL, t2s, (void **)&ba.buf);
+ OCTET_STRING_fromBuf(&ie->value.choice.TargetToSource_TransparentContainer, (const char *)ba.buf, ba.len);
+ ASN_STRUCT_FREE(asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, t2s);
+ free_byte_array(ba);
+ }
+ }
+
+ // PDU Session Resource Admitted List (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_PDUSessionResourceAdmittedList;
+ ie->criticality = NGAP_Criticality_ignore;
+ ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_PDUSessionResourceAdmittedList;
+
+ for (int pduSesIdx = 0; pduSesIdx < msg->nb_of_pdusessions; pduSesIdx++) {
+ asn1cSequenceAdd(ie->value.choice.PDUSessionResourceAdmittedList.list, NGAP_PDUSessionResourceAdmittedItem_t, item);
+ item->pDUSessionID = msg->pdusessions[pduSesIdx].pdu_session_id;
+ /* dLQosFlowPerTNLInformation */
+ NGAP_HandoverRequestAcknowledgeTransfer_t transfer = {0};
+ transfer.dL_NGU_UP_TNLInformation.present = NGAP_UPTransportLayerInformation_PR_gTPTunnel;
+ asn1cCalloc(transfer.dL_NGU_UP_TNLInformation.choice.gTPTunnel, tmp);
+ GTP_TEID_TO_ASN1(msg->pdusessions[pduSesIdx].ack_transfer.gtp_teid, &tmp->gTP_TEID);
+ tnl_to_bitstring(&tmp->transportLayerAddress, msg->pdusessions[pduSesIdx].ack_transfer.gNB_addr);
+
+ for (int j = 0; j < msg->pdusessions[pduSesIdx].ack_transfer.nb_of_qos_flow; j++) {
+ asn1cSequenceAdd(transfer.qosFlowSetupResponseList.list, NGAP_QosFlowItemWithDataForwarding_t, qosItem);
+ qosItem->qosFlowIdentifier = msg->pdusessions[pduSesIdx].ack_transfer.qos_setup_list[j].qfi;
+ qosItem->dataForwardingAccepted = malloc_or_fail(sizeof(*qosItem->dataForwardingAccepted));
+ *qosItem->dataForwardingAccepted = NGAP_DataForwardingAccepted_data_forwarding_accepted;
+ }
+
+ void *buf;
+ ssize_t encoded = aper_encode_to_new_buffer(&asn_DEF_NGAP_HandoverRequestAcknowledgeTransfer, NULL, &transfer, &buf);
+ AssertFatal(encoded > 0, "ASN1 message encoding failed !\n");
+ item->handoverRequestAcknowledgeTransfer.buf = buf;
+ item->handoverRequestAcknowledgeTransfer.size = encoded;
+
+ ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_HandoverRequestAcknowledgeTransfer, &transfer);
+ }
+ }
+
+ return pdu;
+}
+
+void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg)
+{
+ free_byte_array(msg->target2source);
+}
+
+/** @brief Decoder for the NG Handover Command */
+int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu)
+{
+ DevAssert(pdu != NULL);
+ NGAP_HandoverCommandIEs_t *ie;
+ NGAP_HandoverCommand_t *container = &pdu->choice.successfulOutcome->value.choice.HandoverCommand;
+
+ // AMF UE NGAP ID (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true);
+ asn_INTEGER2ulong(&(ie->value.choice.AMF_UE_NGAP_ID), &msg->amf_ue_ngap_id);
+
+ // RAN UE NGAP ID (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true);
+ msg->gNB_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID;
+
+ // Handover Type (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, true);
+ if (ie->value.choice.HandoverType != HANDOVER_TYPE_INTRA5GS) {
+ NGAP_ERROR("Only Intra 5GS Handover is supported at the moment!\n");
+ return -1;
+ }
+
+ // PDU Session Resource Handover List (O)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, false);
+ if (ie != NULL) {
+ msg->nb_of_pdusessions = ie->value.choice.PDUSessionResourceHandoverList.list.count;
+
+ for (int i = 0; i < msg->nb_of_pdusessions; ++i) {
+ NGAP_PDUSessionResourceHandoverItem_t *item = ie->value.choice.PDUSessionResourceHandoverList.list.array[i];
+
+ // PDU Session ID (M)
+ msg->pdu_sessions[i].pdusession_id = item->pDUSessionID;
+
+ // Handover Command Transfer (M)
+ NGAP_HandoverCommandTransfer_t *hoCommandTransfer = NULL;
+ asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
+ &asn_DEF_NGAP_HandoverCommandTransfer,
+ (void **)&hoCommandTransfer,
+ item->handoverCommandTransfer.buf,
+ item->handoverCommandTransfer.size);
+ if (dec_rval.code != RC_OK) {
+ NGAP_ERROR("Failed to decode handoverCommandTransfer\n");
+ continue;
+ }
+
+ // DL Forwarding UP TNL Information (O)
+ if (hoCommandTransfer->dLForwardingUP_TNLInformation) {
+ NGAP_UPTransportLayerInformation_t *up_tnl = hoCommandTransfer->dLForwardingUP_TNLInformation;
+ if (hoCommandTransfer->dLForwardingUP_TNLInformation->present == NGAP_UPTransportLayerInformation_PR_gTPTunnel) {
+ OCTET_STRING_TO_INT32(&(up_tnl->choice.gTPTunnel->gTP_TEID), msg->pdu_sessions[i].ho_command_transfer.gtp_teid);
+ bitstring_to_tnl(&msg->pdu_sessions[i].ho_command_transfer.gNB_addr, up_tnl->choice.gTPTunnel->transportLayerAddress);
+ } else {
+ NGAP_WARN("Missing DL Forwarding UP TNL Information\n");
+ }
+ }
+ ASN_STRUCT_FREE(asn_DEF_NGAP_HandoverCommandTransfer, hoCommandTransfer);
+ }
+ }
+
+ // Target to Source Transparent Container (M)
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t,
+ ie,
+ container,
+ NGAP_ProtocolIE_ID_id_TargetToSource_TransparentContainer,
+ true);
+ NGAP_TargetToSource_TransparentContainer_t *target2source = &(ie->value.choice.TargetToSource_TransparentContainer);
+ NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer_t *transparentContainer = NULL;
+ asn_dec_rval_t dec_rval = aper_decode_complete(NULL,
+ &asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer,
+ (void **)&transparentContainer,
+ target2source->buf,
+ target2source->size);
+ if (dec_rval.code != RC_OK) {
+ NGAP_ERROR("Failed to decode TargetToSource_TransparentContainer\n");
+ return -1;
+ }
+
+ msg->handoverCommand = create_byte_array(transparentContainer->rRCContainer.size, transparentContainer->rRCContainer.buf);
+ ASN_STRUCT_FREE(asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, transparentContainer);
+
+ return 0;
+}
+
+void free_ng_handover_command(ngap_handover_command_t *msg)
+{
+ free_byte_array(msg->handoverCommand);
+}
+
+NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg)
+{
+ NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu));
+
+ // Message Type (M)
+ pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage;
+ asn1cCalloc(pdu->choice.initiatingMessage, head);
+ head->procedureCode = NGAP_ProcedureCode_id_HandoverNotification;
+ head->criticality = NGAP_Criticality_ignore;
+ head->value.present = NGAP_InitiatingMessage__value_PR_HandoverNotify;
+ NGAP_HandoverNotify_t *out = &head->value.choice.HandoverNotify;
+
+ // AMF UE NGAP ID (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverNotifyIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
+ ie->criticality = NGAP_Criticality_reject;
+ ie->value.present = NGAP_HandoverNotifyIEs__value_PR_AMF_UE_NGAP_ID;
+ asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
+ }
+
+ // RAN UE NGAP ID (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverNotifyIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID;
+ ie->criticality = NGAP_Criticality_reject;
+ ie->value.present = NGAP_HandoverNotifyIEs__value_PR_RAN_UE_NGAP_ID;
+ ie->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id;
+ }
+
+ // User Location Information (M)
+ {
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverNotifyIEs_t, ie);
+ ie->id = NGAP_ProtocolIE_ID_id_UserLocationInformation;
+ ie->criticality = NGAP_Criticality_ignore;
+ ie->value.present = NGAP_HandoverNotifyIEs__value_PR_UserLocationInformation;
+ ie->value.choice.UserLocationInformation.present = NGAP_UserLocationInformation_PR_userLocationInformationNR;
+ asn1cCalloc(ie->value.choice.UserLocationInformation.choice.userLocationInformationNR, userinfo_nr_p);
+ // NR User Location Information
+ const target_ran_node_id_t *target = &msg->user_info.target_ng_ran;
+ // CGI (M)
+ MACRO_GNB_ID_TO_CELL_IDENTITY(target->targetgNBId, msg->user_info.nrCellIdentity, &userinfo_nr_p->nR_CGI.nRCellIdentity);
+ MCC_MNC_TO_TBCD(target->plmn_identity.mcc,
+ target->plmn_identity.mnc,
+ target->plmn_identity.mnc_digit_length,
+ &userinfo_nr_p->nR_CGI.pLMNIdentity);
+ // TAI (M)
+ INT24_TO_OCTET_STRING(target->tac, &userinfo_nr_p->tAI.tAC);
+ MCC_MNC_TO_PLMNID(target->plmn_identity.mcc,
+ target->plmn_identity.mnc,
+ target->plmn_identity.mnc_digit_length,
+ &userinfo_nr_p->tAI.pLMNIdentity);
+ }
+
+ return pdu;
+}
+
+/** @brief NGAP Handover Cancel encoding (9.2.3.11 3GPP TS 38.413 Handover Cancel)
+ * 8.4.5 Handover Cancellation (source NG-RAN -> AMF) */
+NGAP_NGAP_PDU_t *encode_ng_handover_cancel(const ngap_handover_cancel_t *msg)
+{
+ DevAssert(msg != NULL);
+
+ NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu));
+
+ pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage;
+ asn1cCalloc(pdu->choice.initiatingMessage, head);
+ head->procedureCode = NGAP_ProcedureCode_id_HandoverCancel;
+ head->criticality = NGAP_Criticality_reject;
+ head->value.present = NGAP_InitiatingMessage__value_PR_HandoverCancel;
+ NGAP_HandoverCancel_t *out = &head->value.choice.HandoverCancel;
+
+ // AMF UE NGAP ID (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie1);
+ ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
+ ie1->criticality = NGAP_Criticality_reject;
+ ie1->value.present = NGAP_HandoverCancelIEs__value_PR_AMF_UE_NGAP_ID;
+ asn_uint642INTEGER(&ie1->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
+
+ // RAN UE NGAP ID (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie2);
+ ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID;
+ ie2->criticality = NGAP_Criticality_reject;
+ ie2->value.present = NGAP_HandoverCancelIEs__value_PR_RAN_UE_NGAP_ID;
+ ie2->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id;
+
+ // Cause (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie3);
+ ie3->id = NGAP_ProtocolIE_ID_id_Cause;
+ ie3->criticality = NGAP_Criticality_reject;
+ ie3->value.present = NGAP_HandoverCancelIEs__value_PR_Cause;
+ encode_ngap_cause(&ie3->value.choice.Cause, &msg->cause);
+
+ return pdu;
+}
+
+/** @brief Decode NGAP Handover Cancel Acknowledge (9.2.3.12 3GPP TS 38.413) */
+int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NGAP_PDU_t *pdu)
+{
+ DevAssert(out != NULL);
+ DevAssert(pdu != NULL);
+
+ NGAP_HandoverCancelAcknowledge_t *container = &pdu->choice.successfulOutcome->value.choice.HandoverCancelAcknowledge;
+ NGAP_HandoverCancelAcknowledgeIEs_t *ie;
+
+ /* AMF UE NGAP ID (M) */
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCancelAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true);
+ asn_INTEGER2ulong(&ie->value.choice.AMF_UE_NGAP_ID, &out->amf_ue_ngap_id);
+
+ /* RAN UE NGAP ID (M) */
+ NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCancelAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true);
+ out->gNB_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID;
+
+ return 0;
+}
+
+/** @brief Encode NGAP UL RAN Status Transfer (9.2.3.14 of 3GPP TS 38.413) */
+NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg)
+{
+ NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu));
+ pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage;
+ asn1cCalloc(pdu->choice.initiatingMessage, head);
+ head->procedureCode = NGAP_ProcedureCode_id_UplinkRANStatusTransfer;
+ head->criticality = NGAP_Criticality_ignore;
+ head->value.present = NGAP_InitiatingMessage__value_PR_UplinkRANStatusTransfer;
+ NGAP_UplinkRANStatusTransfer_t *out = &head->value.choice.UplinkRANStatusTransfer;
+
+ // AMF UE NGAP ID (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_UplinkRANStatusTransferIEs_t, ie1);
+ ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
+ ie1->criticality = NGAP_Criticality_reject;
+ ie1->value.present = NGAP_UplinkRANStatusTransferIEs__value_PR_AMF_UE_NGAP_ID;
+ asn_uint642INTEGER(&ie1->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
+
+ // RAN UE NGAP ID (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_UplinkRANStatusTransferIEs_t, ie2);
+ ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID;
+ ie2->criticality = NGAP_Criticality_reject;
+ ie2->value.present = NGAP_UplinkRANStatusTransferIEs__value_PR_RAN_UE_NGAP_ID;
+ ie2->value.choice.RAN_UE_NGAP_ID = msg->gnb_ue_ngap_id;
+
+ // RAN Status Transfer Transparent Container (M)
+ asn1cSequenceAdd(out->protocolIEs.list, NGAP_UplinkRANStatusTransferIEs_t, ie3);
+ ie3->id = NGAP_ProtocolIE_ID_id_RANStatusTransfer_TransparentContainer;
+ ie3->criticality = NGAP_Criticality_reject;
+ ie3->value.present = NGAP_UplinkRANStatusTransferIEs__value_PR_RANStatusTransfer_TransparentContainer;
+ NGAP_RANStatusTransfer_TransparentContainer_t *container = &ie3->value.choice.RANStatusTransfer_TransparentContainer;
+
+ for (int i = 0; i < msg->ran_status.nb_drb; ++i) {
+ const ngap_drb_status_t *s = &msg->ran_status.drb_status_list[i];
+ asn1cSequenceAdd(container->dRBsSubjectToStatusTransferList.list, NGAP_DRBsSubjectToStatusTransferItem_t, drb_item);
+ drb_item->dRB_ID = s->drb_id;
+
+ // UL COUNT
+ if (s->ul_count.sn_len == NGAP_SN_LENGTH_18) {
+ drb_item->dRBStatusUL.present = NGAP_DRBStatusUL_PR_dRBStatusUL18;
+ asn1cCalloc(drb_item->dRBStatusUL.choice.dRBStatusUL18, ul18);
+ ul18->uL_COUNTValue.pDCP_SN18 = s->ul_count.pdcp_sn;
+ ul18->uL_COUNTValue.hFN_PDCP_SN18 = s->ul_count.hfn;
+ } else {
+ drb_item->dRBStatusUL.present = NGAP_DRBStatusUL_PR_dRBStatusUL12;
+ asn1cCalloc(drb_item->dRBStatusUL.choice.dRBStatusUL12, ul12);
+ ul12->uL_COUNTValue.pDCP_SN12 = s->ul_count.pdcp_sn;
+ ul12->uL_COUNTValue.hFN_PDCP_SN12 = s->ul_count.hfn;
+ }
+
+ // DL COUNT
+ if (s->dl_count.sn_len == NGAP_SN_LENGTH_18) {
+ drb_item->dRBStatusDL.present = NGAP_DRBStatusDL_PR_dRBStatusDL18;
+ asn1cCalloc(drb_item->dRBStatusDL.choice.dRBStatusDL18, dl18);
+ dl18->dL_COUNTValue.pDCP_SN18 = s->dl_count.pdcp_sn;
+ dl18->dL_COUNTValue.hFN_PDCP_SN18 = s->dl_count.hfn;
+ } else {
+ drb_item->dRBStatusDL.present = NGAP_DRBStatusDL_PR_dRBStatusDL12;
+ asn1cCalloc(drb_item->dRBStatusDL.choice.dRBStatusDL12, dl12);
+ dl12->dL_COUNTValue.pDCP_SN12 = s->dl_count.pdcp_sn;
+ dl12->dL_COUNTValue.hFN_PDCP_SN12 = s->dl_count.hfn;
+ }
+ }
+
+ if (LOG_DEBUGFLAG(DEBUG_ASN1))
+ xer_fprint(stdout, &asn_DEF_NGAP_RANStatusTransfer_TransparentContainer, &container);
+
+ return pdu;
+}
diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h
new file mode 100644
index 0000000000..18aac7c61d
--- /dev/null
+++ b/openair3/NGAP/ngap_gNB_mobility_management.h
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ * contact@openairinterface.org
+ */
+
+#ifndef NGAP_GNB_MOBILITY_MANAGEMENT_H_
+#define NGAP_GNB_MOBILITY_MANAGEMENT_H_
+
+#include
+#include "common/platform_types.h"
+#include "ngap_messages_types.h"
+
+NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg);
+NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg);
+int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU_t *pdu);
+NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg);
+void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg);
+int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu);
+void free_ng_handover_command(ngap_handover_command_t *msg);
+NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg);
+NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg);
+NGAP_NGAP_PDU_t *encode_ng_handover_cancel(const ngap_handover_cancel_t *msg);
+int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NGAP_PDU_t *pdu);
+
+#endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */
diff --git a/openair3/NGAP/ngap_gNB_ue_context.c b/openair3/NGAP/ngap_gNB_ue_context.c
index d0949b88c2..994271d822 100644
--- a/openair3/NGAP/ngap_gNB_ue_context.c
+++ b/openair3/NGAP/ngap_gNB_ue_context.c
@@ -76,6 +76,12 @@ struct ngap_gNB_ue_context_s *ngap_get_ue_context(uint32_t gNB_ue_ngap_id)
return RB_FIND(ngap_ue_map, &ngap_ue_head, &temp);
}
+ngap_gNB_ue_context_t *ngap_get_ue_context_from_amf_ue_ngap_id(uint32_t amf_ue_ngap_id)
+{
+ ngap_gNB_ue_context_t temp = {.amf_ue_ngap_id = amf_ue_ngap_id};
+ return RB_FIND(ngap_ue_map, &ngap_ue_head, &temp);
+}
+
struct ngap_gNB_ue_context_s *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id)
{
struct ngap_gNB_ue_context_s *tmp = ngap_get_ue_context(gNB_ue_ngap_id);
diff --git a/openair3/NGAP/ngap_gNB_ue_context.h b/openair3/NGAP/ngap_gNB_ue_context.h
index aaf9e83bbc..1022f2f4c3 100644
--- a/openair3/NGAP/ngap_gNB_ue_context.h
+++ b/openair3/NGAP/ngap_gNB_ue_context.h
@@ -81,6 +81,7 @@ typedef struct ngap_gNB_ue_context_s {
void ngap_store_ue_context(const ngap_gNB_ue_context_t *ue_desc_p);
ngap_gNB_ue_context_t *ngap_get_ue_context(uint32_t gNB_ue_ngap_id);
+ngap_gNB_ue_context_t *ngap_get_ue_context_from_amf_ue_ngap_id(uint32_t amf_ue_ngap_id);
ngap_gNB_ue_context_t *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id);
#endif /* NGAP_GNB_UE_CONTEXT_H_ */
diff --git a/openair3/NGAP/ngap_msg_includes.h b/openair3/NGAP/ngap_msg_includes.h
index 1debc2e9f1..816cdfb979 100644
--- a/openair3/NGAP/ngap_msg_includes.h
+++ b/openair3/NGAP/ngap_msg_includes.h
@@ -78,5 +78,36 @@
#include "NGAP_Dynamic5QIDescriptor.h"
#include "NGAP_PDUSessionResourceModifyRequestTransfer.h"
#include "NGAP_QosFlowAddOrModifyRequestItem.h"
+#include "NGAP_PDUSessionResourceItemHORqd.h"
+#include "NGAP_PDUSessionResourceInformationItem.h"
+#include "NGAP_QosFlowInformationItem.h"
+#include "NGAP_LastVisitedCellItem.h"
+#include "NGAP_LastVisitedNGRANCellInformation.h"
+#include "NGAP_PDUSessionResourceInformationList.h"
+#include "NGAP_HandoverRequiredTransfer.h"
+#include "NGAP_SourceNGRANNode-ToTargetNGRANNode-TransparentContainer.h"
+#include "NGAP_PDUSessionResourceSetupItemHOReq.h"
+#include "NGAP_HandoverCommand.h"
+#include "NGAP_HandoverCommandTransfer.h"
+#include "NGAP_TargetNGRANNode-ToSourceNGRANNode-TransparentContainer.h"
+#include "NGAP_TargetToSource-TransparentContainer.h"
+#include "NGAP_UEHistoryInformation.h"
+#include "NGAP_Cause.h"
+#include "NGAP_PDUSessionResourceHandoverItem.h"
+#include "NGAP_SourceToTarget-TransparentContainer.h"
+#include "NGAP_HandoverRequest.h"
+#include "NGAP_PDUSessionResourceAdmittedList.h"
+#include "NGAP_PDUSessionResourceAdmittedItem.h"
+#include "NGAP_HandoverRequestAcknowledgeTransfer.h"
+#include "NGAP_QosFlowItemWithDataForwarding.h"
+#include "NR_HandoverPreparationInformation.h"
+#include "NGAP_HandoverNotify.h"
+#include "NGAP_DRBsSubjectToStatusTransferItem.h"
+#include "NGAP_DRBStatusUL.h"
+#include "NGAP_DRBStatusUL18.h"
+#include "NGAP_DRBStatusUL12.h"
+#include "NGAP_DRBStatusDL.h"
+#include "NGAP_DRBStatusDL18.h"
+#include "NGAP_DRBStatusDL12.h"
#endif // NGAP_MSG_INCLUDES_H
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf
new file mode 100644
index 0000000000..b486b52dec
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf
@@ -0,0 +1,251 @@
+Active_gNBs = ( "gNB-OAI");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xe00;
+ gNB_name = "gNB-OAI";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
+ @include "neighbour-config-rfsim.conf"
+
+ nr_cellid = 12345678L;
+
+ ////////// Physical parameters:
+
+ do_CSIRS = 1;
+ do_SRS = 1;
+
+ #uess_agg_levels = [0,1,2,2,1]
+ servingCellConfigCommon = (
+ {
+ #spCellConfigCommon
+
+ physCellId = 0;
+
+# downlinkConfigCommon
+ #frequencyInfoDL
+ # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
+ absoluteFrequencySSB = 641280;
+ dl_frequencyBand = 78;
+ # this is 3600 MHz
+ dl_absoluteFrequencyPointA = 640008;
+ #scs-SpecificCarrierList
+ dl_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ dl_subcarrierSpacing = 1;
+ dl_carrierBandwidth = 106;
+ #initialDownlinkBWP
+ #genericParameters
+ # this is RBstart=27,L=48 (275*(L-1))+RBstart
+ initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialDLBWPsubcarrierSpacing = 1;
+ #pdcch-ConfigCommon
+ initialDLBWPcontrolResourceSetZero = 12;
+ initialDLBWPsearchSpaceZero = 0;
+
+ #uplinkConfigCommon
+ #frequencyInfoUL
+ ul_frequencyBand = 78;
+ #scs-SpecificCarrierList
+ ul_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ ul_subcarrierSpacing = 1;
+ ul_carrierBandwidth = 106;
+ pMax = 20;
+ #initialUplinkBWP
+ #genericParameters
+ initialULBWPlocationAndBandwidth = 28875;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialULBWPsubcarrierSpacing = 1;
+ #rach-ConfigCommon
+ #rach-ConfigGeneric
+ prach_ConfigurationIndex = 98;
+#prach_msg1_FDM
+#0 = one, 1=two, 2=four, 3=eight
+ prach_msg1_FDM = 0;
+ prach_msg1_FrequencyStart = 0;
+ zeroCorrelationZoneConfig = 13;
+ preambleReceivedTargetPower = -96;
+#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
+ preambleTransMax = 6;
+#powerRampingStep
+# 0=dB0,1=dB2,2=dB4,3=dB6
+ powerRampingStep = 1;
+#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
+#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
+#one (0..15) 4,8,12,16,...60,64
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
+#ra_ContentionResolutionTimer
+#(0..7) 8,16,24,32,40,48,56,64
+ ra_ContentionResolutionTimer = 7;
+ rsrp_ThresholdSSB = 19;
+#prach-RootSequenceIndex_PR
+#1 = 839, 2 = 139
+ prach_RootSequenceIndex_PR = 2;
+ prach_RootSequenceIndex = 1;
+ # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
+ #
+ msg1_SubcarrierSpacing = 1,
+# restrictedSetConfig
+# 0=unrestricted, 1=restricted type A, 2=restricted type B
+ restrictedSetConfig = 0,
+
+ msg3_DeltaPreamble = 1;
+ p0_NominalWithGrant =-90;
+
+# pucch-ConfigCommon setup :
+# pucchGroupHopping
+# 0 = neither, 1= group hopping, 2=sequence hopping
+ pucchGroupHopping = 0;
+ hoppingId = 40;
+ p0_nominal = -90;
+
+ ssb_PositionsInBurst_Bitmap = 1;
+
+# ssb_periodicityServingCell
+# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
+ ssb_periodicityServingCell = 2;
+
+# dmrs_TypeA_position
+# 0 = pos2, 1 = pos3
+ dmrs_TypeA_Position = 0;
+
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ subcarrierSpacing = 1;
+
+
+ #tdd-UL-DL-ConfigurationCommon
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ referenceSubcarrierSpacing = 1;
+ # pattern1
+ # dl_UL_TransmissionPeriodicity
+ # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
+ dl_UL_TransmissionPeriodicity = 6;
+ nrofDownlinkSlots = 7;
+ nrofDownlinkSymbols = 6;
+ nrofUplinkSlots = 2;
+ nrofUplinkSymbols = 4;
+
+ ssPBCH_BlockPower = -25;
+ }
+
+ );
+
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+
+
+ ////////// AMF parameters:
+ amf_ip_address = ({ ipv4 = "192.168.70.132"; });
+
+
+ NETWORK_INTERFACES :
+ {
+ GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129/24";
+ GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129/24";
+ GNB_PORT_FOR_S1U = 2152; # Spec 2152
+ };
+
+ }
+);
+
+MACRLCs = (
+{
+ num_cc = 1;
+ tr_s_preference = "local_L1";
+ tr_n_preference = "local_RRC";
+ pusch_TargetSNRx10 = 150;
+ pucch_TargetSNRx10 = 200;
+}
+);
+
+L1s = (
+{
+ num_cc = 1;
+ tr_n_preference = "local_mac";
+ prach_dtx_threshold = 120;
+ pucch0_dtx_threshold = 100;
+ ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
+}
+);
+
+RUs = (
+{
+ local_rf = "yes"
+ nb_tx = 1
+ nb_rx = 1
+ att_tx = 12;
+ att_rx = 12;
+ bands = [78];
+ max_pdschReferenceSignalPower = -27;
+ max_rxgain = 114;
+ eNB_instances = [0];
+ clock_src = "internal";
+}
+);
+
+
+rfsimulator :
+{
+ serveraddr = "server";
+ serverport = 4043;
+ options = (); #("saviq"); or/and "chanmod"
+ modelname = "AWGN";
+ IQfile = "/tmp/rfsimulator.iqs";
+};
+
+security = {
+ # preferred ciphering algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nea0, nea1, nea2, nea3
+ ciphering_algorithms = ( "nea0" );
+
+ # preferred integrity algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nia0, nia1, nia2, nia3
+ integrity_algorithms = ( "nia2", "nia0" );
+
+ # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter
+ # what 'ciphering_algorithms' configures; same thing for 'drb_integrity'
+ drb_ciphering = "yes";
+ drb_integrity = "no";
+};
+
+log_config :
+{
+ global_log_level ="info";
+ hw_log_level ="info";
+ phy_log_level ="info";
+ mac_log_level ="info";
+ rlc_log_level ="info";
+ pdcp_log_level ="info";
+ rrc_log_level ="info";
+ ngap_log_level ="debug";
+ f1ap_log_level ="debug";
+};
+
+e2_agent = {
+ near_ric_ip_addr = "127.0.0.1";
+ #sm_dir = "/path/where/the/SMs/are/located/"
+ sm_dir = "/usr/local/lib/flexric/"
+};
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf
new file mode 100644
index 0000000000..c649c8b0f8
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf
@@ -0,0 +1,251 @@
+Active_gNBs = ( "gNB-OAI");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+gNBs =
+(
+ {
+ ////////// Identification parameters:
+ gNB_ID = 0xb00;
+ gNB_name = "gNB-OAI";
+
+ // Tracking area code, 0x0000 and 0xfffe are reserved values
+ tracking_area_code = 1;
+ plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
+ @include "neighbour-config-rfsim.conf"
+
+ nr_cellid = 720898;
+
+ ////////// Physical parameters:
+
+ do_CSIRS = 1;
+ do_SRS = 1;
+
+ #uess_agg_levels = [0,1,2,2,1]
+ servingCellConfigCommon = (
+ {
+ #spCellConfigCommon
+
+ physCellId = 1;
+
+# downlinkConfigCommon
+ #frequencyInfoDL
+ # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP)
+ absoluteFrequencySSB = 621312;
+ dl_frequencyBand = 78;
+ # this is 3600 MHz
+ dl_absoluteFrequencyPointA = 620040;
+ #scs-SpecificCarrierList
+ dl_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ dl_subcarrierSpacing = 1;
+ dl_carrierBandwidth = 106;
+ #initialDownlinkBWP
+ #genericParameters
+ # this is RBstart=27,L=48 (275*(L-1))+RBstart
+ initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialDLBWPsubcarrierSpacing = 1;
+ #pdcch-ConfigCommon
+ initialDLBWPcontrolResourceSetZero = 12;
+ initialDLBWPsearchSpaceZero = 0;
+
+ #uplinkConfigCommon
+ #frequencyInfoUL
+ ul_frequencyBand = 78;
+ #scs-SpecificCarrierList
+ ul_offstToCarrier = 0;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ ul_subcarrierSpacing = 1;
+ ul_carrierBandwidth = 106;
+ pMax = 20;
+ #initialUplinkBWP
+ #genericParameters
+ initialULBWPlocationAndBandwidth = 28875;
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ initialULBWPsubcarrierSpacing = 1;
+ #rach-ConfigCommon
+ #rach-ConfigGeneric
+ prach_ConfigurationIndex = 98;
+#prach_msg1_FDM
+#0 = one, 1=two, 2=four, 3=eight
+ prach_msg1_FDM = 0;
+ prach_msg1_FrequencyStart = 0;
+ zeroCorrelationZoneConfig = 13;
+ preambleReceivedTargetPower = -96;
+#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
+ preambleTransMax = 6;
+#powerRampingStep
+# 0=dB0,1=dB2,2=dB4,3=dB6
+ powerRampingStep = 1;
+#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
+#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
+#one (0..15) 4,8,12,16,...60,64
+ ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
+#ra_ContentionResolutionTimer
+#(0..7) 8,16,24,32,40,48,56,64
+ ra_ContentionResolutionTimer = 7;
+ rsrp_ThresholdSSB = 19;
+#prach-RootSequenceIndex_PR
+#1 = 839, 2 = 139
+ prach_RootSequenceIndex_PR = 2;
+ prach_RootSequenceIndex = 1;
+ # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
+ #
+ msg1_SubcarrierSpacing = 1,
+# restrictedSetConfig
+# 0=unrestricted, 1=restricted type A, 2=restricted type B
+ restrictedSetConfig = 0,
+
+ msg3_DeltaPreamble = 1;
+ p0_NominalWithGrant =-90;
+
+# pucch-ConfigCommon setup :
+# pucchGroupHopping
+# 0 = neither, 1= group hopping, 2=sequence hopping
+ pucchGroupHopping = 0;
+ hoppingId = 40;
+ p0_nominal = -90;
+
+ ssb_PositionsInBurst_Bitmap = 1;
+
+# ssb_periodicityServingCell
+# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
+ ssb_periodicityServingCell = 2;
+
+# dmrs_TypeA_position
+# 0 = pos2, 1 = pos3
+ dmrs_TypeA_Position = 0;
+
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ subcarrierSpacing = 1;
+
+
+ #tdd-UL-DL-ConfigurationCommon
+# subcarrierSpacing
+# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
+ referenceSubcarrierSpacing = 1;
+ # pattern1
+ # dl_UL_TransmissionPeriodicity
+ # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
+ dl_UL_TransmissionPeriodicity = 6;
+ nrofDownlinkSlots = 7;
+ nrofDownlinkSymbols = 6;
+ nrofUplinkSlots = 2;
+ nrofUplinkSymbols = 4;
+
+ ssPBCH_BlockPower = -25;
+ }
+
+ );
+
+
+ # ------- SCTP definitions
+ SCTP :
+ {
+ # Number of streams to use in input/output
+ SCTP_INSTREAMS = 2;
+ SCTP_OUTSTREAMS = 2;
+ };
+
+
+ ////////// AMF parameters:
+ amf_ip_address = ({ ipv4 = "192.168.70.132"; });
+
+
+ NETWORK_INTERFACES :
+ {
+ GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129/24";
+ GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129/24";
+ GNB_PORT_FOR_S1U = 2152; # Spec 2152
+ };
+
+ }
+);
+
+MACRLCs = (
+{
+ num_cc = 1;
+ tr_s_preference = "local_L1";
+ tr_n_preference = "local_RRC";
+ pusch_TargetSNRx10 = 150;
+ pucch_TargetSNRx10 = 200;
+}
+);
+
+L1s = (
+{
+ num_cc = 1;
+ tr_n_preference = "local_mac";
+ prach_dtx_threshold = 120;
+ pucch0_dtx_threshold = 100;
+ ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
+}
+);
+
+RUs = (
+{
+ local_rf = "yes"
+ nb_tx = 1
+ nb_rx = 1
+ att_tx = 12;
+ att_rx = 12;
+ bands = [78];
+ max_pdschReferenceSignalPower = -27;
+ max_rxgain = 114;
+ eNB_instances = [0];
+ clock_src = "internal";
+}
+);
+
+
+rfsimulator :
+{
+ serveraddr = "server";
+ serverport = 4043;
+ options = (); #("saviq"); or/and "chanmod"
+ modelname = "AWGN";
+ IQfile = "/tmp/rfsimulator.iqs";
+};
+
+security = {
+ # preferred ciphering algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nea0, nea1, nea2, nea3
+ ciphering_algorithms = ( "nea0" );
+
+ # preferred integrity algorithms
+ # the first one of the list that an UE supports in chosen
+ # valid values: nia0, nia1, nia2, nia3
+ integrity_algorithms = ( "nia2", "nia0" );
+
+ # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter
+ # what 'ciphering_algorithms' configures; same thing for 'drb_integrity'
+ drb_ciphering = "yes";
+ drb_integrity = "no";
+};
+
+log_config :
+{
+ global_log_level ="info";
+ hw_log_level ="info";
+ phy_log_level ="info";
+ mac_log_level ="info";
+ rlc_log_level ="info";
+ pdcp_log_level ="info";
+ rrc_log_level ="info";
+ ngap_log_level ="debug";
+ f1ap_log_level ="debug";
+};
+
+e2_agent = {
+ near_ric_ip_addr = "127.0.0.1";
+ #sm_dir = "/path/where/the/SMs/are/located/"
+ sm_dir = "/usr/local/lib/flexric/"
+};
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/neighbour-config-rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/neighbour-config-rfsim.conf
new file mode 100644
index 0000000000..cff9d2b293
--- /dev/null
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/neighbour-config-rfsim.conf
@@ -0,0 +1,78 @@
+############################################################
+# gNB-to-gNB neighbour list + measurement configuration #
+# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) #
+############################################################
+
+neighbour_list = (
+ ##########################################################
+ # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 12345678L) #
+ ##########################################################
+ {
+ nr_cellid = 12345678L; # Serving cell of gNB 0xe00
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xb00;
+ nr_cellid = 720898; # Cell served by gNB 0xb00
+ physical_cellId = 1;
+ absoluteFrequencySSB= 621312;
+ subcarrierSpacing = 1; # 30 kHz
+ band = 78;
+ plmn = { mcc = 001; mnc = 01; mnc_length = 2 };
+ tracking_area_code = 1;
+ }
+ );
+ },
+
+ ##########################################################
+ # Entry USED BY gNB_ID = 0xb00 (nr_cellid = 720898) #
+ ##########################################################
+ {
+ nr_cellid = 720898; # Serving cell of gNB 0xb00
+ neighbour_cell_configuration = (
+ {
+ gNB_ID = 0xe00;
+ nr_cellid = 12345678L; # Cell served by gNB 0xe00
+ physical_cellId = 0;
+ absoluteFrequencySSB= 641280;
+ subcarrierSpacing = 1; # 30 kHz
+ band = 78;
+ plmn = { mcc = 001; mnc = 01; mnc_length = 2 };
+ tracking_area_code = 1;
+ }
+ );
+ }
+);
+
+############################################################
+# Common NR measurement-event configuration #
+############################################################
+
+nr_measurement_configuration = {
+ Periodical = {
+ enable = 1;
+ includeBeamMeasurements = 1;
+ maxNrofRS_IndexesToReport = 4;
+ };
+
+ A2 = {
+ enable = 1;
+ threshold = 60;
+ timeToTrigger = 1;
+ };
+
+ A3 = (
+ {
+ cell_id = 720898; # neighbour of gNB 0xe00
+ offset = 10;
+ hysteresis = 0;
+ timeToTrigger = 1;
+ },
+ {
+ cell_id = 12345678; # neighbour of gNB 0xb00
+ offset = 5;
+ hysteresis = 1;
+ timeToTrigger = 2;
+ }
+ );
+};
+