NGAP - NG Application Protocol

Removed in version 1.6.0: pcapkit.protocols.application.ngap._PYCRATE, pcapkit.protocols.application.ngap._PDU_LOCK, pcapkit.protocols.application.ngap.load_pycrate, pcapkit.protocols.application.ngap.PDUKind, pcapkit.protocols.application.ngap.Criticality, pcapkit.protocols.application.ngap._convert and pcapkit.protocols.application.ngap._revert, with no alias: they are in this module, and pcapkit.protocols.application.ngap exports the protocol class alone (#2534).

pcapkit.protocols.application.ngap.protocol contains NGAP, which implements an extractor for the NG Application Protocol (NGAP) [*], as specified in 3GPP TS 38.413, and the enumerations its data models report with: PDUKind, Criticality, ProcedureCode and ProtocolIE.

NGAP is the control plane between a 5G RAN node (gNB or ng-eNB) and an AMF. It runs over SCTP and is named by the DATA chunk’s payload protocol identifier rather than by a port, so NGAP is registered on SCTP.__proto__ under PPID 60 (PayloadProtocolIdentifier_3GPP_NG_Application_Protocol) and PPID 66 (PayloadProtocolIdentifier_3GPP_NGAP_over_DTLS_over_SCTP); see pcapkit.foundation.registry.protocols.transport.register_sctp().

An SCTP DATA chunk that names NGAP carries exactly one NGAP-PDU, encoded in aligned PER (ALIGNED PACKED ENCODING RULES, APER). There is no header to read and no framing to resolve: the whole payload is the encoding, and none of its structure is visible until an ASN.1 decoder has run over it.

Decoding needs the optional pycrate dependency (pip install pypcapkit[NGAP]). Without it pcapkit still works, and an NGAP payload falls back to Raw as an unregistered PPID’s does: _import_next_layer(), which SCTP inherits, is wrapped in beholder().

class pcapkit.protocols.application.ngap.protocol.NGAP(file=None, length=None, **kwargs)[source]

Bases: Application[NGAP, NGAP]

This class implements NG Application Protocol.

Changed in version 1.6.0: procedure and criticality of make() take a member or an int code, resolved with the enumeration’s get; a name or a bool raises ProtocolError, where a name resolved to its member and a bool was taken as a code (#2594).

property name: Literal['NG Application Protocol']

Name of current protocol.

property length: int

Header length of current protocol.

NGAP prefixes its payload with nothing and carries no next layer, so the whole NGAP-PDU is the header and this is its length. That is not self.__length_hint__, which reports the four-octet prefix common to every PDU rather than this PDU’s size.

read(length=None, **kwargs)[source]

Read NG Application Protocol (NGAP).

Parameters:
  • length (int | None) – Length of packet data.

  • **kwargs (Any) – Arbitrary keyword arguments.

Return type:

NGAP

Returns:

Parsed packet data.

Raises:

ProtocolError – If pycrate is not installed, if the payload is not a well formed aligned PER NGAP-PDU, or if octets follow the NGAP-PDU. A whole octet left over is not part of it: an SCTP DATA chunk holds one whole message (RFC 9260 §1.3, §3.3.1), and APER pads to the octet (X.691 (02/2021) §11.1.4-5).

make(kind=PDUKind.INITIATING_MESSAGE, procedure=None, criticality=Criticality.reject, message=None, value=None, data=None, **kwargs)[source]

Make (construct) packet data.

Parameters:
  • kind (PDUKind | str) – Which NGAP-PDU alternative to construct.

  • procedure (ProcedureCode | int | None) – Procedure code.

  • criticality (Criticality | int) – Criticality of the procedure.

  • message (str | None) – Name of the message type, e.g. NGSetupRequest.

  • value (Any) – Message body, either as Sequence from a previous read() or as the plain Python value pycrate expects.

  • data (bytes | None) – Pre-encoded NGAP-PDU. When given, it is used verbatim and every other argument is ignored, which is also the only path that does not need pycrate.

  • **kwargs (Any) – Arbitrary keyword arguments.

Return type:

NGAP

Returns:

Constructed packet data.

Raises:

ProtocolError – If data is not given and either procedure or message is missing, if procedure (0..255) or criticality (two bits) is a name, a bool, or a member or code its field cannot carry, if pycrate is not installed, or if the arguments do not describe a message the specification can encode.

__length_hint__()[source]

Return an estimated length for the object.

Every NGAP-PDU opens with the same four octets under aligned PER – the CHOICE index, the procedure code, the criticality, and the first octet of the open type’s length determinant – so four is the fixed prefix NGAP has in place of a header. It is not a minimum PDU length: the smallest complete NGAP-PDU measured here, a message whose protocolIEs list is empty, is seven octets.

Return type:

Literal[4]

classmethod __index__()[source]

Numeral registry index of the protocol.

Return type:

int

Returns:

Numeral registry index of the protocol in IANA service names and port numbers: SCTP port 38412, the port of SCTP.ng_control as an int. SCTP.__proto__ dispatches by payload protocol identifier rather than by port, so this value keys no dispatch (The Registry Index).

Changed in version 1.6.0: Returns SCTP port 38412, where it raised IntError (#2627).

classmethod _make_data(data)[source]

Create key-value pairs from data for protocol construction.

Parameters:

data (NGAP) – protocol data

Return type:

dict[str, Any]

Returns:

Key-value pairs for protocol construction.

Decoder Dependency

pycrate is used rather than a PER codec of our own. It ships NGAP already compiled, at pycrate_asn1dir/NGAP.py, so the 3GPP ASN.1 source does not have to be vendored here and tracked across releases; and it is pure Python, with no compiled extension to build on any platform. Decoding costs 0.15 ms per PDU, the same order as pcapkit’s own per-packet cost, so the generic strategy below is not paying for the convenience.

Generic Conversion

The decoded value tree is mapped into Info objects structurally, by ASN.1 shape rather than by procedure:

ASN.1 / pycrate shape

pcapkit model

SEQUENCE / SET (a dict)

Sequence

SEQUENCE OF (a list)

list

CHOICE / open type

Choice

BIT STRING

BitString

INTEGER, OCTET STRING, ENUMERATED, BOOLEAN

kept as int, bytes, str, bool

That is a deliberate trade: every elementary procedure and protocol IE the decoder knows is converted, and a new 3GPP release needs no code change here; what is given up is per-IE typing, so an IE’s value keeps the specification’s own shape rather than a pcapkit-specific model. The PDU kind, procedure code, criticality, message type name and IE list are first-class fields on NGAP regardless.

Known Limitations

  • PPID 66 payloads are not decoded. NGAP over DTLS over SCTP wraps the NGAP-PDU in a DTLS record, and SCTP.__proto__ maps PPID 66 to NGAP rather than through DTLS, so the bytes reaching NGAP.read() are not an APER encoding. The PPID is registered so that it is named rather than anonymous; the payload itself degrades to Raw.

  • The specification version is that of the installed decoder, not this package’s. The IE and procedure enumerations were generated from NGAP_Constants of pycrate 0.8.1 (Release-18-era). A pycrate that carries a newer NGAP will decode values that ProcedureCode and ProtocolIE do not name. Such a value resolves to a non-registering member called Unassigned rather than failing or adding a member to either enumeration.

  • NGAP over a fragmented SCTP association is not reassembled. A DATA chunk is decoded on its own, so an NGAP-PDU split across chunks by SCTP fragmentation fails to decode rather than being reassembled first.

  • Private IEs (PrivateMessage) carry no schema. Their contents are vendor defined, so the generic conversion reports whatever ASN.1 shape the encoding declares and cannot name the fields.

Auxiliary Functions

pcapkit.protocols.application.ngap.protocol.load_pycrate()[source]

Load the optional pycrate NGAP-PDU object.

Return type:

Any | None

Returns:

pycrate_asn1dir.NGAP.NGAP_PDU_Descriptions.NGAP_PDU, the CHOICE over initiatingMessage / successfulOutcome / unsuccessfulOutcome that is the entry point of the compiled specification, or None when pycrate is not installed.

Notes

The import is attempted at most once and the outcome is cached. It is not free even when it succeeds – pycrate_asn1dir.NGAP is a 4.9 MB module – which is the other reason it happens here rather than at module import: neither import pcapkit nor the documentation build should pay for it.

pcapkit.protocols.application.ngap.protocol._convert(value)[source]

Convert a pycrate decoded value into pcapkit data models.

Parameters:

value (Any) – A node of the tree returned by NGAP_PDU.get_val().

Return type:

Any

Returns:

The same tree, with mappings as Sequence, CHOICE pairs as Choice, and BIT STRING pairs as BitString.

Notes

The 2-tuple shapes are told apart by their first element, which is unambiguous: pycrate spells a CHOICE as (name, value) with a str name and a BIT STRING as (bits, length) with two int. Any other tuple is passed through with its members converted, so an ASN.1 construct not listed above degrades to its own shape rather than being mangled into one of these.

pcapkit.protocols.application.ngap.protocol._revert(value)[source]

Convert pcapkit data models back into a pycrate value.

Parameters:

value (Any) – A node of a tree produced by _convert(), or the equivalent plain Python value.

Return type:

Any

Returns:

The same tree in the shape NGAP_PDU.set_val() expects.

Notes

Choice and BitString are tested before Info, since they are subclasses of it and would otherwise be reverted to mappings.

Auxiliary Data

class pcapkit.protocols.application.ngap.protocol.PDUKind(*values)[source]

Bases: EnumLookup, StrEnum

Which alternative of the NGAP-PDU CHOICE a PDU is.

The values are spelled as the ASN.1 identifiers, so that a name decoded by pycrate resolves by value.

Built on EnumLookup, the lookup contract shared by every non-registry enumeration (#877). The class defines neither get nor _missing_ of its own.

INITIATING_MESSAGE = 'initiatingMessage'

A procedure’s request, or a class 2 procedure’s only message.

SUCCESSFUL_OUTCOME = 'successfulOutcome'

A class 1 procedure’s successful response.

UNSUCCESSFUL_OUTCOME = 'unsuccessfulOutcome'

A class 1 procedure’s unsuccessful response.

class pcapkit.protocols.application.ngap.protocol.Criticality(*values)[source]

Bases: EnumLookup, aenum.IntEnum

[Criticality] What a receiver must do with an IE it does not understand.

Members are named for the ASN.1 identifiers rather than upper-cased, so that get() resolves a name decoded by pycrate through the standard member map. The values are the ENUMERATED indices, which is what goes on the wire.

Carries no get of its own: the inherited get() is used as is, so an unknown name is KeyError-shaped, exactly as E['nosuch'] is on a stdlib Enum (#923), and an unknown value raises ValueError.

A default is honoured only when it names a registered value: get('nosuch', Criticality.reject) answers reject, and get('nosuch', 99) raises. A member-valued default resolves through _value2member_map_ and never through the constructor, so on this closed ASN.1 ENUMERATED with no extension marker no path can mint a fourth value; see _missing_(), which refuses one outright.

reject = 0

Reject the whole message.

ignore = 1

Ignore the IE and carry on.

notify = 2

Ignore the IE, carry on, and report it.

classmethod _missing_(value)[source]

Lookup function used when value is not found.

Parameters:

value (int) – Value to get enum item.

Raises:

ValueError – Always. Criticality is an ENUMERATED with no extension marker, so a fourth value cannot be encoded and a lookup for one is a bug rather than a newer specification.

Return type:

NoReturn

pcapkit.protocols.application.ngap.protocol._PYCRATE: Any

Cached NGAP-PDU object; see load_pycrate(). NotImplemented means the import has not been attempted yet, None that it was attempted and pycrate is not installed, and anything else is the loaded object. “Not installed” is kept apart from “not attempted” so that a capture full of NGAP packets does not pay for a failing import on every frame.

pcapkit.protocols.application.ngap.protocol._PDU_LOCK

Guards the module-level NGAP-PDU object returned by load_pycrate(). That object is stateful: from_aper stores the decoded value on it and get_val hands back the decoder’s own containers rather than copies, so two decodes running concurrently through it would each see the other’s tree. The lock is held across the conversion, not merely across the decode, for that second reason.

Header Schemas

Removed in version 1.6.0: pcapkit.protocols.schema.application.ngap, with no alias: the module moved here (#2534).

class pcapkit.protocols.application.ngap.schema.NGAP(data=NO_VALUE, *, __packet__=None, **kwargs)[source]

Bases: Schema

Header schema for NGAP packet.

NGAP has no header of its own: an SCTP DATA chunk whose payload protocol identifier names NGAP carries exactly one aligned-PER-encoded NGAP-PDU and nothing else, so there is no length field to read and no framing to resolve. The whole payload is the encoding, and its structure only becomes visible once the ASN.1 decoder has run.

data: bytes = <BytesField data>

Aligned PER encoding of one NGAP-PDU.

Data Models

Removed in version 1.6.0: pcapkit.protocols.data.application.ngap, with no alias: the module moved here (#2534).

class pcapkit.protocols.application.ngap.data.NGAP(kind, procedure, criticality, message, ies, value)[source]

Bases: Protocol

Data model for NGAP protocol.

The three NGAP-PDU alternatives – initiatingMessage, successfulOutcome and unsuccessfulOutcome – carry an identical field set and are distinguished by kind rather than by three near-identical models.

kind: PDUKind

Which of the three NGAP-PDU alternatives this is.

procedure: ProcedureCode

Procedure code.

criticality: Criticality

Criticality of the procedure.

message: str

Name of the message type, e.g. NGSetupRequest.

ies: tuple[IE, ...]

Protocol IEs of the message, in the order they were encoded. Empty for the few messages that carry no protocolIEs field.

value: Sequence

The message body, converted in full. ies is a flattened view of its protocolIEs field, so anything not surfaced above is reachable here.

class pcapkit.protocols.application.ngap.data.IE(id, criticality, type, value)[source]

Bases: Data

Data model for one NGAP protocol information element.

id: ProtocolIE

Protocol IE ID.

criticality: Criticality

Criticality, i.e. what a receiver must do when it does not understand id.

type: str

Name of the IE’s open type, as spelled in 3GPP TS 38.413. This is not always id’s own spelling – IE 21 is id-DefaultPagingDRX but its value is keyed PagingDRX.

value: Any

Value of the IE.

class pcapkit.protocols.application.ngap.data.Choice(name, value)[source]

Bases: Data

Data model for an ASN.1 CHOICE alternative or open type.

Both the selected alternative’s name and its value are kept, since the name is the only thing that says which of the alternatives was sent – an NGAP-PDU carrying ('globalGNB-ID', ...) and one carrying ('globalNgENB-ID', ...) are otherwise indistinguishable once the value has been converted.

name: str

Name of the selected alternative, as spelled in 3GPP TS 38.413.

value: Any

Value of the selected alternative.

class pcapkit.protocols.application.ngap.data.BitString(value, length)[source]

Bases: Data

Data model for an ASN.1 BIT STRING.

A bit string is not a whole number of octets, so its length is carried alongside its value rather than being implied by it – gNB-ID is a 22-to-32-bit field, and (0x000102, 24) and (0x000102, 32) are different identifiers.

value: int

Bits, as a big-endian unsigned integer.

length: int

Number of significant bits in value.

class pcapkit.protocols.application.ngap.data.Sequence(dict_=None, **kwargs)[source]

Bases: Data

Data model for an ASN.1 SEQUENCE, SET or SEQUENCE OF member.

Fields are whatever the specification names them, so this model carries no fixed annotations: it is populated from the decoded value tree. ASN.1 identifiers are hyphenated where Python identifiers cannot be, e.g. gNB-ID, so such fields are reachable by subscription (seq['gNB-ID']) rather than by attribute access.

Footnotes