NGAP interface architecture

How OCUDU implements both ends of the NGAP (N2) control-plane protocol — class ownership, procedure layout, per-procedure flows (interface management, UE context, PDU session, NAS transport, paging, mobility), and how NGAP integrates with CU-CP, E1AP, F1AP, and RRC. Reference: 3GPP TS 38.413.

Target audience: RAN engineers. Scope: the NGAP (N2) interface implementation in OCUDU — class ownership, procedure layout, per-procedure flows (interface mgmt, UE context, PDU session, NAS transport, paging, mobility), and how NGAP hands off into CU-CP / E1AP / F1AP / RRC. Reference: 3GPP TS 38.413. F1-U (NR-U user-plane) is data-plane and lives separately under lib/f1u/ — out of scope for this doc.


1. What NGAP Is in OCUDU

NGAP is the control-plane signaling protocol between the gNB and the 5GC AMF, carried over SCTP on the N2 reference point. In OCUDU, NGAP is a peer of CU-CP — not a subordinate — with its own state machine, ASN.1 codec layer, per-UE context store, and coroutine-based procedure framework. It sits in lib/ngap/ and is instantiated once per AMF association; a ngap_repository inside CU-CP manages the set.

Three design rules shape the module:

  1. One ngap_impl per AMF. A gNB can be connected to multiple AMFs (AMF pool per TS 23.501). Each AMF gets its own NGAP instance, its own TNL association, its own transaction managers, and its own served-GUAMI list. The CU-CP’s ngap_repository keys them by amf_index_t.
  2. Procedures are coroutines. Every request-response exchange (NG Setup, Initial Context Setup, HO Preparation, Path Switch, PDU Session Setup/Modify/Release, Context Release, Context Modification) is an async_task<R> under lib/ngap/procedures/. Non-transaction messages (Initial UE Message, DL/UL NAS Transport, Paging, Error Indication) are handled by direct dispatch in ngap_impl.
  3. Handoff through ngap_cu_cp_notifier. NGAP never reaches into CU-CP’s UE manager, RRC, F1AP, or E1AP directly. It calls a single notifier interface; the CU-CP-side ngap_cu_cp_adapter implements that interface and orchestrates the rest of the stack.

2. Module Ownership Graph

ngap_impl (lib/ngap/ngap_impl.h) is the top-level class. It holds:

  • ngap_context_t context — global per-AMF state: gnb_id, ran_node_name, served_guami_list (set on NG Setup Response), supported_tas, procedure timeouts, PDU session request timeout, AMF name.
  • ngap_ue_context_list ue_ctxt_list — UE context repository with three indices (by ue_index, ran_ue_id, amf_ue_id) and a sequential RAN-UE-NGAP-ID allocator.
  • ngap_transaction_manager ev_mng — global transaction event sources: ng_setup_outcome (Response/Failure), ng_reset_outcome (Ack).
  • ngap_connection_handler conn_handler — wraps the N2 SCTP gateway client.
  • tx_pdu_notifier_with_logging — outgoing PDU path; decorates the gateway notifier with PCAP/log.
  • ngap_cu_cp_notifier& cu_cp_notifier — the only escape hatch into CU-CP; a ngap_metrics_aggregator, timer_manager&, task_executor& ctrl_exec.

Per-UE statengap_ue_context:

struct ngap_ue_ids { ue_index_t ue_index; ran_ue_id_t ran_ue_id; amf_ue_id_t amf_ue_id; };
struct ngap_ue_context {
  ngap_ue_ids               ue_ids;
  ngap_cu_cp_ue_notifier*   ue;                            // back to CU-CP UE
  guami_t                   serving_guami;
  unique_timer              request_pdu_session_timer;      // TS 38.413 tng-ranue-ngap
  bool                      release_requested, release_scheduled;
  std::optional<ngap_core_network_assist_info_for_inactive> core_network_assist_info_for_inactive;
  byte_buffer               last_pdu_session_resource_modify_request;  // duplicate detect
  ngap_ue_transaction_manager ev_mng;                       // HO Prep / Path Switch / HO Cancel / DL RAN Status
  ngap_ue_logger            logger;
};

The per-UE transaction manager is what lets an async_task coroutine await a specific response (e.g. HandoverCommand, PathSwitchRequestAck) with a timeout — the AMF’s matching PDU sets the event source, the coroutine resumes.

