5G NR Protocol Stack Testing: Patterns and Tools

5G NR Protocol Stack Testing: Patterns and Tools

5G New Radio (NR) protocol stack testing is one of the most complex verification domains in software engineering. The stack spans seven layers (PHY through NAS), each defined by thousands of pages of 3GPP specifications, with conformance testing requiring both specialized hardware and sophisticated software test harnesses. This guide covers practical testing patterns across the stack, from open-source emulation environments to commercial test equipment.

5G NR Protocol Stack Overview

The 5G NR protocol stack for the radio interface (Uu interface between UE and gNB) consists of:

┌─────────────────────────────────┐
│  NAS (Non-Access Stratum)       │  Mobility, session management, auth
├─────────────────────────────────┤
│  RRC (Radio Resource Control)   │  Connection setup, handover, config
├─────────────────────────────────┤
│  SDAP (Service Data Adaptation) │  QoS flow to DRB mapping
├─────────────────────────────────┤
│  PDCP (Packet Data Conv. Proto) │  Header compression, ciphering, SN
├─────────────────────────────────┤
│  RLC (Radio Link Control)       │  Segmentation, retransmission (ARQ)
├─────────────────────────────────┤
│  MAC (Medium Access Control)    │  Scheduling, HARQ, multiplexing
├─────────────────────────────────┤
│  PHY (Physical Layer)           │  Modulation, coding, beam management
└─────────────────────────────────┘

Each layer has distinct test responsibilities. Testing the full stack requires understanding where each failure mode originates.

Conformance Testing vs Interoperability Testing

Conformance testing verifies that a single implementation (UE or gNB) complies with 3GPP specifications (primarily 3GPP TS 38.523 for UE conformance). Test cases are defined by 3GPP and executed using approved test systems.

Interoperability (IOT) testing verifies that two real implementations work together — a specific UE with a specific gNB, or a gNB with a specific 5GC. IOT testing reveals issues that conformance testing misses because conformance tests use a perfectly-behaved counterpart.

Dimension Conformance Interoperability
Counterpart Test system (golden reference) Real device
Scope Spec compliance End-to-end behavior
Tools Anritsu MT8000A, Keysight UXM Commercial devices + protocol analyzers
When Pre-GCF certification Network integration

Test Equipment Overview

Commercial Test Systems

Anritsu MT8000A (Radio Communication Test Station):

  • Supports 5G NR SA and NSA
  • Runs 3GPP TS 38.523 conformance test cases
  • Scripting via GPIB/LAN with SCPI commands
# SCPI control of Anritsu MT8000A via pyvisa
import pyvisa

rm = pyvisa.ResourceManager()
mt8000a = rm.open_resource("TCPIP0::192.168.1.10::inst0::INSTR")

# Configure 5G NR SA test cell
mt8000a.write("CALL:CELL:NRSA:FREQ:DL 3600MHZ")
mt8000a.write("CALL:CELL:NRSA:FREQ:UL 3600MHZ")
mt8000a.write("CALL:CELL:NRSA:BW 100MHZ")
mt8000a.write("CALL:CELL:NRSA:SCS 30KHZ")  # 30kHz subcarrier spacing

# Activate cell
mt8000a.write("CALL:CELL:ACT")

# Wait for UE registration
import time
for _ in range(30):
    status = mt8000a.query("CALL:UE:REG?")
    if "REG" in status:
        print("UE registered")
        break
    time.sleep(1)

# Run RRC connection test
mt8000a.write("CALL:TEST:RRC:CONN:RUN")
result = mt8000a.query("CALL:TEST:RRC:CONN:RESULT?")
print(f"RRC Connection test: {result}")

Open-Source: UERANSIM + Open5GS

For protocol testing without commercial hardware, UERANSIM (UE and RAN simulator) paired with Open5GS (5G Core) provides a complete 5G SA test environment.

# Install Open5GS (Ubuntu 22.04)
sudo apt install open5gs

