From 14a5cf7342e4302c07bd35e4a8cda6cc0656106d Mon Sep 17 00:00:00 2001 From: dragon Date: Wed, 8 Jul 2026 19:10:55 +0300 Subject: [PATCH] test: implement gNB python integration tests - create tests/integration/ folder with a Mock RadioHub harness in Python - add QDataStream and Protobuf serializers in Python to decode/encode network packets - implement parameterized end-to-end test cases covering: Registration, SIB1 broadcasts, RACH, RRC Setup, NAS, User Plane Relay, Handover, and Inactivity Timeouts - add pure-Python Protobuf fallback to ensure compatibility with older system protoc binaries - add description how to run unit tests and gNB integration test in README.md - update check_before_push.sh to automatically execute the Python integration tests - update ci.yml to install Python dependencies and run the Pytest integration suite in the GitHub Actions pipeline Close #74 --- .github/workflows/ci.yml | 9 + README.md | 8 +- check_before_push.sh | 6 + tests/integration/codec.py | 347 ++++++++++ tests/integration/config.yaml | 44 ++ tests/integration/conftest.py | 194 ++++++ tests/integration/ran_messages_pb2.py | 924 ++++++++++++++++++++++++++ tests/integration/requirements.txt | 3 + tests/integration/test_gnb.py | 217 ++++++ 9 files changed, 1751 insertions(+), 1 deletion(-) create mode 100644 tests/integration/codec.py create mode 100644 tests/integration/config.yaml create mode 100644 tests/integration/conftest.py create mode 100644 tests/integration/ran_messages_pb2.py create mode 100644 tests/integration/requirements.txt create mode 100644 tests/integration/test_gnb.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cefa5f2..6e94037 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,3 +67,12 @@ jobs: run: | cd build ctest --output-on-failure + + - name: Install Python Dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install pytest pyyaml protobuf + + - name: Run Python Integration Tests + run: | + PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python python3 -m pytest tests/integration/test_gnb.py -v diff --git a/README.md b/README.md index ed268a9..7e1b8ce 100755 --- a/README.md +++ b/README.md @@ -42,4 +42,10 @@ The simulator now supports two types os serializer (include Protobuf), configure Use case: Designed for realistic telecom network emulation, scalability testing, and simulating real-world distributed environments. -STATUS: in progress +## Testing + +1. Unit tests: bash ./check_before_push.sh +2. Integration test: python3 -m pytest tests/integration/test_gnb.py -v + +## Status + in progress diff --git a/check_before_push.sh b/check_before_push.sh index bd74bc2..d424f50 100644 --- a/check_before_push.sh +++ b/check_before_push.sh @@ -53,6 +53,12 @@ else ANY_FAILURE=1 fi +echo "" +echo "---------------------------------------" +echo "It's time for Python Integration tests:" +python3 -m pytest tests/integration/test_gnb.py -v +if [ $? -ne 0 ]; then ANY_FAILURE=1; fi + echo "---------------------------------------" if [ $ANY_FAILURE -ne 0 ]; then diff --git a/tests/integration/codec.py b/tests/integration/codec.py new file mode 100644 index 0000000..20cb863 --- /dev/null +++ b/tests/integration/codec.py @@ -0,0 +1,347 @@ +import struct +import os +import sys +import subprocess + +os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python' + +def compile_protobuf(): + """ + Compiles ran_messages.proto using protoc if it exists and imports it. + """ + current_dir = os.path.dirname(os.path.abspath(__file__)) + proto_dir = os.path.abspath(os.path.join(current_dir, '../../common/protobuf/proto')) + proto_file = os.path.join(proto_dir, 'ran_messages.proto') + + if not os.path.exists(proto_file): + raise FileNotFoundError(f"ran_messages.proto not found at {proto_file}") + + cmd = [ + 'protoc', + '--experimental_allow_proto3_optional', + f'-I={proto_dir}', + f'--python_out={current_dir}', + proto_file + ] + subprocess.run(cmd, check=True) + + if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + compile_protobuf() + import ran_messages_pb2 +except Exception as e: + print(f"Warning: Oops -> Failed to compile or import protobuf ran_messages_pb2: {e}") + ran_messages_pb2 = None + +def pack_qstring(s): + if s is None: + return struct.pack('>I', 0xffffffff) + encoded = s.encode('utf-16-be') + return struct.pack('>I', len(encoded)) + encoded + +def unpack_qstring(data, offset): + length, = struct.unpack_from('>I', data, offset) + offset += 4 + if length == 0xffffffff: + return None, offset + val = data[offset:offset+length].decode('utf-16-be') + offset += length + return val, offset + +# EntityTypes +UE_TYPE = 0 +GNB_TYPE = 1 +HUB_TYPE = 2 + +# SimMessageTypes +SIM_MSG_REGISTRATION = 0 +SIM_MSG_REGISTRATION_RESPONSE = 1 +SIM_MSG_DEREGISTRATION = 2 +SIM_MSG_DATA = 3 + +# ProtocolMsgTypes +PROTO_MSG_SIB1 = 0 +PROTO_MSG_RACH_PREAMBLE = 1 +PROTO_MSG_RAR = 2 +PROTO_MSG_RRC_SETUP = 3 +PROTO_MSG_RRC_SETUP_REQUEST = 4 +PROTO_MSG_RRC_SETUP_COMPLETE = 5 +PROTO_MSG_RRC_RELEASE = 6 +PROTO_MSG_REGISTRATION_REQUEST = 7 +PROTO_MSG_REGISTRATION_ACCEPT = 8 +PROTO_MSG_DEREGISTRATION_REQUEST = 9 +PROTO_MSG_SERVICE_REQUEST = 10 +PROTO_MSG_PAGING = 11 +PROTO_MSG_MEASUREMENT_REPORT = 12 +PROTO_MSG_RRC_RECONFIGURATION = 13 +PROTO_MSG_RRC_RECONFIGURATION_COMPLETE = 14 +PROTO_MSG_USER_PLANE_DATA = 15 + +class SimPacket: + def __init__(self, src_id, node_type, dst_id, msg_type, pos_x=0.0, pos_y=0.0, payload=b''): + self.src_id = src_id + self.node_type = node_type + self.dst_id = dst_id + self.msg_type = msg_type + self.pos_x = pos_x + self.pos_y = pos_y + self.payload = payload + + def pack(self): + # Remember header format: >IBIBdd (Big-Endian byte order. struct size: 26 bytes) + header = struct.pack('>IBIBdd', self.src_id, self.node_type, self.dst_id, self.msg_type, self.pos_x, self.pos_y) + return header + self.payload + + @classmethod + def unpack(cls, data): + if len(data) < 26: + raise ValueError("Packet too short") + src_id, node_type, dst_id, msg_type, pos_x, pos_y = struct.unpack_from('>IBIBdd', data, 0) + payload = data[26:] + return cls(src_id, node_type, dst_id, msg_type, pos_x, pos_y, payload) + +class QDataStreamCodec: + @staticmethod + def serialize_registration_payload(radius): + return struct.pack('>d', radius) + + @staticmethod + def deserialize_hub_registration_response(payload): + status, = struct.unpack('>B', payload) + return {'status': status} + + @staticmethod + def deserialize_sib1(payload): + cell_id, tac, q_rx, barred, reserved, reselection = struct.unpack_from('>Ihh???', payload, 0) + offset = 11 + + # RachConfigCommon + total_p, trans_max, win, target_p, ramping_step = struct.unpack_from('>BBHbB', payload, offset) + offset += 6 + + # SiSchedulingInfo + win_len, tag, sibs_size = struct.unpack_from('>BBI', payload, offset) + offset += 6 + sibs = [] + for _ in range(sibs_size): + sib_type, periodicity = struct.unpack_from('>BI', payload, offset) + sibs.append({'sib_type': sib_type, 'periodicity_ms': periodicity}) + offset += 5 + + # PLMN list + plmns_size, = struct.unpack_from('>B', payload, offset) + offset += 1 + plmns = [] + for _ in range(plmns_size): + mcc, mnc = struct.unpack_from('>II', payload, offset) + plmns.append({'mcc': mcc, 'mnc': mnc}) + offset += 8 + + return { + 'cell_identity': cell_id, + 'tac': tac, + 'qRx_lev_min': q_rx, + 'cell_barred': barred, + 'reserved_for_operator_use': reserved, + 'intra_freq_reselection': reselection, + 'rach_config': { + 'total_number_of_RA_preambles': total_p, + 'preamble_trans_max': trans_max, + 'ra_response_window_ms': win, + 'preamble_received_target_power': target_p, + 'power_ramping_step': ramping_step + }, + 'si_scheduling': { + 'si_window_length_ms': win_len, + 'system_info_value_tag': tag, + 'scheduled_sibs': sibs + }, + 'plmn_identity_info_list': plmns + } + + @staticmethod + def serialize_rach_preamble(ra_rnti): + return struct.pack('>H', ra_rnti) + + @staticmethod + def deserialize_rar(payload): + ra_rnti, temp_c_rnti, timing_advance = struct.unpack('>HHH', payload) + return {'ra_rnti': ra_rnti, 'temp_c_rnti': temp_c_rnti, 'timing_advance': timing_advance} + + @staticmethod + def serialize_rrc_setup_request(ue_identity, cause): + return struct.pack('>QB', ue_identity, cause) + + @staticmethod + def deserialize_rrc_setup(payload): + received_identity, config_status = struct.unpack('>QB', payload) + return {'received_identity': received_identity, 'config_status': config_status} + + @staticmethod + def serialize_rrc_setup_complete(mcc, mnc): + return struct.pack('>II', mcc, mnc) + + @staticmethod + def serialize_registration_request(ue_id, ue_cap): + return struct.pack('>I', ue_id) + pack_qstring(ue_cap) + + @staticmethod + def deserialize_registration_answer(payload): + status, = struct.unpack_from('>B', payload, 0) + offset = 1 + reject_reason = None + if status != 1 and len(payload) > 1: + reject_reason, _ = unpack_qstring(payload, offset) + return {'status': status, 'reject_reason': reject_reason} + + @staticmethod + def serialize_chat_message(receiver_ue_id, sender_ue_id, text): + return struct.pack('>II', receiver_ue_id, sender_ue_id) + pack_qstring(text) + + @staticmethod + def deserialize_chat_message(payload): + receiver, sender = struct.unpack_from('>II', payload, 0) + text, _ = unpack_qstring(payload, 8) + return {'receiver_ue_id': receiver, 'sender_ue_id': sender, 'text': text} + + @staticmethod + def serialize_measurement_report(reported_gnb_id, rsrp): + return struct.pack('>Id', reported_gnb_id, rsrp) + + @staticmethod + def deserialize_rrc_reconfiguration(payload): + target_gnb_id, = struct.unpack('>I', payload) + return {'target_gnb_id': target_gnb_id} + + @staticmethod + def deserialize_rrc_release(payload): + cause, = struct.unpack('>B', payload) + return {'cause': cause} + +class ProtobufCodec: + @staticmethod + def serialize_registration_payload(radius): + msg = ran_messages_pb2.HubRegistrationPayload() + msg.radius = radius + return msg.SerializeToString() + + @staticmethod + def deserialize_hub_registration_response(payload): + msg = ran_messages_pb2.HubRegistrationResponse() + msg.ParseFromString(payload) + return {'status': msg.status} + + @staticmethod + def deserialize_sib1(payload): + msg = ran_messages_pb2.SIB1Info() + msg.ParseFromString(payload) + sibs = [] + for s in msg.si_scheduling.scheduled_sibs: + sibs.append({'sib_type': s.sib_type, 'periodicity_ms': s.periodicity_ms}) + + plmns = [] + for p in msg.plmn_identity_info_list: + plmns.append({'mcc': p.mcc, 'mnc': p.mnc}) + + return { + 'cell_identity': msg.cell_identity, + 'tac': msg.tac, + 'qRx_lev_min': msg.qRx_lev_min, + 'cell_barred': msg.cell_barred, + 'reserved_for_operator_use': msg.reserved_for_operator_use, + 'intra_freq_reselection': msg.intra_freq_reselection, + 'rach_config': { + 'total_number_of_RA_preambles': msg.rach_config.total_number_of_RA_preambles, + 'preamble_trans_max': msg.rach_config.preamble_trans_max, + 'ra_response_window_ms': msg.rach_config.ra_response_window_ms, + 'preamble_received_target_power': -100, # Not present in proto + 'power_ramping_step': 2 # Not present in proto + }, + 'si_scheduling': { + 'si_window_length_ms': msg.si_scheduling.si_window_length_ms, + 'system_info_value_tag': msg.si_scheduling.system_info_value_tag, + 'scheduled_sibs': sibs + }, + 'plmn_identity_info_list': plmns + } + + @staticmethod + def serialize_rach_preamble(ra_rnti): + msg = ran_messages_pb2.RachPreambleInfo() + msg.ra_rnti = ra_rnti + return msg.SerializeToString() + + @staticmethod + def deserialize_rar(payload): + msg = ran_messages_pb2.RarInfo() + msg.ParseFromString(payload) + return {'ra_rnti': msg.ra_rnti, 'temp_c_rnti': msg.temp_c_rnti, 'timing_advance': msg.timing_advance} + + @staticmethod + def serialize_rrc_setup_request(ue_identity, cause): + msg = ran_messages_pb2.RrcSetupRequest() + msg.ue_identity = ue_identity + msg.cause = cause + return msg.SerializeToString() + + @staticmethod + def deserialize_rrc_setup(payload): + msg = ran_messages_pb2.RrcSetupInfo() + msg.ParseFromString(payload) + return {'received_identity': msg.received_identity, 'config_status': msg.config_status} + + @staticmethod + def serialize_rrc_setup_complete(mcc, mnc): + msg = ran_messages_pb2.RrcSetupCompleteInfo() + msg.plmn.mcc = mcc + msg.plmn.mnc = mnc + return msg.SerializeToString() + + @staticmethod + def serialize_registration_request(ue_id, ue_cap): + msg = ran_messages_pb2.RegistrationRequestInfo() + msg.ue_id = ue_id + msg.ue_cap = ue_cap + return msg.SerializeToString() + + @staticmethod + def deserialize_registration_answer(payload): + msg = ran_messages_pb2.RegistrationAnswerInfo() + msg.ParseFromString(payload) + reject = msg.reject_reason if msg.HasField('reject_reason') else None + return {'status': msg.status, 'reject_reason': reject} + + @staticmethod + def serialize_chat_message(receiver_ue_id, sender_ue_id, text): + msg = ran_messages_pb2.ChatMessageInfo() + msg.receiver_ue_id = receiver_ue_id + msg.sender_ue_id = sender_ue_id + msg.text = text + return msg.SerializeToString() + + @staticmethod + def deserialize_chat_message(payload): + msg = ran_messages_pb2.ChatMessageInfo() + msg.ParseFromString(payload) + return {'receiver_ue_id': msg.receiver_ue_id, 'sender_ue_id': msg.sender_ue_id, 'text': msg.text} + + @staticmethod + def serialize_measurement_report(reported_gnb_id, rsrp): + msg = ran_messages_pb2.MeasurementReportInfo() + msg.reported_gnb_id = reported_gnb_id + msg.rsrp = rsrp + return msg.SerializeToString() + + @staticmethod + def deserialize_rrc_reconfiguration(payload): + msg = ran_messages_pb2.RrcReconfigurationInfo() + msg.ParseFromString(payload) + return {'target_gnb_id': msg.target_gnb_id} + + @staticmethod + def deserialize_rrc_release(payload): + msg = ran_messages_pb2.RrcReconfigurationInfo() + msg.ParseFromString(payload) + return {'cause': msg.target_gnb_id} diff --git a/tests/integration/config.yaml b/tests/integration/config.yaml new file mode 100644 index 0000000..07e8132 --- /dev/null +++ b/tests/integration/config.yaml @@ -0,0 +1,44 @@ +hub_settings: + id: 0 + port: 6000 + broadcast_id: 4294967295 + virtual_position: [0, 0] + address: "127.0.0.1" + +paths: + build_dir: "../../build_test" + +gnb_settings: + radius: 1200 + node_settings: + cell: + tracking_area_code: 100 + radio: + radio_frame_duration: 10 + tx_power_db: 43.0 + +ue_settings: + node_settings: + cell: + tracking_area_code: 100 + radio: + radio_frame_duration: 10 + tx_power_db: 5.0 + +simulation: + deploy_mode: 1 # Distributed Mode for integration testing + serializer_type: 0 # 0 - QDataStream, 1 - Protobuf + gnb_id_start: 101 + ue_id_start: 501 + gnb_count: 1 + ue_count: 2 + +positions: + gnb_positions_list: + - id: 101 + pos: [ 0, 0 ] + ue_positions_list: + - id: 501 + pos: [ 100, 100 ] + - id: 502 + pos: [ 200, 200 ] diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..93bd75d --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,194 @@ +import os +import tempfile +import yaml +import socket +import subprocess +import time +import pytest + +from codec import ( + SimPacket, + QDataStreamCodec, + ProtobufCodec, + GNB_TYPE, + UE_TYPE, + HUB_TYPE, + SIM_MSG_REGISTRATION, + SIM_MSG_REGISTRATION_RESPONSE, + SIM_MSG_DATA +) + +class GnbTestHarness: + def __init__(self, serializer_type): + self.serializer_type = serializer_type # 0 for QDataStream, 1 for Protobuf + self.codec = QDataStreamCodec if serializer_type == 0 else ProtobufCodec + self.sock = None + self.proc = None + self.gnb_addr = None # ip and port + self.temp_config_path = None + self.gnb_id = 101 + self.hub_id = 0 + + def setup(self): + # 1. Read base integration config + current_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(current_dir, 'config.yaml') + with open(config_path, 'r') as f: + config_data = yaml.safe_load(f) + + # Update serializer type + config_data['simulation']['serializer_type'] = self.serializer_type + + # 2. Write to temp config file + fd, self.temp_config_path = tempfile.mkstemp(suffix='.yaml', prefix='gnb_test_config_') + os.close(fd) + with open(self.temp_config_path, 'w') as f: + yaml.safe_dump(config_data, f) + + # 3. Bind UDP socket to RadioHub port (6000) + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if hasattr(socket, 'SO_REUSEPORT'): + try: + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except Exception: + pass + self.sock.bind(('127.0.0.1', 6000)) + self.sock.settimeout(3.0) + + # Let's drain any leftover packets in the OS buffer + self.sock.setblocking(False) + while True: + try: + self.sock.recvfrom(65536) + except Exception: + break + self.sock.setblocking(True) + + # 4. Launch gnb_app subprocess + gnb_bin = os.path.abspath(os.path.join(current_dir, '../../build_test/gnb/gnb_app')) + if not os.path.exists(gnb_bin): + gnb_bin = os.path.abspath(os.path.join(current_dir, '../../build/gnb/gnb_app')) + if not os.path.exists(gnb_bin): + raise FileNotFoundError("gnb_app binary not found in build_test or build directories") + self.proc = subprocess.Popen( + [gnb_bin, '-i', str(self.gnb_id), '-c', self.temp_config_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + # 5. Wait for Registration packet from gNB + try: + raw_data, addr = self.sock.recvfrom(4096) + self.gnb_addr = addr + packet = SimPacket.unpack(raw_data) + + assert packet.src_id == self.gnb_id + assert packet.node_type == GNB_TYPE + assert packet.dst_id == self.hub_id + assert packet.msg_type == SIM_MSG_REGISTRATION + + # Respond with Registration Response (ACCEPTED = 1) + reg_resp_payload = self.codec.serialize_registration_payload(1200.0) # Dummy radius/status representation + # Wait, C++ BaseEntity expects deserialized status from registration response + # Let's serialize the HubRegistrationResponse status=1 + if self.serializer_type == 1: + # Protobuf Registration response status + reg_resp_payload = self.codec.serialize_registration_payload(1.0) # Actually we have status=1 + import ran_messages_pb2 + proto_resp = ran_messages_pb2.HubRegistrationResponse() + proto_resp.status = 1 + reg_resp_payload = proto_resp.SerializeToString() + else: + import struct + reg_resp_payload = struct.pack('>B', 1) + + resp_packet = SimPacket( + src_id = self.hub_id, + node_type = HUB_TYPE, + dst_id = self.gnb_id, + msg_type = SIM_MSG_REGISTRATION_RESPONSE, + payload = reg_resp_payload + ) + self.sock.sendto(resp_packet.pack(), self.gnb_addr) + + except socket.timeout: + self.cleanup() + raise TimeoutError("gNB registration timed out") + + def send_proto_message(self, ue_id, proto_msg_type, payload): + """ + Sends a simulated protocol message "from the UE" to the gNB. + """ + # Prefix the protocol message type byte + full_payload = bytes([proto_msg_type]) + payload + packet = SimPacket( + src_id = ue_id, + node_type = UE_TYPE, + dst_id = self.gnb_id, + msg_type=SIM_MSG_DATA, + payload=full_payload + ) + self.sock.sendto(packet.pack(), self.gnb_addr) + + def recv_packet(self, timeout=2.0): + """ + Let's receive a SimPacket from the socket. + """ + self.sock.settimeout(timeout) + raw_data, _ = self.sock.recvfrom(4096) + return SimPacket.unpack(raw_data) + + def recv_proto_message(self, expected_dst_id=None, timeout=2.0): + """ + Now we receive a protocol message from the gNB, returning + (target_id, proto_type, payload). + Filters out broadcast packets if expected_dst_id is specified. + """ + #import time + start = time.time() + while time.time() - start < timeout: + try: + packet = self.recv_packet(timeout=max(0.1, timeout - (time.time() - start))) + if packet.msg_type == SIM_MSG_DATA: + proto_type = packet.payload[0] + payload = packet.payload[1:] + if expected_dst_id is None or packet.dst_id == expected_dst_id: + return packet.dst_id, proto_type, payload + except socket.timeout: + break + raise TimeoutError(f"Timed out waiting for message targeting {expected_dst_id}") + + def cleanup(self): + if self.proc: + self.proc.terminate() + try: + self.proc.wait(timeout=1.0) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc = None + if self.sock: + self.sock.close() + self.sock = None + if self.temp_config_path and os.path.exists(self.temp_config_path): + os.remove(self.temp_config_path) + +@pytest.fixture(params=[0, 1]) +def gnb_harness(request): + """ + Pytest fixture parameterized to run tests under QDataStream (0) and Protobuf (1) modes. + """ + # Skip Protobuf tests if protobuf package is not installed in the environment + # import ran_messages_pb2 checks if compilation succeeded. + try: + import ran_messages_pb2 + except ImportError: + ran_messages_pb2 = None + if request.param == 1 and ran_messages_pb2 is None: + pytest.skip("Oops: protobuf Python package is not installed." + "We should skip Protobuf tests") + harness = GnbTestHarness(request.param) + harness.setup() + yield harness + harness.cleanup() diff --git a/tests/integration/ran_messages_pb2.py b/tests/integration/ran_messages_pb2.py new file mode 100644 index 0000000..e4b7ea4 --- /dev/null +++ b/tests/integration/ran_messages_pb2.py @@ -0,0 +1,924 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: ran_messages.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='ran_messages.proto', + package='ran.protocol', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x12ran_messages.proto\x12\x0cran.protocol\"(\n\x0cPlmnIdentity\x12\x0b\n\x03mcc\x18\x01 \x01(\r\x12\x0b\n\x03mnc\x18\x02 \x01(\r\"5\n\x0fRrcSetupRequest\x12\x13\n\x0bue_identity\x18\x01 \x01(\x04\x12\r\n\x05\x63\x61use\x18\x02 \x01(\r\"#\n\x10RachPreambleInfo\x12\x0f\n\x07ra_rnti\x18\x01 \x01(\r\"G\n\x07RarInfo\x12\x0f\n\x07ra_rnti\x18\x01 \x01(\r\x12\x13\n\x0btemp_c_rnti\x18\x02 \x01(\r\x12\x16\n\x0etiming_advance\x18\x03 \x01(\r\"@\n\x0cRrcSetupInfo\x12\x19\n\x11received_identity\x18\x01 \x01(\x04\x12\x15\n\rconfig_status\x18\x02 \x01(\r\"@\n\x14RrcSetupCompleteInfo\x12(\n\x04plmn\x18\x01 \x01(\x0b\x32\x1a.ran.protocol.PlmnIdentity\"8\n\x17RegistrationRequestInfo\x12\r\n\x05ue_id\x18\x01 \x01(\r\x12\x0e\n\x06ue_cap\x18\x02 \x01(\t\"V\n\x16RegistrationAnswerInfo\x12\x0e\n\x06status\x18\x01 \x01(\r\x12\x1a\n\rreject_reason\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_reject_reason\">\n\x15MeasurementReportInfo\x12\x17\n\x0freported_gnb_id\x18\x01 \x01(\r\x12\x0c\n\x04rsrp\x18\x02 \x01(\x01\"/\n\x16RrcReconfigurationInfo\x12\x15\n\rtarget_gnb_id\x18\x01 \x01(\r\"M\n\x0f\x43hatMessageInfo\x12\x16\n\x0ereceiver_ue_id\x18\x01 \x01(\r\x12\x14\n\x0csender_ue_id\x18\x02 \x01(\r\x12\x0c\n\x04text\x18\x03 \x01(\t\"s\n\x10RachConfigCommon\x12$\n\x1ctotal_number_of_RA_preambles\x18\x01 \x01(\r\x12\x1a\n\x12preamble_trans_max\x18\x02 \x01(\r\x12\x1d\n\x15ra_response_window_ms\x18\x03 \x01(\r\"6\n\nSibMapping\x12\x10\n\x08sib_type\x18\x01 \x01(\r\x12\x16\n\x0eperiodicity_ms\x18\x02 \x01(\r\"\x80\x01\n\x10SiSchedulingInfo\x12\x1b\n\x13si_window_length_ms\x18\x01 \x01(\r\x12\x1d\n\x15system_info_value_tag\x18\x02 \x01(\r\x12\x30\n\x0escheduled_sibs\x18\x03 \x03(\x0b\x32\x18.ran.protocol.SibMapping\"\xc4\x02\n\x08SIB1Info\x12\x15\n\rcell_identity\x18\x01 \x01(\r\x12\x0b\n\x03tac\x18\x02 \x01(\r\x12\x13\n\x0bqRx_lev_min\x18\x03 \x01(\x05\x12\x13\n\x0b\x63\x65ll_barred\x18\x04 \x01(\x08\x12!\n\x19reserved_for_operator_use\x18\x05 \x01(\x08\x12\x1e\n\x16intra_freq_reselection\x18\x06 \x01(\x08\x12\x33\n\x0brach_config\x18\x07 \x01(\x0b\x32\x1e.ran.protocol.RachConfigCommon\x12\x35\n\rsi_scheduling\x18\x08 \x01(\x0b\x32\x1e.ran.protocol.SiSchedulingInfo\x12;\n\x17plmn_identity_info_list\x18\t \x03(\x0b\x32\x1a.ran.protocol.PlmnIdentity\"\x1e\n\x0cHandoverInfo\x12\x0e\n\x06gnb_id\x18\x01 \x01(\r\"(\n\x16HubRegistrationPayload\x12\x0e\n\x06radius\x18\x01 \x01(\x01\")\n\x17HubRegistrationResponse\x12\x0e\n\x06status\x18\x01 \x01(\rb\x06proto3' +) + + + + +_PLMNIDENTITY = _descriptor.Descriptor( + name='PlmnIdentity', + full_name='ran.protocol.PlmnIdentity', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='mcc', full_name='ran.protocol.PlmnIdentity.mcc', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='mnc', full_name='ran.protocol.PlmnIdentity.mnc', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=36, + serialized_end=76, +) + + +_RRCSETUPREQUEST = _descriptor.Descriptor( + name='RrcSetupRequest', + full_name='ran.protocol.RrcSetupRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='ue_identity', full_name='ran.protocol.RrcSetupRequest.ue_identity', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cause', full_name='ran.protocol.RrcSetupRequest.cause', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=78, + serialized_end=131, +) + + +_RACHPREAMBLEINFO = _descriptor.Descriptor( + name='RachPreambleInfo', + full_name='ran.protocol.RachPreambleInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='ra_rnti', full_name='ran.protocol.RachPreambleInfo.ra_rnti', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=133, + serialized_end=168, +) + + +_RARINFO = _descriptor.Descriptor( + name='RarInfo', + full_name='ran.protocol.RarInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='ra_rnti', full_name='ran.protocol.RarInfo.ra_rnti', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='temp_c_rnti', full_name='ran.protocol.RarInfo.temp_c_rnti', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='timing_advance', full_name='ran.protocol.RarInfo.timing_advance', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=170, + serialized_end=241, +) + + +_RRCSETUPINFO = _descriptor.Descriptor( + name='RrcSetupInfo', + full_name='ran.protocol.RrcSetupInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='received_identity', full_name='ran.protocol.RrcSetupInfo.received_identity', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='config_status', full_name='ran.protocol.RrcSetupInfo.config_status', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=243, + serialized_end=307, +) + + +_RRCSETUPCOMPLETEINFO = _descriptor.Descriptor( + name='RrcSetupCompleteInfo', + full_name='ran.protocol.RrcSetupCompleteInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='plmn', full_name='ran.protocol.RrcSetupCompleteInfo.plmn', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=309, + serialized_end=373, +) + + +_REGISTRATIONREQUESTINFO = _descriptor.Descriptor( + name='RegistrationRequestInfo', + full_name='ran.protocol.RegistrationRequestInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='ue_id', full_name='ran.protocol.RegistrationRequestInfo.ue_id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='ue_cap', full_name='ran.protocol.RegistrationRequestInfo.ue_cap', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=375, + serialized_end=431, +) + + +_REGISTRATIONANSWERINFO = _descriptor.Descriptor( + name='RegistrationAnswerInfo', + full_name='ran.protocol.RegistrationAnswerInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='ran.protocol.RegistrationAnswerInfo.status', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reject_reason', full_name='ran.protocol.RegistrationAnswerInfo.reject_reason', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='_reject_reason', full_name='ran.protocol.RegistrationAnswerInfo._reject_reason', + index=0, containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[]), + ], + serialized_start=433, + serialized_end=519, +) + + +_MEASUREMENTREPORTINFO = _descriptor.Descriptor( + name='MeasurementReportInfo', + full_name='ran.protocol.MeasurementReportInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='reported_gnb_id', full_name='ran.protocol.MeasurementReportInfo.reported_gnb_id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='rsrp', full_name='ran.protocol.MeasurementReportInfo.rsrp', index=1, + number=2, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=521, + serialized_end=583, +) + + +_RRCRECONFIGURATIONINFO = _descriptor.Descriptor( + name='RrcReconfigurationInfo', + full_name='ran.protocol.RrcReconfigurationInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='target_gnb_id', full_name='ran.protocol.RrcReconfigurationInfo.target_gnb_id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=585, + serialized_end=632, +) + + +_CHATMESSAGEINFO = _descriptor.Descriptor( + name='ChatMessageInfo', + full_name='ran.protocol.ChatMessageInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='receiver_ue_id', full_name='ran.protocol.ChatMessageInfo.receiver_ue_id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sender_ue_id', full_name='ran.protocol.ChatMessageInfo.sender_ue_id', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='text', full_name='ran.protocol.ChatMessageInfo.text', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=634, + serialized_end=711, +) + + +_RACHCONFIGCOMMON = _descriptor.Descriptor( + name='RachConfigCommon', + full_name='ran.protocol.RachConfigCommon', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='total_number_of_RA_preambles', full_name='ran.protocol.RachConfigCommon.total_number_of_RA_preambles', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='preamble_trans_max', full_name='ran.protocol.RachConfigCommon.preamble_trans_max', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='ra_response_window_ms', full_name='ran.protocol.RachConfigCommon.ra_response_window_ms', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=713, + serialized_end=828, +) + + +_SIBMAPPING = _descriptor.Descriptor( + name='SibMapping', + full_name='ran.protocol.SibMapping', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='sib_type', full_name='ran.protocol.SibMapping.sib_type', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='periodicity_ms', full_name='ran.protocol.SibMapping.periodicity_ms', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=830, + serialized_end=884, +) + + +_SISCHEDULINGINFO = _descriptor.Descriptor( + name='SiSchedulingInfo', + full_name='ran.protocol.SiSchedulingInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='si_window_length_ms', full_name='ran.protocol.SiSchedulingInfo.si_window_length_ms', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='system_info_value_tag', full_name='ran.protocol.SiSchedulingInfo.system_info_value_tag', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='scheduled_sibs', full_name='ran.protocol.SiSchedulingInfo.scheduled_sibs', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=887, + serialized_end=1015, +) + + +_SIB1INFO = _descriptor.Descriptor( + name='SIB1Info', + full_name='ran.protocol.SIB1Info', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='cell_identity', full_name='ran.protocol.SIB1Info.cell_identity', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='tac', full_name='ran.protocol.SIB1Info.tac', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='qRx_lev_min', full_name='ran.protocol.SIB1Info.qRx_lev_min', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cell_barred', full_name='ran.protocol.SIB1Info.cell_barred', index=3, + number=4, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='reserved_for_operator_use', full_name='ran.protocol.SIB1Info.reserved_for_operator_use', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='intra_freq_reselection', full_name='ran.protocol.SIB1Info.intra_freq_reselection', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='rach_config', full_name='ran.protocol.SIB1Info.rach_config', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='si_scheduling', full_name='ran.protocol.SIB1Info.si_scheduling', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='plmn_identity_info_list', full_name='ran.protocol.SIB1Info.plmn_identity_info_list', index=8, + number=9, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1018, + serialized_end=1342, +) + + +_HANDOVERINFO = _descriptor.Descriptor( + name='HandoverInfo', + full_name='ran.protocol.HandoverInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='gnb_id', full_name='ran.protocol.HandoverInfo.gnb_id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1344, + serialized_end=1374, +) + + +_HUBREGISTRATIONPAYLOAD = _descriptor.Descriptor( + name='HubRegistrationPayload', + full_name='ran.protocol.HubRegistrationPayload', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='radius', full_name='ran.protocol.HubRegistrationPayload.radius', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1376, + serialized_end=1416, +) + + +_HUBREGISTRATIONRESPONSE = _descriptor.Descriptor( + name='HubRegistrationResponse', + full_name='ran.protocol.HubRegistrationResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='ran.protocol.HubRegistrationResponse.status', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1418, + serialized_end=1459, +) + +_RRCSETUPCOMPLETEINFO.fields_by_name['plmn'].message_type = _PLMNIDENTITY +_REGISTRATIONANSWERINFO.oneofs_by_name['_reject_reason'].fields.append( + _REGISTRATIONANSWERINFO.fields_by_name['reject_reason']) +_REGISTRATIONANSWERINFO.fields_by_name['reject_reason'].containing_oneof = _REGISTRATIONANSWERINFO.oneofs_by_name['_reject_reason'] +_SISCHEDULINGINFO.fields_by_name['scheduled_sibs'].message_type = _SIBMAPPING +_SIB1INFO.fields_by_name['rach_config'].message_type = _RACHCONFIGCOMMON +_SIB1INFO.fields_by_name['si_scheduling'].message_type = _SISCHEDULINGINFO +_SIB1INFO.fields_by_name['plmn_identity_info_list'].message_type = _PLMNIDENTITY +DESCRIPTOR.message_types_by_name['PlmnIdentity'] = _PLMNIDENTITY +DESCRIPTOR.message_types_by_name['RrcSetupRequest'] = _RRCSETUPREQUEST +DESCRIPTOR.message_types_by_name['RachPreambleInfo'] = _RACHPREAMBLEINFO +DESCRIPTOR.message_types_by_name['RarInfo'] = _RARINFO +DESCRIPTOR.message_types_by_name['RrcSetupInfo'] = _RRCSETUPINFO +DESCRIPTOR.message_types_by_name['RrcSetupCompleteInfo'] = _RRCSETUPCOMPLETEINFO +DESCRIPTOR.message_types_by_name['RegistrationRequestInfo'] = _REGISTRATIONREQUESTINFO +DESCRIPTOR.message_types_by_name['RegistrationAnswerInfo'] = _REGISTRATIONANSWERINFO +DESCRIPTOR.message_types_by_name['MeasurementReportInfo'] = _MEASUREMENTREPORTINFO +DESCRIPTOR.message_types_by_name['RrcReconfigurationInfo'] = _RRCRECONFIGURATIONINFO +DESCRIPTOR.message_types_by_name['ChatMessageInfo'] = _CHATMESSAGEINFO +DESCRIPTOR.message_types_by_name['RachConfigCommon'] = _RACHCONFIGCOMMON +DESCRIPTOR.message_types_by_name['SibMapping'] = _SIBMAPPING +DESCRIPTOR.message_types_by_name['SiSchedulingInfo'] = _SISCHEDULINGINFO +DESCRIPTOR.message_types_by_name['SIB1Info'] = _SIB1INFO +DESCRIPTOR.message_types_by_name['HandoverInfo'] = _HANDOVERINFO +DESCRIPTOR.message_types_by_name['HubRegistrationPayload'] = _HUBREGISTRATIONPAYLOAD +DESCRIPTOR.message_types_by_name['HubRegistrationResponse'] = _HUBREGISTRATIONRESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +PlmnIdentity = _reflection.GeneratedProtocolMessageType('PlmnIdentity', (_message.Message,), { + 'DESCRIPTOR' : _PLMNIDENTITY, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.PlmnIdentity) + }) +_sym_db.RegisterMessage(PlmnIdentity) + +RrcSetupRequest = _reflection.GeneratedProtocolMessageType('RrcSetupRequest', (_message.Message,), { + 'DESCRIPTOR' : _RRCSETUPREQUEST, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RrcSetupRequest) + }) +_sym_db.RegisterMessage(RrcSetupRequest) + +RachPreambleInfo = _reflection.GeneratedProtocolMessageType('RachPreambleInfo', (_message.Message,), { + 'DESCRIPTOR' : _RACHPREAMBLEINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RachPreambleInfo) + }) +_sym_db.RegisterMessage(RachPreambleInfo) + +RarInfo = _reflection.GeneratedProtocolMessageType('RarInfo', (_message.Message,), { + 'DESCRIPTOR' : _RARINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RarInfo) + }) +_sym_db.RegisterMessage(RarInfo) + +RrcSetupInfo = _reflection.GeneratedProtocolMessageType('RrcSetupInfo', (_message.Message,), { + 'DESCRIPTOR' : _RRCSETUPINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RrcSetupInfo) + }) +_sym_db.RegisterMessage(RrcSetupInfo) + +RrcSetupCompleteInfo = _reflection.GeneratedProtocolMessageType('RrcSetupCompleteInfo', (_message.Message,), { + 'DESCRIPTOR' : _RRCSETUPCOMPLETEINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RrcSetupCompleteInfo) + }) +_sym_db.RegisterMessage(RrcSetupCompleteInfo) + +RegistrationRequestInfo = _reflection.GeneratedProtocolMessageType('RegistrationRequestInfo', (_message.Message,), { + 'DESCRIPTOR' : _REGISTRATIONREQUESTINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RegistrationRequestInfo) + }) +_sym_db.RegisterMessage(RegistrationRequestInfo) + +RegistrationAnswerInfo = _reflection.GeneratedProtocolMessageType('RegistrationAnswerInfo', (_message.Message,), { + 'DESCRIPTOR' : _REGISTRATIONANSWERINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RegistrationAnswerInfo) + }) +_sym_db.RegisterMessage(RegistrationAnswerInfo) + +MeasurementReportInfo = _reflection.GeneratedProtocolMessageType('MeasurementReportInfo', (_message.Message,), { + 'DESCRIPTOR' : _MEASUREMENTREPORTINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.MeasurementReportInfo) + }) +_sym_db.RegisterMessage(MeasurementReportInfo) + +RrcReconfigurationInfo = _reflection.GeneratedProtocolMessageType('RrcReconfigurationInfo', (_message.Message,), { + 'DESCRIPTOR' : _RRCRECONFIGURATIONINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RrcReconfigurationInfo) + }) +_sym_db.RegisterMessage(RrcReconfigurationInfo) + +ChatMessageInfo = _reflection.GeneratedProtocolMessageType('ChatMessageInfo', (_message.Message,), { + 'DESCRIPTOR' : _CHATMESSAGEINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.ChatMessageInfo) + }) +_sym_db.RegisterMessage(ChatMessageInfo) + +RachConfigCommon = _reflection.GeneratedProtocolMessageType('RachConfigCommon', (_message.Message,), { + 'DESCRIPTOR' : _RACHCONFIGCOMMON, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.RachConfigCommon) + }) +_sym_db.RegisterMessage(RachConfigCommon) + +SibMapping = _reflection.GeneratedProtocolMessageType('SibMapping', (_message.Message,), { + 'DESCRIPTOR' : _SIBMAPPING, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.SibMapping) + }) +_sym_db.RegisterMessage(SibMapping) + +SiSchedulingInfo = _reflection.GeneratedProtocolMessageType('SiSchedulingInfo', (_message.Message,), { + 'DESCRIPTOR' : _SISCHEDULINGINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.SiSchedulingInfo) + }) +_sym_db.RegisterMessage(SiSchedulingInfo) + +SIB1Info = _reflection.GeneratedProtocolMessageType('SIB1Info', (_message.Message,), { + 'DESCRIPTOR' : _SIB1INFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.SIB1Info) + }) +_sym_db.RegisterMessage(SIB1Info) + +HandoverInfo = _reflection.GeneratedProtocolMessageType('HandoverInfo', (_message.Message,), { + 'DESCRIPTOR' : _HANDOVERINFO, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.HandoverInfo) + }) +_sym_db.RegisterMessage(HandoverInfo) + +HubRegistrationPayload = _reflection.GeneratedProtocolMessageType('HubRegistrationPayload', (_message.Message,), { + 'DESCRIPTOR' : _HUBREGISTRATIONPAYLOAD, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.HubRegistrationPayload) + }) +_sym_db.RegisterMessage(HubRegistrationPayload) + +HubRegistrationResponse = _reflection.GeneratedProtocolMessageType('HubRegistrationResponse', (_message.Message,), { + 'DESCRIPTOR' : _HUBREGISTRATIONRESPONSE, + '__module__' : 'ran_messages_pb2' + # @@protoc_insertion_point(class_scope:ran.protocol.HubRegistrationResponse) + }) +_sym_db.RegisterMessage(HubRegistrationResponse) + + +# @@protoc_insertion_point(module_scope) diff --git a/tests/integration/requirements.txt b/tests/integration/requirements.txt new file mode 100644 index 0000000..dd83455 --- /dev/null +++ b/tests/integration/requirements.txt @@ -0,0 +1,3 @@ +pytest>=7.0.0 +pyyaml>=6.0 +protobuf>=3.12.0 diff --git a/tests/integration/test_gnb.py b/tests/integration/test_gnb.py new file mode 100644 index 0000000..dd0d5a9 --- /dev/null +++ b/tests/integration/test_gnb.py @@ -0,0 +1,217 @@ +import pytest +import time +import socket +from codec import ( + PROTO_MSG_SIB1, + PROTO_MSG_RACH_PREAMBLE, + PROTO_MSG_RAR, + PROTO_MSG_RRC_SETUP_REQUEST, + PROTO_MSG_RRC_SETUP, + PROTO_MSG_RRC_SETUP_COMPLETE, + PROTO_MSG_REGISTRATION_REQUEST, + PROTO_MSG_USER_PLANE_DATA, + PROTO_MSG_MEASUREMENT_REPORT, + PROTO_MSG_RRC_RECONFIGURATION, + PROTO_MSG_RRC_RELEASE +) + +## +# @file test_gnb.py +# @brief Integration tests for the 5G RAN gNB protocol stack and signaling flows. +# @details Validates Cell Broadcast, RACH Procedure, RRC state transitions, NAS registration, User Plane data routing, Handover triggers, and Inactivity timers. +# + +## +# @brief Verifies that the gNB starts up and successfully registers at the RadioHub. +# @param gnb_harness The parameterized test harness fixture. +# @return None +# @details This test checks that the gNB sends a SIM_MSG_REGISTRATION packet to the RadioHub +# and transitions into an operational state upon receiving a registration confirmation. +# +def test_gnb_registration(gnb_harness): + assert gnb_harness.gnb_addr is not None + + +## +# @brief Verifies the periodic broadcasting of System Information Block Type 1 (SIB1). +# @param gnb_harness The parameterized test harness fixture. +# @return None +# @details SIB1 should be sent to the broadcast address containing cell parameters like TAC, Cell ID, and PLMN lists. +# +def test_sib1_broadcast(gnb_harness): + # Wait for SIB1 broadcast + dst_id, proto_type, payload = gnb_harness.recv_proto_message(expected_dst_id=4294967295, timeout=3.0) + assert dst_id == 4294967295 # Broadcast ID + assert proto_type == PROTO_MSG_SIB1 + + sib1 = gnb_harness.codec.deserialize_sib1(payload) + assert (sib1['cell_identity'] >> 8) == gnb_harness.gnb_id + assert sib1['tac'] == 100 + assert len(sib1['plmn_identity_info_list']) == 0 + + +## +# @brief Helper function to perform RACH and RRC connection setup for a UE. +# @param harness The parameterized test harness. +# @param ue_id Unique identifier of the User Equipment. +# @param ue_identity IMSI or temporary random identity of the UE. +# @return The assigned C-RNTI of the UE. +# +def establish_rrc_connection(harness, ue_id, ue_identity): + # Step 1: RACH Preamble (Msg1) + preamble = harness.codec.serialize_rach_preamble(ra_rnti=5) + harness.send_proto_message(ue_id, PROTO_MSG_RACH_PREAMBLE, preamble) + + # Step 2: Random Access Response (Msg2) + dst_id, proto_type, payload = harness.recv_proto_message(expected_dst_id=ue_id, timeout=2.0) + assert dst_id == ue_id + assert proto_type == PROTO_MSG_RAR + rar = harness.codec.deserialize_rar(payload) + assert rar['ra_rnti'] == 5 + crnti = rar['temp_c_rnti'] + + # Step 3: RRC Connection Setup Request (Msg3) + # RrcEstablishmentCause::MO_SIGNALLING = 3 + req = harness.codec.serialize_rrc_setup_request(ue_identity=ue_identity, cause=3) + harness.send_proto_message(ue_id, PROTO_MSG_RRC_SETUP_REQUEST, req) + + # Step 4: RRC Setup (Msg4) + dst_id, proto_type, payload = harness.recv_proto_message(expected_dst_id=ue_id, timeout=2.0) + assert dst_id == ue_id + assert proto_type == PROTO_MSG_RRC_SETUP + setup = harness.codec.deserialize_rrc_setup(payload) + assert setup['received_identity'] == ue_identity + assert setup['config_status'] == 1 # Success + + # Step 5: RRC Setup Complete (Msg5) + complete = harness.codec.serialize_rrc_setup_complete(mcc=255, mnc=1) + harness.send_proto_message(ue_id, PROTO_MSG_RRC_SETUP_COMPLETE, complete) + + return crnti + + +## +# @brief Tests Random Access Channel (RACH) procedure and RRC connection setup. +# @param gnb_harness The parameterized test harness fixture. +# @return None +# @details UE performs random access preamble transmission, decodes the timing advance and C-RNTI from Msg2, +# requests configuration from gNB, and completes the setup. +# +def test_rach_and_rrc_setup(gnb_harness): + establish_rrc_connection(gnb_harness, ue_id=777, ue_identity=987654321) + + +## +# @brief Tests NAS registration request handling. +# @param gnb_harness The parameterized test harness fixture. +# @return None +# @details Once RRC connected, the UE sends a Registration Request (containing capabilities). +# The gNB responds with a Registration Answer indicating Acceptance. +# +def test_nas_registration(gnb_harness): + establish_rrc_connection(gnb_harness, ue_id=777, ue_identity=987654321) + + # Send Registration Request + reg_req = gnb_harness.codec.serialize_registration_request(ue_id=777, ue_cap="Model-X-MIMO4x4") + gnb_harness.send_proto_message(777, PROTO_MSG_REGISTRATION_REQUEST, reg_req) + + # Receive Registration Response (wrapped in RrcSetup) + dst_id, proto_type, payload = gnb_harness.recv_proto_message(expected_dst_id=777, timeout=2.0) + assert dst_id == 777 + assert proto_type == PROTO_MSG_RRC_SETUP + ans = gnb_harness.codec.deserialize_registration_answer(payload) + assert ans['status'] == 1 # Accepted + + +## +# @brief Tests User Plane message routing between two connected UEs. +# @param gnb_harness The parameterized test harness fixture. +# @return None +# @details Registers two UEs (UE A and UE B) at the gNB, then routes a chat message from UE A to UE B. +# +def test_user_plane_relay(gnb_harness): + # Establish connection for UE A (777) + establish_rrc_connection(gnb_harness, ue_id=777, ue_identity=111111111) + + # Establish connection for UE B (888) + establish_rrc_connection(gnb_harness, ue_id=888, ue_identity=222222222) + + # Complete NAS registration for both to trigger full attachment + reg_req_a = gnb_harness.codec.serialize_registration_request(ue_id=777, ue_cap="UE-A") + gnb_harness.send_proto_message(777, PROTO_MSG_REGISTRATION_REQUEST, reg_req_a) + harness_payload = gnb_harness.recv_proto_message(expected_dst_id=777, timeout=2.0) + + reg_req_b = gnb_harness.codec.serialize_registration_request(ue_id=888, ue_cap="UE-B") + gnb_harness.send_proto_message(888, PROTO_MSG_REGISTRATION_REQUEST, reg_req_b) + harness_payload = gnb_harness.recv_proto_message(expected_dst_id=888, timeout=2.0) + + # Send User Plane Data from A to B + chat_payload = gnb_harness.codec.serialize_chat_message(receiver_ue_id=888, sender_ue_id=777, text="Hello UE B!") + gnb_harness.send_proto_message(777, PROTO_MSG_USER_PLANE_DATA, chat_payload) + + # Receive relayed message at UE B + dst_id, proto_type, payload = gnb_harness.recv_proto_message(expected_dst_id=888, timeout=2.0) + assert dst_id == 888 + assert proto_type == PROTO_MSG_USER_PLANE_DATA + chat = gnb_harness.codec.deserialize_chat_message(payload) + assert chat['sender_ue_id'] == 777 + assert chat['receiver_ue_id'] == 888 + assert chat['text'] == "Hello UE B!" + + +## +# @brief Tests handover trigger logic based on UE Measurement Reports. +# @param gnb_harness The parameterized test harness fixture. +# @return None +# @details UE reports low RSSI on serving cell and higher RSSI on target cell. +# gNB must trigger handover by issuing an RRC Reconfiguration command with target GNB ID. +# +def test_handover_trigger(gnb_harness): + establish_rrc_connection(gnb_harness, ue_id=777, ue_identity=987654321) + + # 1. Update serving cell RSSI + serv_report = gnb_harness.codec.serialize_measurement_report(reported_gnb_id=gnb_harness.gnb_id, rsrp=-90.0) + gnb_harness.send_proto_message(777, PROTO_MSG_MEASUREMENT_REPORT, serv_report) + + # Wait for serving update processing (ignoring broadcast packets if they arrive) + time.sleep(0.1) + + # 2. Send neighbor report triggering handover (neighbor RSRP = -80.0, serving was -90.0, diff = 10dB > 3dB hysteresis) + neigh_report = gnb_harness.codec.serialize_measurement_report(reported_gnb_id=102, rsrp=-80.0) + gnb_harness.send_proto_message(777, PROTO_MSG_MEASUREMENT_REPORT, neigh_report) + + # 3. Receive RRC Reconfiguration + dst_id, proto_type, payload = gnb_harness.recv_proto_message(expected_dst_id=777, timeout=2.0) + assert dst_id == 777 + assert proto_type == PROTO_MSG_RRC_RECONFIGURATION + reconfig = gnb_harness.codec.deserialize_rrc_reconfiguration(payload) + assert reconfig['target_gnb_id'] == 102 + + +## +# @brief Tests the inactivity release timer. +# @param gnb_harness The parameterized test harness fixture. +# @return None +# @details Once connected, if the UE sends no messages, the gNB should trigger RRC Release after 30 seconds. +# +@pytest.mark.slow +def test_inactivity_timeout(gnb_harness): + establish_rrc_connection(gnb_harness, ue_id=777, ue_identity=987654321) + + # Wait for the inactivity release (~30s + grace time) + # We poll to skip broadcast/SIB1 messages + start_time = time.time() + release_received = False + + while time.time() - start_time < 45.0: + try: + dst_id, proto_type, payload = gnb_harness.recv_proto_message(expected_dst_id=777, timeout=1.0) + if dst_id == 777 and proto_type == PROTO_MSG_RRC_RELEASE: + release = gnb_harness.codec.deserialize_rrc_release(payload) + assert release['cause'] == 1 # UserInactivity + release_received = True + break + except (socket.timeout, TimeoutError): + pass + + assert release_received, "RRC Release not received after inactivity timeout"