Factory. create_ngap(cfg, cu_cp_notifier, n2_gateway_handler, timers, ctrl_exec) instantiates one ngap_impl. The ngap_repository in CU-CP calls it once per AMF entry in the gNB config.


3. Interface Management Procedures (TS 38.413 Section 8.7)

sequenceDiagram
    autonumber
    participant CUCP as cu_cp
    participant NGAP as ngap_impl
    participant AMF
    Note over CUCP,AMF: NG Setup (RAN-initiated, Section 8.7.1)
    CUCP->>NGAP: handle_ng_setup_request(max_retries)
    NGAP->>AMF: NGSetupRequest (gNB ID, TAs, PLMNs)
    alt Success
      AMF-->>NGAP: NGSetupResponse (served GUAMI, AMF name)
      NGAP->>NGAP: ctx.served_guami_list = ...
    else Failure w/ time_to_wait
      AMF-->>NGAP: NGSetupFailure (time_to_wait)
      NGAP->>NGAP: wait · retry (up to max_retries)
    end
    NGAP-->>CUCP: ng_setup_result

NG Setup (Section 8.7.1). procedures/ng_setup_procedure.{h,cpp}. Coroutine returning async_task<ngap_ng_setup_result>. Builds NGSetupRequest (global RAN node ID, supported TA list, PLMN/S-NSSAI lists), subscribes to ev_mng.ng_setup_outcome, sends, and awaits. On NGSetupFailure carrying a time_to_wait IE and retries remaining, it backs off and retries; otherwise returns failure. On success, the served-GUAMI list and AMF name are cached in ngap_context. This is the precondition for every other procedure on that N2 link.

NG Reset (Section 8.7.4). procedures/ng_reset_procedure.{h,cpp}. Coroutine returning async_task<void>. Supports both full reset and partial reset (UE list). Subscribes to ev_mng.ng_reset_outcome. On NGResetAcknowledge for a full reset, it cascades through ue_ctxt_list and tells CU-CP (via cu_cp_notifier) to release the affected UE contexts. A partial reset releases only the listed UEs.

Error Indication (Section 8.7.3). Asymmetric. Outgoing is a helper in ngap_error_indication_helper.h: send_error_indication(notifier, logger, [ran_ue_id], [amf_ue_id], [cause]). Can be UE-associated or non-UE-associated. Incoming is handled by ngap_impl::handle_error_indication() which logs the cause and does not cascade — Error Indication is informational in NGAP. A per-UE stored_error_indications map holds deferred indications that are attached to the next UEContextReleaseComplete.

AMF Configuration Update (Section 8.7.2), RAN Configuration Update, Overload Start/Stop (Section 8.7.5/6). Not currently implemented — these are optional for baseline interop. Overload gating, when implemented, will live on the CU-CP admission-control side rather than inside ngap_impl.


4. UE Context Management Procedures (TS 38.413 Section 8.3, Section 8.6)

Initial UE Message (Section 8.6.1). Handled directly by ngap_impl::handle_initial_ue_message(cu_cp_initial_ue_message). Triggered by CU-CP after the UE completes RRC Setup. Steps: allocate a fresh ran_ue_id_t via ue_ctxt_list.allocate_ran_ue_id(); create the ngap_ue_context; build InitialUEMessage with RAN-UE-ID, NAS PDU (from RRC), user location info (TAI + NR-CGI), GUAMI, establishment cause; start the request_pdu_session_timer (protects against AMF silence); send via tx_pdu_notifier. AMF-UE-NGAP-ID is not yet known here — it is bound later when AMF returns InitialContextSetupRequest.

Initial Context Setup (Section 8.3.1). procedures/ngap_initial_context_setup_procedure.{h,cpp}. AMF-initiated, request-response with failure option. On receipt:

  1. Extract AMF-UE-NGAP-ID; update ue_ctxt_list so the index by AMF-UE-ID becomes populated.
  2. Stop request_pdu_session_timer.
  3. Convert ASN.1 into ngap_init_context_setup_request (security context, UE AMBR, PDU session list, GUAMI).
  4. CORO_AWAIT_VALUE(cu_cp_notifier.on_new_initial_context_setup_request(...)) — this is the critical handoff: CU-CP runs RRC Security Mode Command → F1AP UE Context Setup → RRC Capability Transfer → (nested) PDU Session Resource Setup → RRC Reconfiguration.
  5. On success, send InitialContextSetupResponse (with PDU session admit list). On failure, send InitialContextSetupFailure with cause.