# Configure AMF for test network
cat > /etc/open5gs/amf.yaml << 'EOF'
amf:
  sbi:
    server:
      - address: 127.0.0.5
        port: 7777
  ngap:
    server:
      - address: 127.0.0.5
  guami:
    - plmn_id:
        mcc: 999
        mnc: 70
      amf_id:
        region: 2
        set: 1
  tai:
    - plmn_id:
        mcc: 999
        mnc: 70
      tac: 1
  plmn_support:
    - plmn_id:
        mcc: 999
        mnc: 70
      s_nssai:
        - sst: 1
EOF

# Install UERANSIM
git clone https://github.com/aligungr/UERANSIM
cd UERANSIM && make

# Configure gNB
cat > config/open5gs-gnb.yaml << 'EOF'
mcc: '999'
mnc: '70'
nci: '0x000000010'
idLength: 32
tac: 1
linkIp: 127.0.0.1
ngapIp: 127.0.0.1
gtpIp: 127.0.0.1
amfConfigs:
  - address: 127.0.0.5
    port: 38412
slices:
  - sst: 1
EOF

# Start gNB
./build/nr-gnb -c config/open5gs-gnb.yaml &

# Start UE
./build/nr-ue -c config/open5gs-ue.yaml

Protocol Analyzer Testing with Wireshark

Wireshark includes 5G NR dissectors for all protocol layers. For automated testing, use tshark to extract and validate protocol fields.

# Capture 5G NR traffic on NGAP interface (N2 interface: AMF ↔ gNB)
tshark -i lo -f "sctp port 38412" \
       -w captures/ngap-$(date +%Y%m%d_%H%M%S).pcapng &

# After test execution, analyze RRC Setup Request messages
tshark -r captures/ngap-*.pcapng \
       -Y "ngap.procedureCode == 21" \  # InitialUEMessage
       -T fields \
       -e ngap.RAN_UE_NGAP_ID \
       -e ngap.NAS_PDU \
       -e frame.time_relative

# Verify RRC message sequence: Setup Request → Setup → Complete
python3 << 'EOF'
import subprocess
import json

def extract_rrc_messages(pcap_file):
    result = subprocess.run([
        "tshark", "-r", pcap_file,
        "-Y", "nr-rrc",
        "-T", "json",
        "-e", "nr_rrc.message_type",
        "-e", "frame.number",
        "-e", "frame.time_relative"
    ], capture_output=True, text=True)
    return json.loads(result.stdout)

def verify_rrc_setup_sequence(messages):
    """
    Expected sequence:
    1. RRCSetupRequest (UE → gNB)
    2. RRCSetup (gNB → UE) 
    3. RRCSetupComplete (UE → gNB)
    """
    types = [m["_source"]["layers"]["nr_rrc.message_type"][0] for m in messages]
    
    required_sequence = ["rrcSetupRequest", "rrcSetup", "rrcSetupComplete"]
    
    for req in required_sequence:
        if req not in types:
            return False, f"Missing message type: {req}"
    
    # Verify ordering
    indices = [types.index(t) for t in required_sequence]
    if indices != sorted(indices):
        return False, f"Messages out of order: {types}"
    
    return True, "RRC Setup sequence correct"

msgs = extract_rrc_messages("captures/latest.pcapng")
ok, msg = verify_rrc_setup_sequence(msgs)
print(msg)
EOF

RRC State Machine Testing

The UE RRC state machine (RRC_IDLE → RRC_INACTIVE → RRC_CONNECTED) is a primary source of interoperability issues. Automated testing must drive the UE through all state transitions.

import subprocess
import time
import re