UE Context Release Request (RAN → AMF, Section 8.3.3). ngap_impl::handle_ue_context_release_request(). Triggered by CU-CP on RRC connection loss, inactivity, or internal error. Guards via release_requested flag to avoid duplicates. Encodes cause from ngap_cause_t (radio-network / transport / NAS / protocol / misc, per TS 38.413 Section 9.2.2.1). Scheduled on the per-UE executor, not inline, so the caller is not blocked.

UE Context Release Command / Complete (AMF → RAN, Section 8.3.2). procedures/ngap_ue_context_release_procedure.{h,cpp}. Coroutine. Sets release_scheduled, awaits cu_cp_notifier.on_new_ue_context_release_command(...) (which releases RRC, F1AP UE context, E1AP bearer context, and PDCP/SDAP/GTP-U resources), then sends UEContextReleaseComplete — optionally attaching any pending entries from stored_error_indications. Finally schedules on_ue_context_release_complete() which removes the ngap_ue_context.

UE Context Modification (Section 8.3.4). procedures/ngap_ue_context_modification_procedure.{h,cpp}. Coroutine. Validates UE-ID-pair consistency; persists new GUAMI, UE AMBR, and Core Network Assist Info for Inactive into the NGAP UE context; awaits CU-CP to apply bearer-level changes; responds with UEContextModificationResponse or Failure.

UE Radio Capability Info Indication (Section 8.14.1). Non-transaction RAN → AMF. ngap_impl::handle_tx_ue_radio_capability_info_indication_required() requests the radio capability RAT container from RRC via ue_notifier.on_ue_radio_access_cap_info_required() and wraps it in UERadioCapabilityInfoIndication. UE Radio Capability Check (Section 8.14.2) is not implemented; it is optional.


5. PDU Session Management (TS 38.413 Section 8.2)

These procedures are where NGAP reaches the deepest into the rest of the stack. NGAP itself only decodes/encodes the PDU and awaits CU-CP; CU-CP then fans out to F1AP, E1AP, and UPF.

sequenceDiagram
    autonumber
    participant AMF
    participant NGAP as ngap_impl
    participant CUCP as cu_cp
    participant E1 as E1AP / CU-UP
    participant F1 as F1AP / DU
    participant RRC
    AMF->>NGAP: PDUSessionResourceSetupRequest
    NGAP->>CUCP: on_new_pdu_session_resource_setup_request()
    par CU-CP orchestration
      CUCP->>E1: BearerContextSetup (DRB · QoS)
      E1-->>CUCP: BearerContextSetupResponse (CU-UP F1-U TEID)
      CUCP->>F1: UEContextModification (DRB add · CU-UP TEID)
      F1-->>CUCP: UEContextModificationResponse (DU F1-U TEID)
      CUCP->>RRC: RRCReconfiguration (radioBearerConfig)
      RRC-->>CUCP: RRCReconfigurationComplete
    end
    CUCP-->>NGAP: cu_cp_pdu_session_resource_setup_response
    NGAP->>AMF: PDUSessionResourceSetupResponse (admit / fail list)

PDU Session Resource Setup (Section 8.2.1). procedures/ngap_pdu_session_resource_setup_procedure.{h,cpp}. Coroutine. Stops the PDU-session request timer, validates the UE context (refuses if release_scheduled), extracts each setup item (session ID, QoS flow list, S-NSSAI, NAS PDU), stores UE AMBR if present, converts to cu_cp_pdu_session_resource_setup_request, and awaits CU-CP. The CU-CP side is the three-way dance shown above (E1AP → F1AP → RRC). Per-slice metrics are tallied under ngap_metrics_aggregator.

PDU Session Resource Modify (Section 8.2.2). procedures/ngap_pdu_session_resource_modify_procedure.{h,cpp}. Coroutine. Implements duplicate detection: the raw packed ASN.1 request is stored in ue_ctxt.last_pdu_session_resource_modify_request; a byte-exact duplicate returns an Error Indication rather than re-running the full procedure. On a fresh request, CU-CP runs E1AP Bearer Context Modification with QoS flow add/release/modify.

PDU Session Resource Release (Section 8.2.3). procedures/ngap_pdu_session_resource_release_procedure.{h,cpp}. Coroutine. Awaits CU-CP, which drives E1AP Bearer Context Release + F1AP UE Context Modification (DRB release) + GTP-U TEID teardown at CU-UP. Responds with the released-session list.

PDU Session Resource Notify (Section 8.2.5) and Modify Indication (Section 8.2.4 RAN-initiated) are not currently implemented; they are optional for a baseline gNB.


6. NAS Transport (TS 38.413 Section 8.6)

All three NAS transport paths are non-transaction messages, not procedures.

  • DL NAS Transport (Section 8.6.2). procedures/ngap_dl_nas_message_transfer_procedure.{h,cpp}. Despite being a “transfer” and not a request-response, it is wrapped in a small coroutine because it may trigger an outbound UERadioCapabilityInfoIndication if the AMF sets the capability-request IE. Awaits rrc_ue_notifier.on_new_pdu(nas_pdu) which delivers the NAS PDU down to the UE via SRB1/SRB2.
  • UL NAS Transport (Section 8.6.3). ngap_impl::handle_ul_nas_transport_message(). Direct dispatch: wrap the NAS PDU plus user location info in ULNASTransport, schedule TX on the per-UE executor.
  • NAS Non-Delivery Indication (Section 8.6.4). Not implemented.

7. Paging (TS 38.413 Section 8.5.1)

ngap_impl::handle_paging(paging_s&). Non-UE-associated message. Validated via validate_paging() in ngap_asn1_validators.h (must carry 5G-S-TMSI, at least one TAI, correct DRX IE), converted to cu_cp_paging_message, and forwarded to CU-CP’s paging handler. CU-CP fans out one F1AP Paging message per served cell that matches the TAI list; each DU then schedules the PDCCH P-RNTI transmission in its paging occasion.


8. Mobility (TS 38.413 Section 8.4)

NGAP participates only in the NG-based mobility paths. Xn-based handover lives in lib/xnap/ and never touches NGAP.

sequenceDiagram
    autonumber
    participant SRC as source gNB
    participant AMF
    participant TGT as target gNB
    SRC->>AMF: HandoverRequired (target cell · src-to-tgt container)
    AMF->>TGT: HandoverRequest (PDU sessions · security)
    TGT->>AMF: HandoverRequestAck (DL TNL · tgt-to-src container)
    AMF->>SRC: HandoverCommand (RRCReconfiguration inside)
    Note over SRC: RRC sends HO cmd over Uu
    Note over TGT: UE attaches · RACH · RRCReconfigComplete
    TGT->>AMF: HandoverNotify (new user location)

Handover Preparation (Section 8.4.1) — source side. procedures/ngap_handover_preparation_procedure.{h,cpp}. Coroutine returning async_task<ngap_handover_preparation_response>. Requests the source-to-target transparent container from RRC (carries measurements, K_gnb, security algorithms), builds HandoverRequired, subscribes to ue_ctxt.ev_mng.handover_preparation_outcome with tng_reloc_prep_ms timeout, sends, and awaits HandoverCommand or HandoverPreparationFailure. On success, extracts the target-to-source container (contains the RRC Handover Command to deliver over Uu). On timeout or failure, may trigger HandoverCancel.

Handover Resource Allocation (Section 8.4.2) — target side. procedures/ngap_handover_resource_allocation_procedure.{h,cpp}. Coroutine. Allocates a fresh ue_index via cu_cp_notifier.request_new_ue_index_allocation(), seeds the target-side security context, awaits CU-CP to run RRC setup and F1AP resource allocation on the target cell, then emits HandoverRequestAck with DL TNL info per PDU session — or HandoverFailure on rejection.

Handover Notify (Section 8.4.3). Non-transaction. Emitted by target gNB after RRCReconfigurationComplete from the UE, carrying the new user location info.

Path Switch Request (Section 8.4.4). procedures/ngap_path_switch_procedure.{h,cpp}. Used after an Xn-based handover or after an inactive-to-connected transition, to tell the AMF to update the UPF N3 path. Coroutine, UE-level transaction, ~5 s timeout.