class UERANSIMController:
    """Control UERANSIM UE via its CLI interface."""
    
    def __init__(self, ue_cli="./build/nr-cli"):
        self.cli = ue_cli
    
    def get_state(self, ue_id="imsi-999700000000001"):
        result = subprocess.run(
            [self.cli, ue_id, "--exec", "status"],
            capture_output=True, text=True
        )
        # Parse RRC state from output
        match = re.search(r"RRC State:\s+(\w+)", result.stdout)
        return match.group(1) if match else "UNKNOWN"
    
    def trigger_data(self, ue_id, duration_s=2):
        """Generate data to trigger IDLE→CONNECTED transition."""
        subprocess.Popen([
            self.cli, ue_id, "--exec",
            f"ping -c {duration_s * 10} 8.8.8.8"
        ])
    
    def deregister(self, ue_id):
        subprocess.run([self.cli, ue_id, "--exec", "deregister normal"])

def test_idle_to_connected_transition():
    ue = UERANSIMController()
    ue_id = "imsi-999700000000001"
    
    # Ensure starting state
    time.sleep(5)
    initial_state = ue.get_state(ue_id)
    assert initial_state == "RRC_IDLE", f"Expected RRC_IDLE, got {initial_state}"
    
    # Trigger data to cause transition
    ue.trigger_data(ue_id)
    
    # Verify transition to CONNECTED within 2 seconds
    deadline = time.monotonic() + 2.0
    connected = False
    while time.monotonic() < deadline:
        if ue.get_state(ue_id) == "RRC_CONNECTED":
            connected = True
            break
        time.sleep(0.1)
    
    assert connected, "UE did not transition to RRC_CONNECTED within 2 seconds"
    print("PASS: IDLE → CONNECTED transition within 2s")

def test_inactivity_timer_return_to_idle():
    """Verify UE returns to IDLE after inactivity timer expiry."""
    ue = UERANSIMController()
    ue_id = "imsi-999700000000001"
    
    # Ensure CONNECTED state
    ue.trigger_data(ue_id, duration_s=1)
    time.sleep(2)
    assert ue.get_state(ue_id) == "RRC_CONNECTED"
    
    # Stop traffic — wait for inactivity timer (typically 10-20s in test config)
    time.sleep(25)
    
    final_state = ue.get_state(ue_id)
    assert final_state in ("RRC_IDLE", "RRC_INACTIVE"), \
        f"UE should have returned to IDLE/INACTIVE, got {final_state}"
    print(f"PASS: Inactivity timer returned UE to {final_state}")

Common Failure Modes by Layer

PHY Layer

  • Beam management failures: gNB and UE disagree on optimal beam; manifests as poor RSRP despite proximity
  • Timing advance errors: UL synchronization lost; observable as PUSCH scheduling failures
  • SSB (Synchronization Signal Block) timing: UE fails to synchronize if SSB periodicity misconfigured

MAC Layer

  • HARQ round-trip timing: HARQ feedback timing must match configured HARQ process count; mismatches cause retransmission storms
  • BSR (Buffer Status Report) starvation: UE stops reporting buffer status; scheduler gives zero UL grants

RLC/PDCP Layer

  • SN (Sequence Number) wraparound: 12-bit SN wraps at 4096; implementations that don't handle wraparound lose data
  • Reordering timer misconfiguration: Packets out-of-order cause unnecessary retransmissions

RRC Layer

  • Measurement report triggering: A3 event threshold values misconfigured; UE either never reports (no handover) or floods with reports
  • RRC reconfiguration rejection: UE rejects reconfiguration due to capability mismatch; may not send RRCReconfigurationFailure correctly

Key Takeaways

  • Conformance testing and interoperability testing are complementary; passing conformance does not guarantee IOT success
  • UERANSIM + Open5GS provides a free, scriptable 5G SA test environment for protocol-level testing without hardware
  • Capture and analyze NGAP/RRC message sequences with Wireshark/tshark to verify correct state machine behavior
  • RRC state machine transitions (especially the inactivity timer path) are a common interoperability failure point
  • Layer-specific failure modes require layer-specific test strategies — PHY failures need RF instrumentation, while RRC failures are diagnosable from protocol captures alone

Read more

Start now free