Handover Cancel (Section 8.4.5). Integrated into the preparation procedure on timeout; no standalone class.


9. Warning / Trace / Location (Brief)

  • PWS Write/Replace/Kill Warning (Section 8.8) — not implemented.
  • Trace Start / Deactivate / Failure (Section 8.9) — not implemented.
  • Location Reporting Control / Report / Failure Indication (Section 8.10) — partially implemented in ngap_impl.cpp: handle_location_reporting_control_message(), handle_location_report_transmission(), handle_location_reporting_failure_indication_transmission(). Control message is forwarded to CU-CP’s location-reporting handler; the report/failure directions are built and dispatched on the UE executor.

10. AMF Connection Lifecycle (Brief)

ngap_connection_handler in lib/ngap/ngap_connection_handler.{h,cpp} wraps the N2 SCTP gateway for one AMF. Key members: amf_index, n2_connection_client& client_handler, ngap_message_handler& rx_pdu_handler (points back to ngap_impl), a connected_flag, and a manual_event_flag rx_path_disconnected used to notify coroutines of a link drop.

Bring-up: CU-CP calls connect_to_amf(); the gateway creates the SCTP association (SCTP PPID 60), the TX notifier becomes live, connected_flag is set, then handle_ng_setup_request() kicks off NG Setup. Teardown/loss: the gateway’s COMM_LOST event propagates into rx_path_disconnected; any in-flight coroutine awaiting a response times out. CU-CP retries the connect. Multi-AMF is just multiple ngap_impl instances inside ngap_repository; reselection and load balancing decisions (when a UE is first attached) are taken in CU-CP, not inside NGAP.


11. CU-CP ↔ NGAP Integration (Brief)

The contract is ngap_cu_cp_notifier (callable by NGAP) plus ngap_cu_cp_ue_notifier (per-UE handle returned by CU-CP). The CU-CP-side implementation lives in lib/cu_cp/adapters/ngap_adapters.h. Key callbacks:

  • on_new_ngap_ue(ue_index) — CU-CP creates a cu_cp_ue and returns a UE notifier handle.
  • on_new_initial_context_setup_request(...) — runs the initial_context_setup_routine described in Section 4.
  • on_new_pdu_session_resource_setup_request(...) / ..._modify_... / ..._release_... — orchestrate E1AP + F1AP + RRC.
  • on_new_ue_context_release_command(...) — releases RRC + F1AP + E1AP resources.
  • on_paging_message(...) — fans out F1AP paging per cell.
  • on_handover_request(...) / request_new_ue_index_allocation() / on_handover_request_received() — target-side handover setup.

Per-UE serialization: NGAP per-UE coroutines schedule themselves on the per-UE CU-CP task executor through ue_notifier.schedule_async_task(). This keeps the per-UE ordering invariant (same UE, same FIFO) across NGAP and CU-CP — no locks needed.


12. ASN.1 Layer (Brief)

The generated NGAP ASN.1 types live under asn1::ngap in lib/asn1/ngap/. The root PDU type is ngap_pdu_c, a CHOICE of init_msg_s, successful_outcome_s, unsuccessful_outcome_s. Every procedure has an ASN1_NGAP_ID_* integer code.

ngap_asn1_packer (in the CU-CP adapters) owns the SCTP edge. Inbound: handle_packed_pdu(byte_buffer) unpacks into ngap_pdu_c and dispatches to ngap_impl::handle_message(msg), which switches on PDU type and further on procedure code to call handle_initiating_message() / handle_successful_outcome() / handle_unsuccessful_outcome(). Outbound: the reverse — a logical message is packed and pushed to the SCTP gateway’s data handler.

Three helper files keep the procedure code clean: ngap_asn1_helpers.h (field-fill functions), ngap_asn1_converters.h (typed ↔ ASN.1 conversions including cause encoding), ngap_asn1_validators.h (IE presence/consistency checks before dispatch).


13. Procedure Framework

All NGAP procedures share the same shape: a small functor class with operator()(coro_context<async_task<R>>& ctx) that looks like

CORO_BEGIN(ctx);
transaction_sink.subscribe_to(event_source, timeout);
amf_notifier.on_new_message(request);           // send
CORO_AWAIT(transaction_sink);                    // suspend until response/timeout
if      (transaction_sink.successful())      { ... }
else if (transaction_sink.timeout_expired()) { ... }
else                                         { ... }   // explicit failure
CORO_RETURN(result);

The protocol_transaction_event_source<Success[, Failure]> is what the inbound PDU dispatcher sets when a matching SuccessfulOutcome / UnsuccessfulOutcome arrives. Global event sources sit in ngap_transaction_manager; per-UE event sources sit in ngap_ue_transaction_manager (HO Prep, HO Cancel, Path Switch, DL RAN Status Transfer).

Timers come in three scopes: context-level (procedure_timeout, request_pdu_session_timeout — seconds), procedure-level (NG Setup time_to_wait backoff; HO preparation tng_reloc_prep_timer), UE-level (the request_pdu_session_timer protecting against AMF silence after Initial UE Message).


14. Procedure Cross-Reference

TS 38.413 §ProcedureFileClass / HandlerPattern
8.7.1NG Setupprocedures/ng_setup_procedure.{h,cpp}ng_setup_procedureCoroutine, retry on time_to_wait
8.7.3Error Indicationngap_error_indication_helper.h + ngap_impl.cppsend_error_indication() / handle_error_indication()Inline out; log in
8.7.4NG Resetprocedures/ng_reset_procedure.{h,cpp}ng_reset_procedureCoroutine
8.6.1Initial UE Messagengap_impl.cpphandle_initial_ue_message()Direct dispatch
8.3.1Initial Context Setupprocedures/ngap_initial_context_setup_procedure.{h,cpp}ngap_initial_context_setup_procedureCoroutine, awaits CU-CP
8.3.3UE Context Release Requestngap_impl.cpphandle_ue_context_release_request()UE-executor async task
8.3.2UE Context Release Cmd/Cplprocedures/ngap_ue_context_release_procedure.{h,cpp}ngap_ue_context_release_procedureCoroutine
8.3.4UE Context Modificationprocedures/ngap_ue_context_modification_procedure.{h,cpp}ngap_ue_context_modification_procedureCoroutine
8.14.1UE Radio Cap Info Indngap_impl.cpphandle_tx_ue_radio_capability_info_indication_required()Direct + async tx
8.2.1PDU Session Setupprocedures/ngap_pdu_session_resource_setup_procedure.{h,cpp}ngap_pdu_session_resource_setup_procedureCoroutine, awaits CU-CP fan-out
8.2.2PDU Session Modifyprocedures/ngap_pdu_session_resource_modify_procedure.{h,cpp}ngap_pdu_session_resource_modify_procedureCoroutine, duplicate-detect
8.2.3PDU Session Releaseprocedures/ngap_pdu_session_resource_release_procedure.{h,cpp}ngap_pdu_session_resource_release_procedureCoroutine
8.6.2DL NAS Transportprocedures/ngap_dl_nas_message_transfer_procedure.{h,cpp}ngap_dl_nas_message_transfer_procedureCoroutine (may trigger UE Radio Cap)
8.6.3UL NAS Transportngap_impl.cpphandle_ul_nas_transport_message()UE-executor async task
8.5.1Pagingngap_impl.cpphandle_paging()Direct dispatch to paging_handler
8.4.1HO Preparationprocedures/ngap_handover_preparation_procedure.{h,cpp}ngap_handover_preparation_procedureCoroutine, UE-level txn
8.4.2HO Resource Allocationprocedures/ngap_handover_resource_allocation_procedure.{h,cpp}ngap_handover_resource_allocation_procedureCoroutine, new UE alloc
8.4.3HO Notifyngap_impl.cpphandle_inter_cu_ho_rrc_recfg_complete()Direct dispatch
8.4.4Path Switch Requestprocedures/ngap_path_switch_procedure.{h,cpp}ngap_path_switch_procedureCoroutine, UE-level txn
8.10Location Reportingngap_impl.cppvarious handlersDirect + async tx
8.7.2 / 8.7.5 / 8.7.6 / 8.8 / 8.9AMF/RAN Config Update, Overload, PWS, TraceNot implemented

Entry points to read first in this order: lib/ngap/ngap_impl.h, lib/ngap/ngap_impl.cpp, lib/ngap/ue_context/, then any single file under lib/ngap/procedures/ — they all share the shape described in Section 13.