From 846dd00587bf189c489c9896e2c292c99de63256 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Mon, 28 Sep 2026 22:59:10 -0400 Subject: [PATCH] fix(ngap): source ProcedureCode/ProtocolIE from pycrate, stop the shared -1 mint (#880) Completes #880's ngap.py half (the mh.py half landed in #889). Per the owner's ruling, ProcedureCode/ProtocolIE are genuinely open per 3GPP TS 38.413, so they want a per-key unique value rather than the shared -1 sentinel #880 measured aliasing on -- sourced from pycrate's own compiled NGAP specification through a vendor crawler, not hand-maintained or minted on demand. - Add pcapkit/vendor/ngap/{procedure_code,protocol_ie}.py: crawlers whose _request() imports pycrate_asn1dir.NGAP (no network fetch -- pycrate is already an installed optional dependency) and reads NGAP_Constants' (name, value) pairs, filtered by each value's own typeref. - Add pcapkit/const/ngap/{procedure_code,protocol_ie}.py, generated: EnumRegistry + IntEnum, matching the house pattern. get() no longer mints on an unresolvable string key; _missing_ answers an in-range value it has not seen with a throwaway, non-registering member instead of a shared-value extend_enum mint, which is what stops two different unknown keys from aliasing. - pcapkit/protocols/application/ngap.py now re-exports both from pcapkit.const.ngap under their historical names, dropping ~600 lines of hand-rolled IntEnum + extend_enum logic. - Update tests/const/test_const_registry_protocol.py's own census (122 const files -> 124, 105 generated-shape -> 107) and its narrative paragraph, since the two new const files land on the generated side. Extend four other tests/const/ generic sweeps' pinned counts the same way (+2 registries each). - Per the owner's ruling, declare pycrate in pyproject.toml's `vendor` extra (developer-facing, alongside requests/beautifulsoup4) rather than installing it ad hoc in a workflow; `all` still excludes it, unchanged. cron-vendor.yml installs `.[all,vendor]` so the scheduled regeneration job -- which runs pcapkit-vendor with no target and so now reaches both new crawlers -- has pycrate_asn1dir.NGAP importable. Extended one dependency-gate test (tests/test_tier_guard.py) that pinned `vendor`'s exact requirement-name set. New tests in tests/vendor/ and tests/protocols/application/, each shown to fail without this fix. tests/const/test_const_registry_protocol.py in full via plain unittest: Ran 74 tests, OK. tests/test_tier_guard.py: Ran 107 tests, OK. pytest tests/const/ tests/vendor/ (excluding the reachability test) tests/protocols/application/test_ngap_unit.py: 384 passed, 41026 subtests, 0 known failures; coverage on the touched files 96-100%. --- .github/workflows/cron-vendor.yml | 10 +- docs/source/pcapkit/const/index.rst | 1 + docs/source/pcapkit/const/ngap.rst | 55 ++ docs/source/pcapkit/vendor/index.rst | 1 + docs/source/pcapkit/vendor/ngap.rst | 52 + pcapkit/const/__init__.py | 3 + pcapkit/const/ngap/__init__.py | 36 + pcapkit/const/ngap/procedure_code.py | 195 ++++ pcapkit/const/ngap/protocol_ie.py | 909 ++++++++++++++++++ pcapkit/protocols/application/ngap.py | 668 +------------ pcapkit/vendor/__init__.py | 3 + pcapkit/vendor/ngap/__init__.py | 34 + pcapkit/vendor/ngap/procedure_code.py | 205 ++++ pcapkit/vendor/ngap/protocol_ie.py | 175 ++++ pyproject.toml | 12 +- tests/const/test_const_enum_builtin_parity.py | 10 +- tests/const/test_const_enum_get.py | 15 +- tests/const/test_const_enum_lookup.py | 7 +- tests/const/test_const_registry_protocol.py | 31 +- tests/protocols/application/test_ngap_unit.py | 71 +- tests/test_tier_guard.py | 13 +- tests/vendor/test_vendor_ngap_unit.py | 185 ++++ 22 files changed, 2042 insertions(+), 649 deletions(-) create mode 100644 docs/source/pcapkit/const/ngap.rst create mode 100644 docs/source/pcapkit/vendor/ngap.rst create mode 100644 pcapkit/const/ngap/__init__.py create mode 100644 pcapkit/const/ngap/procedure_code.py create mode 100644 pcapkit/const/ngap/protocol_ie.py create mode 100644 pcapkit/vendor/ngap/__init__.py create mode 100644 pcapkit/vendor/ngap/procedure_code.py create mode 100644 pcapkit/vendor/ngap/protocol_ie.py create mode 100644 tests/vendor/test_vendor_ngap_unit.py diff --git a/.github/workflows/cron-vendor.yml b/.github/workflows/cron-vendor.yml index 600fd5b1f..13b4effa3 100644 --- a/.github/workflows/cron-vendor.yml +++ b/.github/workflows/cron-vendor.yml @@ -111,7 +111,15 @@ jobs: python -m pip install -U pip setuptools wheel packaging python -m pip install -U isort - python -m pip install -e .[all] + + # `all` does not carry `pycrate` -- see its own comment in + # pyproject.toml -- so `vendor` is installed alongside it + # specifically for that: `pcapkit-vendor` below regenerates every + # crawler with no target given, including pcapkit.vendor.ngap's two + # (#880), and both need `pycrate_asn1dir.NGAP` importable. See the + # `vendor` extra's own comment in pyproject.toml for the full + # reasoning (size, licence) behind installing it here at all. + python -m pip install -e .[all,vendor] - name: Update Vendor run: | diff --git a/docs/source/pcapkit/const/index.rst b/docs/source/pcapkit/const/index.rst index b2d6d9613..861721242 100644 --- a/docs/source/pcapkit/const/index.rst +++ b/docs/source/pcapkit/const/index.rst @@ -93,3 +93,4 @@ Application Layer ftp http + ngap diff --git a/docs/source/pcapkit/const/ngap.rst b/docs/source/pcapkit/const/ngap.rst new file mode 100644 index 000000000..cea51d75c --- /dev/null +++ b/docs/source/pcapkit/const/ngap.rst @@ -0,0 +1,55 @@ +======================================================================== +:class:`~pcapkit.protocols.application.ngap.NGAP` Constant Enumerations +======================================================================== + +.. module:: pcapkit.const.ngap + +This module contains all constant enumerations of +:class:`~pcapkit.protocols.application.ngap.NGAP` implementations. Available +enumerations include: + +.. list-table:: + + * - :class:`NGAP_ProcedureCode ` + - NGAP Elementary Procedure Codes [*]_ + * - :class:`NGAP_ProtocolIE ` + - NGAP Protocol IE Identifiers [*]_ + +Both are automatically generated from |pycrate|_'s compiled NGAP +specification rather than an IANA-style registry -- see +:mod:`pcapkit.vendor.ngap.procedure_code`'s module docstring for why, and +GitHub issue #880 for the ruling. + +NGAP Elementary Procedure Codes +=============================== + +.. module:: pcapkit.const.ngap.procedure_code + +This module contains the constant enumeration for **NGAP Elementary Procedure Codes**, +which is automatically generated from :class:`pcapkit.vendor.ngap.procedure_code.ProcedureCode`. + +.. autoclass:: pcapkit.const.ngap.procedure_code.ProcedureCode + :members: + :undoc-members: + :show-inheritance: + +NGAP Protocol IE Identifiers +============================= + +.. module:: pcapkit.const.ngap.protocol_ie + +This module contains the constant enumeration for **NGAP Protocol IE Identifiers**, +which is automatically generated from :class:`pcapkit.vendor.ngap.protocol_ie.ProtocolIE`. + +.. autoclass:: pcapkit.const.ngap.protocol_ie.ProtocolIE + :members: + :undoc-members: + :show-inheritance: + +.. rubric:: Footnotes + +.. [*] 3GPP TS 38.413 +.. [*] 3GPP TS 38.413 + +.. |pycrate| replace:: ``pycrate`` +.. _pycrate: https://github.com/pycrate-org/pycrate diff --git a/docs/source/pcapkit/vendor/index.rst b/docs/source/pcapkit/vendor/index.rst index 78d8f7d65..c54942372 100644 --- a/docs/source/pcapkit/vendor/index.rst +++ b/docs/source/pcapkit/vendor/index.rst @@ -73,6 +73,7 @@ Application Layer ftp http + ngap Command Line Tool ================= diff --git a/docs/source/pcapkit/vendor/ngap.rst b/docs/source/pcapkit/vendor/ngap.rst new file mode 100644 index 000000000..309ec9aaa --- /dev/null +++ b/docs/source/pcapkit/vendor/ngap.rst @@ -0,0 +1,52 @@ +==================================================================== +:class:`~pcapkit.protocols.application.ngap.NGAP` Vendor Crawlers +==================================================================== + +.. module:: pcapkit.vendor.ngap + +This module contains all vendor crawlers of +:class:`~pcapkit.protocols.application.ngap.NGAP` implementations. Available +vendor crawlers include: + +.. list-table:: + + * - :class:`NGAP_ProcedureCode ` + - NGAP Elementary Procedure Codes [*]_ + * - :class:`NGAP_ProtocolIE ` + - NGAP Protocol IE Identifiers [*]_ + +Both source the assignment from |pycrate|_'s already-installed, compiled NGAP +specification rather than fetching a network registry -- see the module +docstring below for why, and GitHub issue #880 for the ruling. + +NGAP Elementary Procedure Codes +=============================== + +.. module:: pcapkit.vendor.ngap.procedure_code + +This module contains the vendor crawler for **NGAP Elementary Procedure Codes**, +which is automatically generating :class:`pcapkit.const.ngap.procedure_code.ProcedureCode`. + +.. autoclass:: pcapkit.vendor.ngap.procedure_code.ProcedureCode + :members: FLAG, LINK + :show-inheritance: + +NGAP Protocol IE Identifiers +============================= + +.. module:: pcapkit.vendor.ngap.protocol_ie + +This module contains the vendor crawler for **NGAP Protocol IE Identifiers**, +which is automatically generating :class:`pcapkit.const.ngap.protocol_ie.ProtocolIE`. + +.. autoclass:: pcapkit.vendor.ngap.protocol_ie.ProtocolIE + :members: FLAG, LINK + :show-inheritance: + +.. rubric:: Footnotes + +.. [*] 3GPP TS 38.413 +.. [*] 3GPP TS 38.413 + +.. |pycrate| replace:: ``pycrate`` +.. _pycrate: https://github.com/pycrate-org/pycrate diff --git a/pcapkit/const/__init__.py b/pcapkit/const/__init__.py index ec0cb9104..20a06e7f6 100644 --- a/pcapkit/const/__init__.py +++ b/pcapkit/const/__init__.py @@ -25,6 +25,7 @@ from pcapkit.const.ipx import * from pcapkit.const.l2tp import * from pcapkit.const.mh import * +from pcapkit.const.ngap import * from pcapkit.const.ospf import * from pcapkit.const.sctp import * from pcapkit.const.tcp import * @@ -72,6 +73,8 @@ 'MH_FlowBindingACKStatus', 'MH_FlowBindingAction', 'MH_QoSAttribute', 'MH_LMAControlledMAGSuboption', 'MH_LLACode', 'MH_CGAType', 'MH_CGAExtension', 'MH_CGASec', 'MH_BindingError', + # NGAP + 'NGAP_ProcedureCode', 'NGAP_ProtocolIE', # OSPF 'OSPF_Authentication', 'OSPF_Packet', # SCTP diff --git a/pcapkit/const/ngap/__init__.py b/pcapkit/const/ngap/__init__.py new file mode 100644 index 000000000..aad35818c --- /dev/null +++ b/pcapkit/const/ngap/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# pylint: disable=unused-import +""":class:`~pcapkit.protocols.application.ngap.NGAP` Constant Enumerations +============================================================================== + +.. module:: pcapkit.const.ngap + +This module contains all constant enumerations of +:class:`~pcapkit.protocols.application.ngap.NGAP` implementations. Available +enumerations include: + +.. list-table:: + + * - :class:`NGAP_ProcedureCode ` + - NGAP Elementary Procedure Codes [*]_ + * - :class:`NGAP_ProtocolIE ` + - NGAP Protocol IE Identifiers [*]_ + +Both are automatically generated from +:mod:`pcapkit.vendor.ngap.procedure_code` and +:mod:`pcapkit.vendor.ngap.protocol_ie`, which source the assignment from +|pycrate|_'s compiled NGAP specification rather than a network registry -- +see that module's docstring for why, and GitHub issue #880 for the ruling. + +.. [*] 3GPP TS 38.413 +.. [*] 3GPP TS 38.413 + +.. |pycrate| replace:: ``pycrate`` +.. _pycrate: https://github.com/pycrate-org/pycrate + +""" + +from pcapkit.const.ngap.procedure_code import ProcedureCode as NGAP_ProcedureCode +from pcapkit.const.ngap.protocol_ie import ProtocolIE as NGAP_ProtocolIE + +__all__ = ['NGAP_ProcedureCode', 'NGAP_ProtocolIE'] diff --git a/pcapkit/const/ngap/procedure_code.py b/pcapkit/const/ngap/procedure_code.py new file mode 100644 index 000000000..d01f7e84e --- /dev/null +++ b/pcapkit/const/ngap/procedure_code.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long +"""NGAP elementary procedure codes, 3GPP TS 38.413. +====================================================== + +.. module:: pcapkit.const.ngap.procedure_code + +This module contains the constant enumeration for **NGAP elementary procedure codes, 3GPP TS 38.413.**, +which is automatically generated from :class:`pcapkit.vendor.ngap.procedure_code.ProcedureCode`. + +""" + +from aenum import IntEnum + +from pcapkit.corekit.enum import EnumRegistry + +__all__ = ['ProcedureCode'] + + +class ProcedureCode(EnumRegistry, IntEnum): + """[ProcedureCode] NGAP elementary procedure codes, 3GPP TS 38.413.""" + + AMFConfigurationUpdate = 0 + + AMFStatusIndication = 1 + + CellTrafficTrace = 2 + + DeactivateTrace = 3 + + DownlinkNASTransport = 4 + + DownlinkNonUEAssociatedNRPPaTransport = 5 + + DownlinkRANConfigurationTransfer = 6 + + DownlinkRANStatusTransfer = 7 + + DownlinkUEAssociatedNRPPaTransport = 8 + + ErrorIndication = 9 + + HandoverCancel = 10 + + HandoverNotification = 11 + + HandoverPreparation = 12 + + HandoverResourceAllocation = 13 + + InitialContextSetup = 14 + + InitialUEMessage = 15 + + LocationReportingControl = 16 + + LocationReportingFailureIndication = 17 + + LocationReport = 18 + + NASNonDeliveryIndication = 19 + + NGReset = 20 + + NGSetup = 21 + + OverloadStart = 22 + + OverloadStop = 23 + + Paging = 24 + + PathSwitchRequest = 25 + + PDUSessionResourceModify = 26 + + PDUSessionResourceModifyIndication = 27 + + PDUSessionResourceRelease = 28 + + PDUSessionResourceSetup = 29 + + PDUSessionResourceNotify = 30 + + PrivateMessage = 31 + + PWSCancel = 32 + + PWSFailureIndication = 33 + + PWSRestartIndication = 34 + + RANConfigurationUpdate = 35 + + RerouteNASRequest = 36 + + RRCInactiveTransitionReport = 37 + + TraceFailureIndication = 38 + + TraceStart = 39 + + UEContextModification = 40 + + UEContextRelease = 41 + + UEContextReleaseRequest = 42 + + UERadioCapabilityCheck = 43 + + UERadioCapabilityInfoIndication = 44 + + UETNLABindingRelease = 45 + + UplinkNASTransport = 46 + + UplinkNonUEAssociatedNRPPaTransport = 47 + + UplinkRANConfigurationTransfer = 48 + + UplinkRANStatusTransfer = 49 + + UplinkUEAssociatedNRPPaTransport = 50 + + WriteReplaceWarning = 51 + + SecondaryRATDataUsageReport = 52 + + UplinkRIMInformationTransfer = 53 + + DownlinkRIMInformationTransfer = 54 + + RetrieveUEInformation = 55 + + UEInformationTransfer = 56 + + RANCPRelocationIndication = 57 + + UEContextResume = 58 + + UEContextSuspend = 59 + + UERadioCapabilityIDMapping = 60 + + HandoverSuccess = 61 + + UplinkRANEarlyStatusTransfer = 62 + + DownlinkRANEarlyStatusTransfer = 63 + + AMFCPRelocationIndication = 64 + + ConnectionEstablishmentIndication = 65 + + BroadcastSessionModification = 66 + + BroadcastSessionRelease = 67 + + BroadcastSessionSetup = 68 + + DistributionSetup = 69 + + DistributionRelease = 70 + + MulticastSessionActivation = 71 + + MulticastSessionDeactivation = 72 + + MulticastSessionUpdate = 73 + + MulticastGroupPaging = 74 + + BroadcastSessionReleaseRequired = 75 + + TimingSynchronisationStatus = 76 + + TimingSynchronisationStatusReport = 77 + + MTCommunicationHandling = 78 + + RANPagingRequest = 79 + + BroadcastSessionTransport = 80 + + @classmethod + def _missing_(cls, value: 'int') -> 'ProcedureCode': + """Lookup function used when value is not found. + + Args: + value: Value to get enum item. + + """ + if not (isinstance(value, int) and 0 <= value <= 255): + raise ValueError('%r is not a valid %s' % (value, cls.__name__)) + return cls._unregistered_member(value, 'Unassigned') diff --git a/pcapkit/const/ngap/protocol_ie.py b/pcapkit/const/ngap/protocol_ie.py new file mode 100644 index 000000000..3d63bfd50 --- /dev/null +++ b/pcapkit/const/ngap/protocol_ie.py @@ -0,0 +1,909 @@ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long +"""NGAP protocol IE identifiers, 3GPP TS 38.413. +=================================================== + +.. module:: pcapkit.const.ngap.protocol_ie + +This module contains the constant enumeration for **NGAP protocol IE identifiers, 3GPP TS 38.413.**, +which is automatically generated from :class:`pcapkit.vendor.ngap.protocol_ie.ProtocolIE`. + +""" + +from aenum import IntEnum + +from pcapkit.corekit.enum import EnumRegistry + +__all__ = ['ProtocolIE'] + + +class ProtocolIE(EnumRegistry, IntEnum): + """[ProtocolIE] NGAP protocol IE identifiers, 3GPP TS 38.413.""" + + AllowedNSSAI = 0 + + AMFName = 1 + + AMFOverloadResponse = 2 + + AMFSetID = 3 + + AMF_TNLAssociationFailedToSetupList = 4 + + AMF_TNLAssociationSetupList = 5 + + AMF_TNLAssociationToAddList = 6 + + AMF_TNLAssociationToRemoveList = 7 + + AMF_TNLAssociationToUpdateList = 8 + + AMFTrafficLoadReductionIndication = 9 + + AMF_UE_NGAP_ID = 10 + + AssistanceDataForPaging = 11 + + BroadcastCancelledAreaList = 12 + + BroadcastCompletedAreaList = 13 + + CancelAllWarningMessages = 14 + + Cause = 15 + + CellIDListForRestart = 16 + + ConcurrentWarningMessageInd = 17 + + CoreNetworkAssistanceInformationForInactive = 18 + + CriticalityDiagnostics = 19 + + DataCodingScheme = 20 + + DefaultPagingDRX = 21 + + DirectForwardingPathAvailability = 22 + + EmergencyAreaIDListForRestart = 23 + + EmergencyFallbackIndicator = 24 + + EUTRA_CGI = 25 + + FiveG_S_TMSI = 26 + + GlobalRANNodeID = 27 + + GUAMI = 28 + + HandoverType = 29 + + IMSVoiceSupportIndicator = 30 + + IndexToRFSP = 31 + + InfoOnRecommendedCellsAndRANNodesForPaging = 32 + + LocationReportingRequestType = 33 + + MaskedIMEISV = 34 + + MessageIdentifier = 35 + + MobilityRestrictionList = 36 + + NASC = 37 + + NAS_PDU = 38 + + NASSecurityParametersFromNGRAN = 39 + + NewAMF_UE_NGAP_ID = 40 + + NewSecurityContextInd = 41 + + NGAP_Message = 42 + + NGRAN_CGI = 43 + + NGRANTraceID = 44 + + NR_CGI = 45 + + NRPPa_PDU = 46 + + NumberOfBroadcastsRequested = 47 + + OldAMF = 48 + + OverloadStartNSSAIList = 49 + + PagingDRX = 50 + + PagingOrigin = 51 + + PagingPriority = 52 + + PDUSessionResourceAdmittedList = 53 + + PDUSessionResourceFailedToModifyListModRes = 54 + + PDUSessionResourceFailedToSetupListCxtRes = 55 + + PDUSessionResourceFailedToSetupListHOAck = 56 + + PDUSessionResourceFailedToSetupListPSReq = 57 + + PDUSessionResourceFailedToSetupListSURes = 58 + + PDUSessionResourceHandoverList = 59 + + PDUSessionResourceListCxtRelCpl = 60 + + PDUSessionResourceListHORqd = 61 + + PDUSessionResourceModifyListModCfm = 62 + + PDUSessionResourceModifyListModInd = 63 + + PDUSessionResourceModifyListModReq = 64 + + PDUSessionResourceModifyListModRes = 65 + + PDUSessionResourceNotifyList = 66 + + PDUSessionResourceReleasedListNot = 67 + + PDUSessionResourceReleasedListPSAck = 68 + + PDUSessionResourceReleasedListPSFail = 69 + + PDUSessionResourceReleasedListRelRes = 70 + + PDUSessionResourceSetupListCxtReq = 71 + + PDUSessionResourceSetupListCxtRes = 72 + + PDUSessionResourceSetupListHOReq = 73 + + PDUSessionResourceSetupListSUReq = 74 + + PDUSessionResourceSetupListSURes = 75 + + PDUSessionResourceToBeSwitchedDLList = 76 + + PDUSessionResourceSwitchedList = 77 + + PDUSessionResourceToReleaseListHOCmd = 78 + + PDUSessionResourceToReleaseListRelCmd = 79 + + PLMNSupportList = 80 + + PWSFailedCellIDList = 81 + + RANNodeName = 82 + + RANPagingPriority = 83 + + RANStatusTransfer_TransparentContainer = 84 + + RAN_UE_NGAP_ID = 85 + + RelativeAMFCapacity = 86 + + RepetitionPeriod = 87 + + ResetType = 88 + + RoutingID = 89 + + RRCEstablishmentCause = 90 + + RRCInactiveTransitionReportRequest = 91 + + RRCState = 92 + + SecurityContext = 93 + + SecurityKey = 94 + + SerialNumber = 95 + + ServedGUAMIList = 96 + + SliceSupportList = 97 + + SONConfigurationTransferDL = 98 + + SONConfigurationTransferUL = 99 + + SourceAMF_UE_NGAP_ID = 100 + + SourceToTarget_TransparentContainer = 101 + + SupportedTAList = 102 + + TAIListForPaging = 103 + + TAIListForRestart = 104 + + TargetID = 105 + + TargetToSource_TransparentContainer = 106 + + TimeToWait = 107 + + TraceActivation = 108 + + TraceCollectionEntityIPAddress = 109 + + UEAggregateMaximumBitRate = 110 + + UE_associatedLogicalNG_connectionList = 111 + + UEContextRequest = 112 + + UE_NGAP_IDs = 114 + + UEPagingIdentity = 115 + + UEPresenceInAreaOfInterestList = 116 + + UERadioCapability = 117 + + UERadioCapabilityForPaging = 118 + + UESecurityCapabilities = 119 + + UnavailableGUAMIList = 120 + + UserLocationInformation = 121 + + WarningAreaList = 122 + + WarningMessageContents = 123 + + WarningSecurityInfo = 124 + + WarningType = 125 + + AdditionalUL_NGU_UP_TNLInformation = 126 + + DataForwardingNotPossible = 127 + + DL_NGU_UP_TNLInformation = 128 + + NetworkInstance = 129 + + PDUSessionAggregateMaximumBitRate = 130 + + PDUSessionResourceFailedToModifyListModCfm = 131 + + PDUSessionResourceFailedToSetupListCxtFail = 132 + + PDUSessionResourceListCxtRelReq = 133 + + PDUSessionType = 134 + + QosFlowAddOrModifyRequestList = 135 + + QosFlowSetupRequestList = 136 + + QosFlowToReleaseList = 137 + + SecurityIndication = 138 + + UL_NGU_UP_TNLInformation = 139 + + UL_NGU_UP_TNLModifyList = 140 + + WarningAreaCoordinates = 141 + + PDUSessionResourceSecondaryRATUsageList = 142 + + HandoverFlag = 143 + + SecondaryRATUsageInformation = 144 + + PDUSessionResourceReleaseResponseTransfer = 145 + + RedirectionVoiceFallback = 146 + + UERetentionInformation = 147 + + S_NSSAI = 148 + + PSCellInformation = 149 + + LastEUTRAN_PLMNIdentity = 150 + + MaximumIntegrityProtectedDataRate_DL = 151 + + AdditionalDLForwardingUPTNLInformation = 152 + + AdditionalDLUPTNLInformationForHOList = 153 + + AdditionalNGU_UP_TNLInformation = 154 + + AdditionalDLQosFlowPerTNLInformation = 155 + + SecurityResult = 156 + + ENDC_SONConfigurationTransferDL = 157 + + ENDC_SONConfigurationTransferUL = 158 + + OldAssociatedQosFlowList_ULendmarkerexpected = 159 + + CNTypeRestrictionsForEquivalent = 160 + + CNTypeRestrictionsForServing = 161 + + NewGUAMI = 162 + + ULForwarding = 163 + + ULForwardingUP_TNLInformation = 164 + + CNAssistedRANTuning = 165 + + CommonNetworkInstance = 166 + + NGRAN_TNLAssociationToRemoveList = 167 + + TNLAssociationTransportLayerAddressNGRAN = 168 + + EndpointIPAddressAndPort = 169 + + LocationReportingAdditionalInfo = 170 + + SourceToTarget_AMFInformationReroute = 171 + + AdditionalULForwardingUPTNLInformation = 172 + + SCTP_TLAs = 173 + + SelectedPLMNIdentity = 174 + + RIMInformationTransfer = 175 + + GUAMIType = 176 + + SRVCCOperationPossible = 177 + + TargetRNC_ID = 178 + + RAT_Information = 179 + + ExtendedRATRestrictionInformation = 180 + + QosMonitoringRequest = 181 + + SgNB_UE_X2AP_ID = 182 + + AdditionalRedundantDL_NGU_UP_TNLInformation = 183 + + AdditionalRedundantDLQosFlowPerTNLInformation = 184 + + AdditionalRedundantNGU_UP_TNLInformation = 185 + + AdditionalRedundantUL_NGU_UP_TNLInformation = 186 + + CNPacketDelayBudgetDL = 187 + + CNPacketDelayBudgetUL = 188 + + ExtendedPacketDelayBudget = 189 + + RedundantCommonNetworkInstance = 190 + + RedundantDL_NGU_TNLInformationReused = 191 + + RedundantDL_NGU_UP_TNLInformation = 192 + + RedundantDLQosFlowPerTNLInformation = 193 + + RedundantQosFlowIndicator = 194 + + RedundantUL_NGU_UP_TNLInformation = 195 + + TSCTrafficCharacteristics = 196 + + RedundantPDUSessionInformation = 197 + + UsedRSNInformation = 198 + + IAB_Authorized = 199 + + IAB_Supported = 200 + + IABNodeIndication = 201 + + NB_IoT_PagingDRX = 202 + + NB_IoT_Paging_eDRXInfo = 203 + + NB_IoT_DefaultPagingDRX = 204 + + Enhanced_CoverageRestriction = 205 + + Extended_ConnectedTime = 206 + + PagingAssisDataforCEcapabUE = 207 + + WUS_Assistance_Information = 208 + + UE_DifferentiationInfo = 209 + + NB_IoT_UEPriority = 210 + + UL_CP_SecurityInformation = 211 + + DL_CP_SecurityInformation = 212 + + TAI = 213 + + UERadioCapabilityForPagingOfNB_IoT = 214 + + LTEV2XServicesAuthorized = 215 + + NRV2XServicesAuthorized = 216 + + LTEUESidelinkAggregateMaximumBitrate = 217 + + NRUESidelinkAggregateMaximumBitrate = 218 + + PC5QoSParameters = 219 + + AlternativeQoSParaSetList = 220 + + CurrentQoSParaSetIndex = 221 + + CEmodeBrestricted = 222 + + EUTRA_PagingeDRXInformation = 223 + + CEmodeBSupport_Indicator = 224 + + LTEM_Indication = 225 + + EndIndication = 226 + + EDT_Session = 227 + + UECapabilityInfoRequest = 228 + + PDUSessionResourceFailedToResumeListRESReq = 229 + + PDUSessionResourceFailedToResumeListRESRes = 230 + + PDUSessionResourceSuspendListSUSReq = 231 + + PDUSessionResourceResumeListRESReq = 232 + + PDUSessionResourceResumeListRESRes = 233 + + UE_UP_CIoT_Support = 234 + + Suspend_Request_Indication = 235 + + Suspend_Response_Indication = 236 + + RRC_Resume_Cause = 237 + + RGLevelWirelineAccessCharacteristics = 238 + + W_AGFIdentityInformation = 239 + + GlobalTNGF_ID = 240 + + GlobalTWIF_ID = 241 + + GlobalW_AGF_ID = 242 + + UserLocationInformationW_AGF = 243 + + UserLocationInformationTNGF = 244 + + AuthenticatedIndication = 245 + + TNGFIdentityInformation = 246 + + TWIFIdentityInformation = 247 + + UserLocationInformationTWIF = 248 + + DataForwardingResponseERABList = 249 + + IntersystemSONConfigurationTransferDL = 250 + + IntersystemSONConfigurationTransferUL = 251 + + SONInformationReport = 252 + + UEHistoryInformationFromTheUE = 253 + + ManagementBasedMDTPLMNList = 254 + + MDTConfiguration = 255 + + PrivacyIndicator = 256 + + TraceCollectionEntityURI = 257 + + NPN_Support = 258 + + NPN_AccessInformation = 259 + + NPN_PagingAssistanceInformation = 260 + + NPN_MobilityInformation = 261 + + TargettoSource_Failure_TransparentContainer = 262 + + NID = 263 + + UERadioCapabilityID = 264 + + UERadioCapability_EUTRA_Format = 265 + + DAPSRequestInfo = 266 + + DAPSResponseInfoList = 267 + + EarlyStatusTransfer_TransparentContainer = 268 + + NotifySourceNGRANNode = 269 + + ExtendedSliceSupportList = 270 + + ExtendedTAISliceSupportList = 271 + + ConfiguredTACIndication = 272 + + Extended_RANNodeName = 273 + + Extended_AMFName = 274 + + GlobalCable_ID = 275 + + QosMonitoringReportingFrequency = 276 + + QosFlowParametersList = 277 + + QosFlowFeedbackList = 278 + + BurstArrivalTimeDownlink = 279 + + ExtendedUEIdentityIndexValue = 280 + + PduSessionExpectedUEActivityBehaviour = 281 + + MicoAllPLMN = 282 + + QosFlowFailedToSetupList = 283 + + SourceTNLAddrInfo = 284 + + ExtendedReportIntervalMDT = 285 + + SourceNodeID = 286 + + NRNTNTAIInformation = 287 + + UEContextReferenceAtSource = 288 + + LastVisitedPSCellList = 289 + + IntersystemSONInformationRequest = 290 + + IntersystemSONInformationReply = 291 + + EnergySavingIndication = 292 + + IntersystemResourceStatusUpdate = 293 + + SuccessfulHandoverReportList = 294 + + MBS_AreaSessionID = 295 + + MBS_QoSFlowsToBeSetupList = 296 + + MBS_QoSFlowsToBeSetupModList = 297 + + MBS_ServiceArea = 298 + + MBS_SessionID = 299 + + MBS_DistributionReleaseRequestTransfer = 300 + + MBS_DistributionSetupRequestTransfer = 301 + + MBS_DistributionSetupResponseTransfer = 302 + + MBS_DistributionSetupUnsuccessfulTransfer = 303 + + MulticastSessionActivationRequestTransfer = 304 + + MulticastSessionDeactivationRequestTransfer = 305 + + MulticastSessionUpdateRequestTransfer = 306 + + MulticastGroupPagingAreaList = 307 + + MBS_SupportIndicator = 309 + + MBSSessionFailedtoSetupList = 310 + + MBSSessionFailedtoSetuporModifyList = 311 + + MBSSessionSetupResponseList = 312 + + MBSSessionSetuporModifyResponseList = 313 + + MBSSessionSetupFailureTransfer = 314 + + MBSSessionSetupRequestTransfer = 315 + + MBSSessionSetupResponseTransfer = 316 + + MBSSessionToReleaseList = 317 + + MBSSessionSetupRequestList = 318 + + MBSSessionSetuporModifyRequestList = 319 + + MBS_ActiveSessionInformation_SourcetoTargetList = 323 + + MBS_ActiveSessionInformation_TargettoSourceList = 324 + + OnboardingSupport = 325 + + TimeSyncAssistanceInfo = 326 + + SurvivalTime = 327 + + QMCConfigInfo = 328 + + QMCDeactivation = 329 + + PDUSessionPairID = 331 + + NR_PagingeDRXInformation = 332 + + RedCapIndication = 333 + + TargetNSSAIInformation = 334 + + UESliceMaximumBitRateList = 335 + + M4ReportAmount = 336 + + M5ReportAmount = 337 + + M6ReportAmount = 338 + + M7ReportAmount = 339 + + IncludeBeamMeasurementsIndication = 340 + + ExcessPacketDelayThresholdConfiguration = 341 + + PagingCause = 342 + + PagingCauseIndicationForVoiceService = 343 + + PEIPSassistanceInformation = 344 + + FiveG_ProSeAuthorized = 345 + + FiveG_ProSeUEPC5AggregateMaximumBitRate = 346 + + FiveG_ProSePC5QoSParameters = 347 + + MBSSessionModificationFailureTransfer = 348 + + MBSSessionModificationRequestTransfer = 349 + + MBSSessionModificationResponseTransfer = 350 + + MBS_QoSFlowToReleaseList = 351 + + MBS_SessionTNLInfo5GC = 352 + + TAINSAGSupportList = 353 + + SourceNodeTNLAddrInfo = 354 + + NGAPIESupportInformationRequestList = 355 + + NGAPIESupportInformationResponseList = 356 + + MBS_SessionFSAIDList = 357 + + MBSSessionReleaseResponseTransfer = 358 + + ManagementBasedMDTPLMNModificationList = 359 + + EarlyMeasurement = 360 + + BeamMeasurementsReportConfiguration = 361 + + HFCNode_ID_new = 362 + + GlobalCable_ID_new = 363 + + TargetHomeENB_ID = 364 + + HashedUEIdentityIndexValue = 365 + + ExtendedMobilityInformation = 366 + + NetworkControlledRepeaterAuthorized = 367 + + AdditionalCancelledlocationReportingReferenceIDList = 368 + + Selected_Target_SNPN_Identity = 369 + + EquivalentSNPNsList = 370 + + SelectedNID = 371 + + SupportedUETypeList = 372 + + AerialUEsubscriptionInformation = 373 + + NR_A2X_ServicesAuthorized = 374 + + LTE_A2X_ServicesAuthorized = 375 + + NR_A2X_UE_PC5_AggregateMaximumBitRate = 376 + + LTE_A2X_UE_PC5_AggregateMaximumBitRate = 377 + + A2X_PC5_QoS_Parameters = 378 + + FiveGProSeLayer2Multipath = 379 + + FiveGProSeLayer2UEtoUERelay = 380 + + FiveGProSeLayer2UEtoUERemote = 381 + + CandidateRelayUEInformationList = 382 + + SuccessfulPSCellChangeReportList = 383 + + IntersystemMobilityFailureforVoiceFallback = 384 + + TargetCellCRNTI = 385 + + TimeSinceFailure = 386 + + RANTimingSynchronisationStatusInfo = 387 + + RAN_TSSRequestType = 388 + + RAN_TSSScope = 389 + + ClockQualityReportingControlInfo = 390 + + RANfeedbacktype = 391 + + QoSFlowTSCList = 392 + + TSCTrafficCharacteristicsFeedback = 393 + + DownlinkTLContainer = 394 + + UplinkTLContainer = 395 + + ANPacketDelayBudgetUL = 396 + + QosFlowAdditionalInfoList = 397 + + AssistanceInformationQoE_Meas = 398 + + MBSCommServiceType = 399 + + MobileIAB_Authorized = 400 + + MobileIAB_MTUserLocationInformation = 401 + + MobileIABNodeIndication = 402 + + NoPDUSessionIndication = 403 + + MobileIAB_Supported = 404 + + CN_MT_CommunicationHandling = 405 + + FiveGCAction = 406 + + PagingPolicyDifferentiation = 407 + + DL_Signalling = 408 + + PNI_NPN_AreaScopeofMDT = 409 + + PNI_NPNBasedMDT = 410 + + SNPN_CellBasedMDT = 411 + + SNPN_TAIBasedMDT = 412 + + SNPN_BasedMDT = 413 + + Partially_Allowed_NSSAI = 414 + + AssociatedSessionID = 415 + + MBS_AssistanceInformation = 416 + + BroadcastTransportFailureTransfer = 417 + + BroadcastTransportRequestTransfer = 418 + + BroadcastTransportResponseTransfer = 419 + + TimeBasedHandoverInformation = 420 + + DLDiscarding = 421 + + PDUsetQoSParameters = 422 + + PDUSetbasedHandlingIndicator = 423 + + N6JitterInformation = 424 + + ECNMarkingorCongestionInformationReportingRequest = 425 + + ECNMarkingorCongestionInformationReportingStatus = 426 + + ERedCapIndication = 427 + + XrDeviceWith2Rx = 428 + + UserPlaneErrorIndicator = 429 + + SLPositioningRangingServiceInfo = 430 + + PDUSessionListMTCommHReq = 431 + + MaximumDataBurstVolume = 432 + + MN_only_MDT_collection = 433 + + MBS_NGUFailureIndication = 434 + + UserPlaneFailureIndication = 435 + + UserPlaneFailureIndicationReport = 436 + + SourceSN_to_TargetSN_QMCInfo = 437 + + QoERVQoEReportingPaths = 438 + + UserLocationInformationN3IWF_without_PortNumber = 439 + + AUN3DeviceAccessInfo = 440 + + TAIMBSSupportList = 441 + + ExtendedBackupAMFName = 442 + + ExtendedOldAMF = 443 + + @classmethod + def _missing_(cls, value: 'int') -> 'ProtocolIE': + """Lookup function used when value is not found. + + Args: + value: Value to get enum item. + + """ + if not (isinstance(value, int) and 0 <= value <= 65535): + raise ValueError('%r is not a valid %s' % (value, cls.__name__)) + return cls._unregistered_member(value, 'Unassigned') diff --git a/pcapkit/protocols/application/ngap.py b/pcapkit/protocols/application/ngap.py index 8bf08ee58..62511c4f1 100644 --- a/pcapkit/protocols/application/ngap.py +++ b/pcapkit/protocols/application/ngap.py @@ -75,13 +75,15 @@ bytes reaching :meth:`NGAP.read` are not an APER encoding. The PPID is registered so that it is *named* rather than anonymous; the payload itself degrades to :class:`~pcapkit.protocols.misc.raw.Raw`. -* **The specification version is |pycrate|_'s, not this package's.** The IE and - procedure enumerations below were generated from ``NGAP_Constants`` of - |pycrate|_ 0.8.1 (Release-18-era: 81 procedure codes, 438 protocol IE IDs, - highest 443). A |pycrate|_ that carries a newer NGAP will decode IEs that - :class:`ProcedureCode` and :class:`ProtocolIE` do not name; both extend - themselves at lookup time rather than failing, so such a value is reported - as ``Unassigned_``. +* **The specification version is |pycrate|_'s, not this package's.** + :class:`ProcedureCode` and :class:`ProtocolIE` (:class:`pcapkit.const.ngap. + procedure_code.ProcedureCode` and :class:`pcapkit.const.ngap.protocol_ie. + ProtocolIE`) were generated from ``NGAP_Constants`` of |pycrate|_ 0.8.1 + (Release-18-era: 81 procedure codes, 438 protocol IE IDs, highest 443). A + |pycrate|_ that carries a newer NGAP will decode IEs that this release does + not name; such a value resolves to a throwaway, non-registering member + named ``Unassigned`` rather than failing or growing either enumeration -- + see :meth:`pcapkit.corekit.enum.EnumRegistry._unregistered_member`. * **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. @@ -98,8 +100,10 @@ import threading from typing import TYPE_CHECKING -from aenum import IntEnum, extend_enum +from aenum import IntEnum +from pcapkit.const.ngap.procedure_code import ProcedureCode as Enum_ProcedureCode +from pcapkit.const.ngap.protocol_ie import ProtocolIE as Enum_ProtocolIE from pcapkit.corekit.infoclass import Info from pcapkit.protocols.application.application import Application from pcapkit.protocols.data.application.ngap import IE as Data_IE @@ -166,15 +170,26 @@ def load_pycrate() -> 'Optional[Any]': ############################################################################## # Enumerations. # -# These are 3GPP TS 38.413 specification values, not an IANA registry with a -# crawlable page, so they live here rather than in pcapkit.const and have no -# vendor crawler. The SCTP payload protocol identifiers *are* IANA's, and are -# used from pcapkit.const.sctp.payload_protocol_identifier rather than -# duplicated here. +# PDUKind and Criticality are closed ASN.1 ENUMERATED types with no extension +# marker, so a lookup for a value neither declares is a bug, not a version +# skew -- they stay hand-written here rather than generated, matching #877's +# ruling for the closed mh.py helper enums. # -# The two large enumerations below were generated from pycrate 0.8.1's -# ``NGAP_Constants`` at authoring time and pasted in. Nothing at import time -# needs pycrate to define them. +# ProcedureCode and ProtocolIE are the other shape: 3GPP TS 38.413 keeps +# assigning new elementary procedures and IEs to both, but neither is an IANA +# registry with a crawlable page -- it is a PDF, with no CSV or HTML kept in +# step with it. GitHub issue #880's owner ruling sources them from |pycrate|_'s +# own compiled specification instead (pycrate_asn1dir.NGAP's NGAP_Constants), +# which is the same optional dependency this module already needs installed to +# decode NGAP at all. They therefore live under pcapkit.const.ngap, generated +# by pcapkit.vendor.ngap.procedure_code and pcapkit.vendor.ngap.protocol_ie, +# and are merely re-exported here under their historical names for backward +# compatibility. Nothing at import time needs pycrate to define them -- only +# the vendor crawler that generates their const module does, not this module +# nor the generated one. +# +# The SCTP payload protocol identifiers *are* IANA's, and are used from +# pcapkit.const.sctp.payload_protocol_identifier rather than duplicated here. ############################################################################## @@ -215,10 +230,12 @@ class Criticality(IntEnum): def get(key: 'int | str | Criticality') -> 'Criticality': """Backport support for original codes. - Unlike :meth:`ProcedureCode.get` and :meth:`ProtocolIE.get`, this takes - no ``default``: those two extend themselves through - :func:`~aenum.extend_enum` when a newer specification names a code this - release does not, which is the right answer for a registry that grows. + Unlike :meth:`ProcedureCode.get ` + and :meth:`ProtocolIE.get `, + this takes no ``default``. Those two answer an in-range value they have + not seen with a throwaway, non-registering member (see + :meth:`~pcapkit.corekit.enum.EnumRegistry._unregistered_member`), which + is the right answer for a registry 3GPP keeps assigning new codes to. ``Criticality`` cannot grow. It is an ASN.1 ``ENUMERATED`` with no extension marker, so a fourth value is unencodable and a lookup for one is a bug rather than a version skew -- see :meth:`_missing_`. A @@ -263,601 +280,22 @@ def _missing_(cls, value: 'int') -> 'NoReturn': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) -class ProcedureCode(IntEnum): - """[ProcedureCode] NGAP elementary procedure codes, 3GPP TS 38.413.""" - - AMFConfigurationUpdate = 0 - AMFStatusIndication = 1 - CellTrafficTrace = 2 - DeactivateTrace = 3 - DownlinkNASTransport = 4 - DownlinkNonUEAssociatedNRPPaTransport = 5 - DownlinkRANConfigurationTransfer = 6 - DownlinkRANStatusTransfer = 7 - DownlinkUEAssociatedNRPPaTransport = 8 - ErrorIndication = 9 - HandoverCancel = 10 - HandoverNotification = 11 - HandoverPreparation = 12 - HandoverResourceAllocation = 13 - InitialContextSetup = 14 - InitialUEMessage = 15 - LocationReportingControl = 16 - LocationReportingFailureIndication = 17 - LocationReport = 18 - NASNonDeliveryIndication = 19 - NGReset = 20 - NGSetup = 21 - OverloadStart = 22 - OverloadStop = 23 - Paging = 24 - PathSwitchRequest = 25 - PDUSessionResourceModify = 26 - PDUSessionResourceModifyIndication = 27 - PDUSessionResourceRelease = 28 - PDUSessionResourceSetup = 29 - PDUSessionResourceNotify = 30 - PrivateMessage = 31 - PWSCancel = 32 - PWSFailureIndication = 33 - PWSRestartIndication = 34 - RANConfigurationUpdate = 35 - RerouteNASRequest = 36 - RRCInactiveTransitionReport = 37 - TraceFailureIndication = 38 - TraceStart = 39 - UEContextModification = 40 - UEContextRelease = 41 - UEContextReleaseRequest = 42 - UERadioCapabilityCheck = 43 - UERadioCapabilityInfoIndication = 44 - UETNLABindingRelease = 45 - UplinkNASTransport = 46 - UplinkNonUEAssociatedNRPPaTransport = 47 - UplinkRANConfigurationTransfer = 48 - UplinkRANStatusTransfer = 49 - UplinkUEAssociatedNRPPaTransport = 50 - WriteReplaceWarning = 51 - SecondaryRATDataUsageReport = 52 - UplinkRIMInformationTransfer = 53 - DownlinkRIMInformationTransfer = 54 - RetrieveUEInformation = 55 - UEInformationTransfer = 56 - RANCPRelocationIndication = 57 - UEContextResume = 58 - UEContextSuspend = 59 - UERadioCapabilityIDMapping = 60 - HandoverSuccess = 61 - UplinkRANEarlyStatusTransfer = 62 - DownlinkRANEarlyStatusTransfer = 63 - AMFCPRelocationIndication = 64 - ConnectionEstablishmentIndication = 65 - BroadcastSessionModification = 66 - BroadcastSessionRelease = 67 - BroadcastSessionSetup = 68 - DistributionSetup = 69 - DistributionRelease = 70 - MulticastSessionActivation = 71 - MulticastSessionDeactivation = 72 - MulticastSessionUpdate = 73 - MulticastGroupPaging = 74 - BroadcastSessionReleaseRequired = 75 - TimingSynchronisationStatus = 76 - TimingSynchronisationStatusReport = 77 - MTCommunicationHandling = 78 - RANPagingRequest = 79 - BroadcastSessionTransport = 80 - - @staticmethod - def get(key: 'int | str | ProcedureCode', default: 'int' = -1) -> 'ProcedureCode': - """Backport support for original codes. - - Args: - key: Key to get enum item. - default: Default value if not found. - - :meta private: - """ - if isinstance(key, ProcedureCode): - return key - if isinstance(key, int): - return ProcedureCode(key) - if key not in ProcedureCode._member_map_: # pylint: disable=no-member - return extend_enum(ProcedureCode, key, default) - return ProcedureCode[key] # type: ignore[misc] - - @classmethod - def _missing_(cls, value: 'int') -> 'ProcedureCode': - """Lookup function used when value is not found. - - Args: - value: Value to get enum item. - - """ - if not (isinstance(value, int) and 0 <= value <= 255): - raise ValueError('%r is not a valid %s' % (value, cls.__name__)) - return extend_enum(cls, 'Unassigned_%d' % value, value) - - -class ProtocolIE(IntEnum): - """[ProtocolIE-ID] NGAP protocol IE identifiers, 3GPP TS 38.413. - - An IE's ID name and the name of the open type its value is keyed under - differ in places -- IE 21 is ``id-DefaultPagingDRX`` but its value arrives - keyed ``PagingDRX`` -- so - :attr:`IE.type ` carries - the latter alongside this. - - """ - - AllowedNSSAI = 0 - AMFName = 1 - AMFOverloadResponse = 2 - AMFSetID = 3 - AMF_TNLAssociationFailedToSetupList = 4 - AMF_TNLAssociationSetupList = 5 - AMF_TNLAssociationToAddList = 6 - AMF_TNLAssociationToRemoveList = 7 - AMF_TNLAssociationToUpdateList = 8 - AMFTrafficLoadReductionIndication = 9 - AMF_UE_NGAP_ID = 10 - AssistanceDataForPaging = 11 - BroadcastCancelledAreaList = 12 - BroadcastCompletedAreaList = 13 - CancelAllWarningMessages = 14 - Cause = 15 - CellIDListForRestart = 16 - ConcurrentWarningMessageInd = 17 - CoreNetworkAssistanceInformationForInactive = 18 - CriticalityDiagnostics = 19 - DataCodingScheme = 20 - DefaultPagingDRX = 21 - DirectForwardingPathAvailability = 22 - EmergencyAreaIDListForRestart = 23 - EmergencyFallbackIndicator = 24 - EUTRA_CGI = 25 - FiveG_S_TMSI = 26 - GlobalRANNodeID = 27 - GUAMI = 28 - HandoverType = 29 - IMSVoiceSupportIndicator = 30 - IndexToRFSP = 31 - InfoOnRecommendedCellsAndRANNodesForPaging = 32 - LocationReportingRequestType = 33 - MaskedIMEISV = 34 - MessageIdentifier = 35 - MobilityRestrictionList = 36 - NASC = 37 - NAS_PDU = 38 - NASSecurityParametersFromNGRAN = 39 - NewAMF_UE_NGAP_ID = 40 - NewSecurityContextInd = 41 - NGAP_Message = 42 - NGRAN_CGI = 43 - NGRANTraceID = 44 - NR_CGI = 45 - NRPPa_PDU = 46 - NumberOfBroadcastsRequested = 47 - OldAMF = 48 - OverloadStartNSSAIList = 49 - PagingDRX = 50 - PagingOrigin = 51 - PagingPriority = 52 - PDUSessionResourceAdmittedList = 53 - PDUSessionResourceFailedToModifyListModRes = 54 - PDUSessionResourceFailedToSetupListCxtRes = 55 - PDUSessionResourceFailedToSetupListHOAck = 56 - PDUSessionResourceFailedToSetupListPSReq = 57 - PDUSessionResourceFailedToSetupListSURes = 58 - PDUSessionResourceHandoverList = 59 - PDUSessionResourceListCxtRelCpl = 60 - PDUSessionResourceListHORqd = 61 - PDUSessionResourceModifyListModCfm = 62 - PDUSessionResourceModifyListModInd = 63 - PDUSessionResourceModifyListModReq = 64 - PDUSessionResourceModifyListModRes = 65 - PDUSessionResourceNotifyList = 66 - PDUSessionResourceReleasedListNot = 67 - PDUSessionResourceReleasedListPSAck = 68 - PDUSessionResourceReleasedListPSFail = 69 - PDUSessionResourceReleasedListRelRes = 70 - PDUSessionResourceSetupListCxtReq = 71 - PDUSessionResourceSetupListCxtRes = 72 - PDUSessionResourceSetupListHOReq = 73 - PDUSessionResourceSetupListSUReq = 74 - PDUSessionResourceSetupListSURes = 75 - PDUSessionResourceToBeSwitchedDLList = 76 - PDUSessionResourceSwitchedList = 77 - PDUSessionResourceToReleaseListHOCmd = 78 - PDUSessionResourceToReleaseListRelCmd = 79 - PLMNSupportList = 80 - PWSFailedCellIDList = 81 - RANNodeName = 82 - RANPagingPriority = 83 - RANStatusTransfer_TransparentContainer = 84 - RAN_UE_NGAP_ID = 85 - RelativeAMFCapacity = 86 - RepetitionPeriod = 87 - ResetType = 88 - RoutingID = 89 - RRCEstablishmentCause = 90 - RRCInactiveTransitionReportRequest = 91 - RRCState = 92 - SecurityContext = 93 - SecurityKey = 94 - SerialNumber = 95 - ServedGUAMIList = 96 - SliceSupportList = 97 - SONConfigurationTransferDL = 98 - SONConfigurationTransferUL = 99 - SourceAMF_UE_NGAP_ID = 100 - SourceToTarget_TransparentContainer = 101 - SupportedTAList = 102 - TAIListForPaging = 103 - TAIListForRestart = 104 - TargetID = 105 - TargetToSource_TransparentContainer = 106 - TimeToWait = 107 - TraceActivation = 108 - TraceCollectionEntityIPAddress = 109 - UEAggregateMaximumBitRate = 110 - UE_associatedLogicalNG_connectionList = 111 - UEContextRequest = 112 - UE_NGAP_IDs = 114 - UEPagingIdentity = 115 - UEPresenceInAreaOfInterestList = 116 - UERadioCapability = 117 - UERadioCapabilityForPaging = 118 - UESecurityCapabilities = 119 - UnavailableGUAMIList = 120 - UserLocationInformation = 121 - WarningAreaList = 122 - WarningMessageContents = 123 - WarningSecurityInfo = 124 - WarningType = 125 - AdditionalUL_NGU_UP_TNLInformation = 126 - DataForwardingNotPossible = 127 - DL_NGU_UP_TNLInformation = 128 - NetworkInstance = 129 - PDUSessionAggregateMaximumBitRate = 130 - PDUSessionResourceFailedToModifyListModCfm = 131 - PDUSessionResourceFailedToSetupListCxtFail = 132 - PDUSessionResourceListCxtRelReq = 133 - PDUSessionType = 134 - QosFlowAddOrModifyRequestList = 135 - QosFlowSetupRequestList = 136 - QosFlowToReleaseList = 137 - SecurityIndication = 138 - UL_NGU_UP_TNLInformation = 139 - UL_NGU_UP_TNLModifyList = 140 - WarningAreaCoordinates = 141 - PDUSessionResourceSecondaryRATUsageList = 142 - HandoverFlag = 143 - SecondaryRATUsageInformation = 144 - PDUSessionResourceReleaseResponseTransfer = 145 - RedirectionVoiceFallback = 146 - UERetentionInformation = 147 - S_NSSAI = 148 - PSCellInformation = 149 - LastEUTRAN_PLMNIdentity = 150 - MaximumIntegrityProtectedDataRate_DL = 151 - AdditionalDLForwardingUPTNLInformation = 152 - AdditionalDLUPTNLInformationForHOList = 153 - AdditionalNGU_UP_TNLInformation = 154 - AdditionalDLQosFlowPerTNLInformation = 155 - SecurityResult = 156 - ENDC_SONConfigurationTransferDL = 157 - ENDC_SONConfigurationTransferUL = 158 - OldAssociatedQosFlowList_ULendmarkerexpected = 159 - CNTypeRestrictionsForEquivalent = 160 - CNTypeRestrictionsForServing = 161 - NewGUAMI = 162 - ULForwarding = 163 - ULForwardingUP_TNLInformation = 164 - CNAssistedRANTuning = 165 - CommonNetworkInstance = 166 - NGRAN_TNLAssociationToRemoveList = 167 - TNLAssociationTransportLayerAddressNGRAN = 168 - EndpointIPAddressAndPort = 169 - LocationReportingAdditionalInfo = 170 - SourceToTarget_AMFInformationReroute = 171 - AdditionalULForwardingUPTNLInformation = 172 - SCTP_TLAs = 173 - SelectedPLMNIdentity = 174 - RIMInformationTransfer = 175 - GUAMIType = 176 - SRVCCOperationPossible = 177 - TargetRNC_ID = 178 - RAT_Information = 179 - ExtendedRATRestrictionInformation = 180 - QosMonitoringRequest = 181 - SgNB_UE_X2AP_ID = 182 - AdditionalRedundantDL_NGU_UP_TNLInformation = 183 - AdditionalRedundantDLQosFlowPerTNLInformation = 184 - AdditionalRedundantNGU_UP_TNLInformation = 185 - AdditionalRedundantUL_NGU_UP_TNLInformation = 186 - CNPacketDelayBudgetDL = 187 - CNPacketDelayBudgetUL = 188 - ExtendedPacketDelayBudget = 189 - RedundantCommonNetworkInstance = 190 - RedundantDL_NGU_TNLInformationReused = 191 - RedundantDL_NGU_UP_TNLInformation = 192 - RedundantDLQosFlowPerTNLInformation = 193 - RedundantQosFlowIndicator = 194 - RedundantUL_NGU_UP_TNLInformation = 195 - TSCTrafficCharacteristics = 196 - RedundantPDUSessionInformation = 197 - UsedRSNInformation = 198 - IAB_Authorized = 199 - IAB_Supported = 200 - IABNodeIndication = 201 - NB_IoT_PagingDRX = 202 - NB_IoT_Paging_eDRXInfo = 203 - NB_IoT_DefaultPagingDRX = 204 - Enhanced_CoverageRestriction = 205 - Extended_ConnectedTime = 206 - PagingAssisDataforCEcapabUE = 207 - WUS_Assistance_Information = 208 - UE_DifferentiationInfo = 209 - NB_IoT_UEPriority = 210 - UL_CP_SecurityInformation = 211 - DL_CP_SecurityInformation = 212 - TAI = 213 - UERadioCapabilityForPagingOfNB_IoT = 214 - LTEV2XServicesAuthorized = 215 - NRV2XServicesAuthorized = 216 - LTEUESidelinkAggregateMaximumBitrate = 217 - NRUESidelinkAggregateMaximumBitrate = 218 - PC5QoSParameters = 219 - AlternativeQoSParaSetList = 220 - CurrentQoSParaSetIndex = 221 - CEmodeBrestricted = 222 - EUTRA_PagingeDRXInformation = 223 - CEmodeBSupport_Indicator = 224 - LTEM_Indication = 225 - EndIndication = 226 - EDT_Session = 227 - UECapabilityInfoRequest = 228 - PDUSessionResourceFailedToResumeListRESReq = 229 - PDUSessionResourceFailedToResumeListRESRes = 230 - PDUSessionResourceSuspendListSUSReq = 231 - PDUSessionResourceResumeListRESReq = 232 - PDUSessionResourceResumeListRESRes = 233 - UE_UP_CIoT_Support = 234 - Suspend_Request_Indication = 235 - Suspend_Response_Indication = 236 - RRC_Resume_Cause = 237 - RGLevelWirelineAccessCharacteristics = 238 - W_AGFIdentityInformation = 239 - GlobalTNGF_ID = 240 - GlobalTWIF_ID = 241 - GlobalW_AGF_ID = 242 - UserLocationInformationW_AGF = 243 - UserLocationInformationTNGF = 244 - AuthenticatedIndication = 245 - TNGFIdentityInformation = 246 - TWIFIdentityInformation = 247 - UserLocationInformationTWIF = 248 - DataForwardingResponseERABList = 249 - IntersystemSONConfigurationTransferDL = 250 - IntersystemSONConfigurationTransferUL = 251 - SONInformationReport = 252 - UEHistoryInformationFromTheUE = 253 - ManagementBasedMDTPLMNList = 254 - MDTConfiguration = 255 - PrivacyIndicator = 256 - TraceCollectionEntityURI = 257 - NPN_Support = 258 - NPN_AccessInformation = 259 - NPN_PagingAssistanceInformation = 260 - NPN_MobilityInformation = 261 - TargettoSource_Failure_TransparentContainer = 262 - NID = 263 - UERadioCapabilityID = 264 - UERadioCapability_EUTRA_Format = 265 - DAPSRequestInfo = 266 - DAPSResponseInfoList = 267 - EarlyStatusTransfer_TransparentContainer = 268 - NotifySourceNGRANNode = 269 - ExtendedSliceSupportList = 270 - ExtendedTAISliceSupportList = 271 - ConfiguredTACIndication = 272 - Extended_RANNodeName = 273 - Extended_AMFName = 274 - GlobalCable_ID = 275 - QosMonitoringReportingFrequency = 276 - QosFlowParametersList = 277 - QosFlowFeedbackList = 278 - BurstArrivalTimeDownlink = 279 - ExtendedUEIdentityIndexValue = 280 - PduSessionExpectedUEActivityBehaviour = 281 - MicoAllPLMN = 282 - QosFlowFailedToSetupList = 283 - SourceTNLAddrInfo = 284 - ExtendedReportIntervalMDT = 285 - SourceNodeID = 286 - NRNTNTAIInformation = 287 - UEContextReferenceAtSource = 288 - LastVisitedPSCellList = 289 - IntersystemSONInformationRequest = 290 - IntersystemSONInformationReply = 291 - EnergySavingIndication = 292 - IntersystemResourceStatusUpdate = 293 - SuccessfulHandoverReportList = 294 - MBS_AreaSessionID = 295 - MBS_QoSFlowsToBeSetupList = 296 - MBS_QoSFlowsToBeSetupModList = 297 - MBS_ServiceArea = 298 - MBS_SessionID = 299 - MBS_DistributionReleaseRequestTransfer = 300 - MBS_DistributionSetupRequestTransfer = 301 - MBS_DistributionSetupResponseTransfer = 302 - MBS_DistributionSetupUnsuccessfulTransfer = 303 - MulticastSessionActivationRequestTransfer = 304 - MulticastSessionDeactivationRequestTransfer = 305 - MulticastSessionUpdateRequestTransfer = 306 - MulticastGroupPagingAreaList = 307 - MBS_SupportIndicator = 309 - MBSSessionFailedtoSetupList = 310 - MBSSessionFailedtoSetuporModifyList = 311 - MBSSessionSetupResponseList = 312 - MBSSessionSetuporModifyResponseList = 313 - MBSSessionSetupFailureTransfer = 314 - MBSSessionSetupRequestTransfer = 315 - MBSSessionSetupResponseTransfer = 316 - MBSSessionToReleaseList = 317 - MBSSessionSetupRequestList = 318 - MBSSessionSetuporModifyRequestList = 319 - MBS_ActiveSessionInformation_SourcetoTargetList = 323 - MBS_ActiveSessionInformation_TargettoSourceList = 324 - OnboardingSupport = 325 - TimeSyncAssistanceInfo = 326 - SurvivalTime = 327 - QMCConfigInfo = 328 - QMCDeactivation = 329 - PDUSessionPairID = 331 - NR_PagingeDRXInformation = 332 - RedCapIndication = 333 - TargetNSSAIInformation = 334 - UESliceMaximumBitRateList = 335 - M4ReportAmount = 336 - M5ReportAmount = 337 - M6ReportAmount = 338 - M7ReportAmount = 339 - IncludeBeamMeasurementsIndication = 340 - ExcessPacketDelayThresholdConfiguration = 341 - PagingCause = 342 - PagingCauseIndicationForVoiceService = 343 - PEIPSassistanceInformation = 344 - FiveG_ProSeAuthorized = 345 - FiveG_ProSeUEPC5AggregateMaximumBitRate = 346 - FiveG_ProSePC5QoSParameters = 347 - MBSSessionModificationFailureTransfer = 348 - MBSSessionModificationRequestTransfer = 349 - MBSSessionModificationResponseTransfer = 350 - MBS_QoSFlowToReleaseList = 351 - MBS_SessionTNLInfo5GC = 352 - TAINSAGSupportList = 353 - SourceNodeTNLAddrInfo = 354 - NGAPIESupportInformationRequestList = 355 - NGAPIESupportInformationResponseList = 356 - MBS_SessionFSAIDList = 357 - MBSSessionReleaseResponseTransfer = 358 - ManagementBasedMDTPLMNModificationList = 359 - EarlyMeasurement = 360 - BeamMeasurementsReportConfiguration = 361 - HFCNode_ID_new = 362 - GlobalCable_ID_new = 363 - TargetHomeENB_ID = 364 - HashedUEIdentityIndexValue = 365 - ExtendedMobilityInformation = 366 - NetworkControlledRepeaterAuthorized = 367 - AdditionalCancelledlocationReportingReferenceIDList = 368 - Selected_Target_SNPN_Identity = 369 - EquivalentSNPNsList = 370 - SelectedNID = 371 - SupportedUETypeList = 372 - AerialUEsubscriptionInformation = 373 - NR_A2X_ServicesAuthorized = 374 - LTE_A2X_ServicesAuthorized = 375 - NR_A2X_UE_PC5_AggregateMaximumBitRate = 376 - LTE_A2X_UE_PC5_AggregateMaximumBitRate = 377 - A2X_PC5_QoS_Parameters = 378 - FiveGProSeLayer2Multipath = 379 - FiveGProSeLayer2UEtoUERelay = 380 - FiveGProSeLayer2UEtoUERemote = 381 - CandidateRelayUEInformationList = 382 - SuccessfulPSCellChangeReportList = 383 - IntersystemMobilityFailureforVoiceFallback = 384 - TargetCellCRNTI = 385 - TimeSinceFailure = 386 - RANTimingSynchronisationStatusInfo = 387 - RAN_TSSRequestType = 388 - RAN_TSSScope = 389 - ClockQualityReportingControlInfo = 390 - RANfeedbacktype = 391 - QoSFlowTSCList = 392 - TSCTrafficCharacteristicsFeedback = 393 - DownlinkTLContainer = 394 - UplinkTLContainer = 395 - ANPacketDelayBudgetUL = 396 - QosFlowAdditionalInfoList = 397 - AssistanceInformationQoE_Meas = 398 - MBSCommServiceType = 399 - MobileIAB_Authorized = 400 - MobileIAB_MTUserLocationInformation = 401 - MobileIABNodeIndication = 402 - NoPDUSessionIndication = 403 - MobileIAB_Supported = 404 - CN_MT_CommunicationHandling = 405 - FiveGCAction = 406 - PagingPolicyDifferentiation = 407 - DL_Signalling = 408 - PNI_NPN_AreaScopeofMDT = 409 - PNI_NPNBasedMDT = 410 - SNPN_CellBasedMDT = 411 - SNPN_TAIBasedMDT = 412 - SNPN_BasedMDT = 413 - Partially_Allowed_NSSAI = 414 - AssociatedSessionID = 415 - MBS_AssistanceInformation = 416 - BroadcastTransportFailureTransfer = 417 - BroadcastTransportRequestTransfer = 418 - BroadcastTransportResponseTransfer = 419 - TimeBasedHandoverInformation = 420 - DLDiscarding = 421 - PDUsetQoSParameters = 422 - PDUSetbasedHandlingIndicator = 423 - N6JitterInformation = 424 - ECNMarkingorCongestionInformationReportingRequest = 425 - ECNMarkingorCongestionInformationReportingStatus = 426 - ERedCapIndication = 427 - XrDeviceWith2Rx = 428 - UserPlaneErrorIndicator = 429 - SLPositioningRangingServiceInfo = 430 - PDUSessionListMTCommHReq = 431 - MaximumDataBurstVolume = 432 - MN_only_MDT_collection = 433 - MBS_NGUFailureIndication = 434 - UserPlaneFailureIndication = 435 - UserPlaneFailureIndicationReport = 436 - SourceSN_to_TargetSN_QMCInfo = 437 - QoERVQoEReportingPaths = 438 - UserLocationInformationN3IWF_without_PortNumber = 439 - AUN3DeviceAccessInfo = 440 - TAIMBSSupportList = 441 - ExtendedBackupAMFName = 442 - ExtendedOldAMF = 443 - - @staticmethod - def get(key: 'int | str | ProtocolIE', default: 'int' = -1) -> 'ProtocolIE': - """Backport support for original codes. - - Args: - key: Key to get enum item. - default: Default value if not found. - - :meta private: - """ - if isinstance(key, ProtocolIE): - return key - if isinstance(key, int): - return ProtocolIE(key) - if key not in ProtocolIE._member_map_: # pylint: disable=no-member - return extend_enum(ProtocolIE, key, default) - return ProtocolIE[key] # type: ignore[misc] - - @classmethod - def _missing_(cls, value: 'int') -> 'ProtocolIE': - """Lookup function used when value is not found. - - Args: - value: Value to get enum item. - - """ - if not (isinstance(value, int) and 0 <= value <= 65535): - raise ValueError('%r is not a valid %s' % (value, cls.__name__)) - return extend_enum(cls, 'Unassigned_%d' % value, value) +#: Re-exported from :class:`pcapkit.const.ngap.procedure_code.ProcedureCode` +#: under this module's historical name -- see the ``Enumerations`` comment +#: above. A plain alias, not a subclass: :mod:`aenum` refuses to extend an +#: enumeration that already has members, and this one carries 81 of them. +ProcedureCode = Enum_ProcedureCode + +#: Re-exported from :class:`pcapkit.const.ngap.protocol_ie.ProtocolIE` under +#: this module's historical name -- see :data:`ProcedureCode` above for why +#: this is an alias rather than a subclass. +#: +#: An IE's ID name and the name of the open type its value is keyed under +#: differ in places -- IE 21 is ``id-DefaultPagingDRX`` but its value arrives +#: keyed ``PagingDRX`` -- so +#: :attr:`IE.type ` carries +#: the latter alongside this. +ProtocolIE = Enum_ProtocolIE ############################################################################## diff --git a/pcapkit/vendor/__init__.py b/pcapkit/vendor/__init__.py index 8eae956d0..957e90f98 100644 --- a/pcapkit/vendor/__init__.py +++ b/pcapkit/vendor/__init__.py @@ -49,6 +49,7 @@ from pcapkit.vendor.ipx import * from pcapkit.vendor.l2tp import * from pcapkit.vendor.mh import * +from pcapkit.vendor.ngap import * from pcapkit.vendor.ospf import * from pcapkit.vendor.sctp import * from pcapkit.vendor.tcp import * @@ -97,6 +98,8 @@ 'MH_FlowBindingACKStatus', 'MH_FlowBindingAction', 'MH_QoSAttribute', 'MH_LMAControlledMAGSuboption', 'MH_LLACode', 'MH_CGAType', 'MH_CGAExtension', 'MH_CGASec', 'MH_BindingError', + # NGAP + 'NGAP_ProcedureCode', 'NGAP_ProtocolIE', # OSPF 'OSPF_Authentication', 'OSPF_Packet', # SCTP diff --git a/pcapkit/vendor/ngap/__init__.py b/pcapkit/vendor/ngap/__init__.py new file mode 100644 index 000000000..768d94c37 --- /dev/null +++ b/pcapkit/vendor/ngap/__init__.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# pylint: disable=unused-import +""":class:`~pcapkit.protocols.application.ngap.NGAP` Vendor Crawlers +======================================================================== + +.. module:: pcapkit.vendor.ngap + +This module contains all vendor crawlers of +:class:`~pcapkit.protocols.application.ngap.NGAP` implementations. Available +enumerations include: + +.. list-table:: + + * - :class:`NGAP_ProcedureCode ` + - NGAP Elementary Procedure Codes [*]_ + * - :class:`NGAP_ProtocolIE ` + - NGAP Protocol IE Identifiers [*]_ + +Both are sourced from |pycrate|_'s compiled NGAP specification rather than a +network registry -- see :mod:`pcapkit.vendor.ngap.procedure_code`'s module +docstring for why, and GitHub issue #880 for the ruling. + +.. [*] 3GPP TS 38.413 +.. [*] 3GPP TS 38.413 + +.. |pycrate| replace:: ``pycrate`` +.. _pycrate: https://github.com/pycrate-org/pycrate + +""" + +from pcapkit.vendor.ngap.procedure_code import ProcedureCode as NGAP_ProcedureCode +from pcapkit.vendor.ngap.protocol_ie import ProtocolIE as NGAP_ProtocolIE + +__all__ = ['NGAP_ProcedureCode', 'NGAP_ProtocolIE'] diff --git a/pcapkit/vendor/ngap/procedure_code.py b/pcapkit/vendor/ngap/procedure_code.py new file mode 100644 index 000000000..cdbbd787f --- /dev/null +++ b/pcapkit/vendor/ngap/procedure_code.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +"""NGAP Elementary Procedure Codes +===================================== + +.. module:: pcapkit.vendor.ngap.procedure_code + +This module contains the vendor crawler for **NGAP Elementary Procedure +Codes**, which is automatically generating +:class:`pcapkit.const.ngap.procedure_code.ProcedureCode`. + +GitHub issue #880's owner ruling: unlike every other crawler in this package, +the source of truth here is not a network registry :mod:`requests` can fetch +-- 3GPP publishes TS 38.413 as a PDF, with no CSV or HTML IANA-style registry +kept in step with it. |pycrate|_ already carries the up-to-date assignment as +compiled ASN.1, at ``pycrate_asn1dir/NGAP.py``'s ``NGAP_Constants`` class (one +``INT`` value object per ``id-`` constant, tagged with the ``ProcedureCode`` +or ``ProtocolIE-ID`` open type it belongs to) -- and it is the very same +dependency :mod:`pcapkit.protocols.application.ngap` already needs installed to +decode NGAP at all. So this crawler reads that already-installed package +directly instead of fetching anything over the network: :attr:`~ProcedureCode.LINK` +is :obj:`None` and :meth:`ProcedureCode._request` imports ``pycrate_asn1dir.NGAP`` +in place of :meth:`~pcapkit.vendor.default.Vendor._request`'s HTTP round trip. +No network access happens at generation time -- only, if at all, whenever +``pip install pypcapkit[NGAP]`` last ran. + +:class:`~pcapkit.const.ngap.procedure_code.ProcedureCode` is a genuinely open +registry -- 3GPP keeps assigning new elementary procedures to TS 38.413 -- so, +unlike the closed :mod:`pcapkit.protocols.internet.mh` enums GitHub issue #877 +ruled on, an in-range value this crawler has not (yet) seen is not a bug to +raise on: :meth:`~pcapkit.const.ngap.procedure_code.ProcedureCode._missing_` +answers it with a throwaway, non-registering member instead (see +:meth:`pcapkit.corekit.enum.EnumRegistry._unregistered_member`), which is what +stops two different unrecognised keys from aliasing onto the same member -- +the defect GitHub issue #880 exists to fix. + +.. |pycrate| replace:: ``pycrate`` +.. _pycrate: https://github.com/pycrate-org/pycrate + +""" +import collections +import sys +from typing import TYPE_CHECKING + +from pcapkit.vendor.default import Vendor + +if TYPE_CHECKING: + from collections import Counter + from typing import Callable + +__all__ = ['ProcedureCode'] + +#: The ``NGAP-CommonDataTypes`` open type this crawler claims. Matched against +#: each ``NGAP_Constants`` value object's own ``_typeref.called`` -- see +#: :meth:`ProcedureCode._request` for why the private attribute is read +#: directly rather than through a public accessor. +_TYPEREF = ('NGAP-CommonDataTypes', 'ProcedureCode') + +#: Default constant template of enumerate registry, sourced from pycrate +#: rather than an IANA CSV -- see the module docstring. +LINE = lambda NAME, DOCS, FLAG, ENUM, MODL: f'''\ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long +"""{(name := DOCS.split(' [', maxsplit=1)[0])} +{'=' * (len(name) + 6)} + +.. module:: {MODL.replace('vendor', 'const')} + +This module contains the constant enumeration for **{name}**, +which is automatically generated from :class:`{MODL}.{NAME}`. + +""" + +from aenum import IntEnum + +from pcapkit.corekit.enum import EnumRegistry + +__all__ = ['{NAME}'] + + +class {NAME}(EnumRegistry, IntEnum): + """[{NAME}] {DOCS}""" + + {ENUM} + + @classmethod + def _missing_(cls, value: 'int') -> '{NAME}': + """Lookup function used when value is not found. + + Args: + value: Value to get enum item. + + """ + if not ({FLAG}): + raise ValueError('%r is not a valid %s' % (value, cls.__name__)) + return cls._unregistered_member(value, 'Unassigned') +'''.strip() # type: Callable[[str, str, str, str, str], str] + + +class ProcedureCode(Vendor): + """NGAP elementary procedure codes, 3GPP TS 38.413.""" + + #: Value limit checker, matching ``pycrate_asn1dir.NGAP``'s own + #: ``ProcedureCode._const_val`` (``ASN1RangeInt(lb=0, ub=255)``): a + #: ``ProcedureCode`` is encoded as one aligned-PER octet. + FLAG = 'isinstance(value, int) and 0 <= value <= 255' + + # No network registry to link to -- see the module docstring. Left + # unset rather than assigned ``None`` explicitly: ``Vendor.LINK`` already + # defaults to :obj:`None`, and a fresh assignment here would need the + # same ``# type: ignore[assignment]`` the base class's own declaration + # carries, for narrowing a ``str``-annotated attribute to ``None`` again. + + def _request(self) -> 'list[tuple[str, int]]': # type: ignore[override] + """Read the assignment from |pycrate|_'s compiled NGAP specification. + + Every ``NGAP_Constants`` value object carries a private ``_typeref`` + naming which ``NGAP-CommonDataTypes`` open type it belongs to (``.called``, + a ``(module, type)`` pair), a private ``_name`` holding the ASN.1 + identifier verbatim (``'id-AMFConfigurationUpdate'``), and a private + ``_val`` holding the assigned integer. None of the three has a public + accessor that survives this far: :meth:`~pycrate_asn1rt.asnobj.ASN1Obj. + get_typeref` resolves to the *referenced type object* rather than its + ``(module, name)`` pair, and that object's own :meth:`get_name` reads + :obj:`None` here despite :func:`repr` showing the name -- measured + against pycrate 0.8.1. Reading the private attributes directly is + therefore the only way to recover what this crawler needs, which is a + property of parsing pycrate's own compiled representation rather than + a shortcut around a public API this package should have used instead. + + Returns: + One ``(python_identifier, value)`` pair per ``ProcedureCode`` + assignment, in the compiled specification's own declaration + order -- already ascending by value, measured against pycrate + 0.8.1. + + Raises: + ImportError: If the optional ``pycrate`` dependency is not + installed. + + """ + try: + from pycrate_asn1dir.NGAP import NGAP_Constants + except ImportError as error: + raise ImportError( + "pcapkit.vendor.ngap.procedure_code needs the optional 'pycrate' " + "dependency installed to source NGAP's ProcedureCode assignments " + "from pycrate_asn1dir.NGAP (pip install pypcapkit[NGAP]); see " + 'GitHub issue #880') from error + + entries = [] # type: list[tuple[str, int]] + for obj in NGAP_Constants._all_: # pylint: disable=protected-access + typeref = obj._typeref # pylint: disable=protected-access + if typeref is None or typeref.called != _TYPEREF: + continue + + raw_name = obj._name # pylint: disable=protected-access + if not raw_name.startswith('id-'): + continue + name = raw_name[3:].replace('-', '_') + + entries.append((name, obj._val)) # pylint: disable=protected-access + return entries + + def count(self, data: 'list[tuple[str, int]]') -> 'Counter[str]': # type: ignore[override] + """No duplicate-name bookkeeping needed. + + Unlike a hand-scraped CSV, |pycrate|_'s compiled specification names + each assignment once -- measured, zero duplicate names and zero + duplicate values across all 81 -- so :meth:`~pcapkit.vendor.default. + Vendor.rename`'s per-name counter has nothing to disambiguate and this + crawler never calls it. + + """ + return collections.Counter() + + def process(self, data: 'list[tuple[str, int]]') -> 'list[str]': # type: ignore[override] + """Render each assignment as a member declaration. + + Args: + data: The ``(name, value)`` pairs :meth:`_request` read from + |pycrate|_. + + Returns: + One ``NAME = VALUE`` source line per assignment. + + """ + return [f'{name} = {value}' for name, value in data] + + def context(self, data: 'list[tuple[str, int]]') -> 'str': # type: ignore[override] + """Generate constant context. + + Args: + data: The ``(name, value)`` pairs :meth:`_request` read from + |pycrate|_. + + Returns: + Constant context. + + """ + ENUM = '\n\n '.join(self.process(data)).strip() + return LINE(self.NAME, self.DOCS, self.FLAG, ENUM, self.__module__) + + +if __name__ == '__main__': + sys.exit(ProcedureCode()) # type: ignore[arg-type] diff --git a/pcapkit/vendor/ngap/protocol_ie.py b/pcapkit/vendor/ngap/protocol_ie.py new file mode 100644 index 000000000..07313d797 --- /dev/null +++ b/pcapkit/vendor/ngap/protocol_ie.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +"""NGAP Protocol IE Identifiers +================================== + +.. module:: pcapkit.vendor.ngap.protocol_ie + +This module contains the vendor crawler for **NGAP Protocol IE Identifiers**, +which is automatically generating +:class:`pcapkit.const.ngap.protocol_ie.ProtocolIE`. + +See :mod:`pcapkit.vendor.ngap.procedure_code`'s module docstring for why this +crawler's source is |pycrate|_'s already-installed, compiled NGAP +specification rather than a network registry, and why an in-range value it has +not seen answers through :meth:`~pcapkit.corekit.enum.EnumRegistry. +_unregistered_member` rather than raising or minting a permanent, shared-value +member -- the same GitHub issue #880 ruling, applied to the sibling registry. + +.. |pycrate| replace:: ``pycrate`` +.. _pycrate: https://github.com/pycrate-org/pycrate + +""" +import collections +import sys +from typing import TYPE_CHECKING + +from pcapkit.vendor.default import Vendor + +if TYPE_CHECKING: + from collections import Counter + from typing import Callable + +__all__ = ['ProtocolIE'] + +#: The ``NGAP-CommonDataTypes`` open type this crawler claims. Matched against +#: each ``NGAP_Constants`` value object's own ``_typeref.called`` -- see +#: :meth:`pcapkit.vendor.ngap.procedure_code.ProcedureCode._request` for why +#: the private attribute is read directly rather than through a public +#: accessor. +_TYPEREF = ('NGAP-CommonDataTypes', 'ProtocolIE-ID') + +#: Default constant template of enumerate registry, sourced from pycrate +#: rather than an IANA CSV -- see :mod:`pcapkit.vendor.ngap.procedure_code`. +LINE = lambda NAME, DOCS, FLAG, ENUM, MODL: f'''\ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long +"""{(name := DOCS.split(' [', maxsplit=1)[0])} +{'=' * (len(name) + 6)} + +.. module:: {MODL.replace('vendor', 'const')} + +This module contains the constant enumeration for **{name}**, +which is automatically generated from :class:`{MODL}.{NAME}`. + +""" + +from aenum import IntEnum + +from pcapkit.corekit.enum import EnumRegistry + +__all__ = ['{NAME}'] + + +class {NAME}(EnumRegistry, IntEnum): + """[{NAME}] {DOCS}""" + + {ENUM} + + @classmethod + def _missing_(cls, value: 'int') -> '{NAME}': + """Lookup function used when value is not found. + + Args: + value: Value to get enum item. + + """ + if not ({FLAG}): + raise ValueError('%r is not a valid %s' % (value, cls.__name__)) + return cls._unregistered_member(value, 'Unassigned') +'''.strip() # type: Callable[[str, str, str, str, str], str] + + +class ProtocolIE(Vendor): + """NGAP protocol IE identifiers, 3GPP TS 38.413.""" + + #: Value limit checker, matching ``pycrate_asn1dir.NGAP``'s own + #: ``ProtocolIE_ID._const_val`` (``ASN1RangeInt(lb=0, ub=65535)``): a + #: ``ProtocolIE-ID`` is encoded as one aligned-PER 16-bit field. + FLAG = 'isinstance(value, int) and 0 <= value <= 65535' + + # No network registry to link to -- see + # :mod:`pcapkit.vendor.ngap.procedure_code`'s module docstring. Left + # unset rather than assigned ``None`` explicitly -- see that module's + # own ``ProcedureCode`` for why. + + def _request(self) -> 'list[tuple[str, int]]': # type: ignore[override] + """Read the assignment from |pycrate|_'s compiled NGAP specification. + + See :meth:`pcapkit.vendor.ngap.procedure_code.ProcedureCode._request` + for why the three private attributes read here (``_typeref``, + ``_name``, ``_val``) have no surviving public equivalent. + + Returns: + One ``(python_identifier, value)`` pair per ``ProtocolIE-ID`` + assignment, in the compiled specification's own declaration + order -- already ascending by value, measured against pycrate + 0.8.1. + + Raises: + ImportError: If the optional ``pycrate`` dependency is not + installed. + + """ + try: + from pycrate_asn1dir.NGAP import NGAP_Constants + except ImportError as error: + raise ImportError( + "pcapkit.vendor.ngap.protocol_ie needs the optional 'pycrate' " + "dependency installed to source NGAP's ProtocolIE-ID assignments " + "from pycrate_asn1dir.NGAP (pip install pypcapkit[NGAP]); see " + 'GitHub issue #880') from error + + entries = [] # type: list[tuple[str, int]] + for obj in NGAP_Constants._all_: # pylint: disable=protected-access + typeref = obj._typeref # pylint: disable=protected-access + if typeref is None or typeref.called != _TYPEREF: + continue + + raw_name = obj._name # pylint: disable=protected-access + if not raw_name.startswith('id-'): + continue + name = raw_name[3:].replace('-', '_') + + entries.append((name, obj._val)) # pylint: disable=protected-access + return entries + + def count(self, data: 'list[tuple[str, int]]') -> 'Counter[str]': # type: ignore[override] + """No duplicate-name bookkeeping needed. + + See :meth:`pcapkit.vendor.ngap.procedure_code.ProcedureCode.count` -- + measured, zero duplicate names and zero duplicate values across all + 438. + + """ + return collections.Counter() + + def process(self, data: 'list[tuple[str, int]]') -> 'list[str]': # type: ignore[override] + """Render each assignment as a member declaration. + + Args: + data: The ``(name, value)`` pairs :meth:`_request` read from + |pycrate|_. + + Returns: + One ``NAME = VALUE`` source line per assignment. + + """ + return [f'{name} = {value}' for name, value in data] + + def context(self, data: 'list[tuple[str, int]]') -> 'str': # type: ignore[override] + """Generate constant context. + + Args: + data: The ``(name, value)`` pairs :meth:`_request` read from + |pycrate|_. + + Returns: + Constant context. + + """ + ENUM = '\n\n '.join(self.process(data)).strip() + return LINE(self.NAME, self.DOCS, self.FLAG, ENUM, self.__module__) + + +if __name__ == '__main__': + sys.exit(ProtocolIE()) # type: ignore[arg-type] diff --git a/pyproject.toml b/pyproject.toml index 1ee7975ae..9ffa5fa48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,8 +199,16 @@ PCAP_CT = [ # and then raises ``ModuleNotFoundError`` the moment the engine is used. Upstream # master has fixed it but has not released. PyPCAPFile = [ "pypcapfile; python_version < '3.12'" ] -# for developers -vendor = [ "requests[socks]", "beautifulsoup4[html5lib]" ] +# for developers -- installs pcapkit.vendor's crawlers, run via +# `pcapkit-vendor` to regenerate pcapkit.const. Includes ``pycrate`` so +# pcapkit.vendor.ngap's two crawlers (#880) can run: the owner's ruling on +# this issue is that it belongs here even though it is excluded from +# ``all`` below, for the same size and licence reasons the ``NGAP`` extra +# above explains in full (~238 MB of ``pycrate_asn1dir``, LGPL-2.1+ against +# this package's BSD-3-Clause) -- a developer opting into ``vendor`` has +# already accepted a far heavier and more permissively-licensed dependency +# set than an end user installing ``all`` ever would. +vendor = [ "requests[socks]", "beautifulsoup4[html5lib]", "pycrate" ] # NB: ``pypcap`` is *not* included here. It is an sdist-only C extension, so # ``pip install pypcapkit[all]`` would require a compiler and the libpcap # development files on every platform, and fails where only the header is diff --git a/tests/const/test_const_enum_builtin_parity.py b/tests/const/test_const_enum_builtin_parity.py index b7a374469..8be31a1ba 100644 --- a/tests/const/test_const_enum_builtin_parity.py +++ b/tests/const/test_const_enum_builtin_parity.py @@ -378,11 +378,15 @@ def test_the_sweep_size_is_pinned(self) -> None: # upper()`` outliers #647 fixed above, and it never reached # tests.const.test_const_enum_get's narrower (IntEnum, IntFlag)-only # sweep, so that module's own pinned count does not move. - self.assertEqual(len(ints), 112) + # 112 became 114 with GitHub issue #880's ngap.ProcedureCode and + # ngap.ProtocolIE, moved onto EnumRegistry from hand-rolled IntEnum + # classes in pcapkit.protocols.application.ngap -- two more IntEnum + # registries, two more modules, no change to the flag or str counts. + self.assertEqual(len(ints), 114) self.assertEqual(len(flags), 6) self.assertEqual(len(strs), 10) - self.assertEqual(len(self.enums), 128) - self.assertEqual(len({obj.__module__ for obj in self.enums}), 122) + self.assertEqual(len(self.enums), 130) + self.assertEqual(len({obj.__module__ for obj in self.enums}), 124) def test_every_registry_rejects_a_negative_value(self) -> None: """The registry-wide form of GitHub issue #647. diff --git a/tests/const/test_const_enum_get.py b/tests/const/test_const_enum_get.py index c2e2c8999..c3e4e8a73 100644 --- a/tests/const/test_const_enum_get.py +++ b/tests/const/test_const_enum_get.py @@ -307,15 +307,19 @@ def test_the_two_unverified_enums_from_the_issue(self) -> None: def test_the_sweep_size_is_pinned(self) -> None: # If this drifts, a const enum was added, removed or renamed, and the # exception sets below need a fresh look rather than a silent pass. + # 118 became 120 with GitHub issue #880's ngap.ProcedureCode and + # ngap.ProtocolIE, moved onto EnumRegistry from hand-rolled IntEnum + # classes in pcapkit.protocols.application.ngap; neither needs an + # exception-set entry, since both take the base's own get(key, default). names = {_qualname(obj) for obj in self.enums} - self.assertEqual(len(self.enums), 118) + self.assertEqual(len(self.enums), 120) for expected in (EXPECTED_TO_RESOLVE_ANYTHING, EXPECTED_WITHOUT_AN_INTEGER_DEFAULT, EXPECTED_WITHOUT_A_CACHEABLE_FALLBACK): self.assertTrue(expected.issubset(names), f'sweep is missing: {expected - names}') def test_every_integer_path_consults_the_default(self) -> None: - """The registry-wide form of #584, across all 118 integer registries.""" + """The registry-wide form of #584, across all 120 integer registries.""" covered = 0 for obj in self.enums: qualname = _qualname(obj) @@ -355,8 +359,11 @@ def test_every_integer_path_consults_the_default(self) -> None: # issue #860 step 2, which gave ``GroupingInformation`` and # ``ResponseKind`` the base ``get(key, default)`` they never had # before, moving them out of ``EXPECTED_WITHOUT_AN_INTEGER_DEFAULT`` - # and into this sweep. - self.assertEqual(covered, 112) + # and into this sweep; plus 2 for GitHub issue #880's + # ngap.ProcedureCode and ngap.ProtocolIE, which take the base + # ``get(key, default)`` from the moment they exist under + # pcapkit.const at all. + self.assertEqual(covered, 114) def test_the_always_resolving_registries_have_nothing_to_fall_back_to(self) -> None: """The two registries excused from the sweep, and why. diff --git a/tests/const/test_const_enum_lookup.py b/tests/const/test_const_enum_lookup.py index 5068b6fd2..b072318a7 100644 --- a/tests/const/test_const_enum_lookup.py +++ b/tests/const/test_const_enum_lookup.py @@ -195,9 +195,12 @@ def test_every_registry_enum_was_discovered(self) -> None: # analysis in GitHub issue #492) needs a fresh look rather than a # silent pass. 112 rather than 111 since GitHub issue #808: dropping # ``TransportProtocol``'s ``IntFlag`` base moved it from - # ``_iter_const_int_flags`` into this sweep. + # ``_iter_const_int_flags`` into this sweep. 114 rather than 112 since + # GitHub issue #880's ngap.ProcedureCode and ngap.ProtocolIE joined + # this sweep, moved onto EnumRegistry from hand-rolled IntEnum classes + # in pcapkit.protocols.application.ngap. names = {f'{obj.__module__}.{obj.__qualname__}' for obj in self.enums} - self.assertEqual(len(self.enums), 112) + self.assertEqual(len(self.enums), 114) self.assertTrue(EXPECTED_TO_REJECT_ZERO.issubset(names), f'expected reject-list entries missing from the sweep: ' f'{EXPECTED_TO_REJECT_ZERO - names}') diff --git a/tests/const/test_const_registry_protocol.py b/tests/const/test_const_registry_protocol.py index 5f52b66e3..16187ab7b 100644 --- a/tests/const/test_const_registry_protocol.py +++ b/tests/const/test_const_registry_protocol.py @@ -2,7 +2,7 @@ """Tests for :class:`pcapkit.corekit.enum.EnumRegistry`, tier 2 of issue #775. Tier 1 (#838) removed the mint from the two sites in -:data:`pcapkit.vendor.default.LINE` that the 105 default-template registries +:data:`pcapkit.vendor.default.LINE` that the 107 default-template registries inherit. It could not reach the eleven crawlers that replace that template with their own, because each of those carries a hand-copied ``get()`` -- and none of them carries ``register``, ``register_alias`` or ``get_all`` at all. @@ -674,7 +674,7 @@ class _Str(EnumRegistry, StrEnum): self.assertNotIn('unit_test_absent', _Str.__members__) -#: Sample of the 105 default-template registries GitHub issue #775's tier 3 +#: Sample of the 107 default-template registries GitHub issue #775's tier 3 #: moved onto :class:`~pcapkit.corekit.enum.EnumRegistry`, chosen to cover #: the two shapes the census turned up in ``pcapkit/vendor/default.py``'s #: ``LINE`` template: a registry whose own ``_missing_`` mints directly via @@ -712,7 +712,7 @@ class _Str(EnumRegistry, StrEnum): #: custom ``__new__`` needed one. They stay excluded from *this* file's #: tier-3 sweep regardless, because the exclusion here is about their #: bespoke ``process()``/``get()`` shape not sharing the generated template, -#: which conversion onto the base does not change -- unlike the 105 +#: which conversion onto the base does not change -- unlike the 107 #: :data:`GENERATED_SAMPLE` registries below, each of these five classes' #: (nine files') own ``_missing_`` stays bespoke rather than sharing the #: generated template, and several also keep a bespoke @@ -748,7 +748,7 @@ class _Str(EnumRegistry, StrEnum): #: (a) too, but on narrower grounds worth spelling out, since -- unlike the #: rest of that group -- it does not carry a bespoke ``get()``/``register()`` #: at all; its own ``get``/``get_all``/``register``/``register_alias`` are -#: exactly the base's generic implementation, same as every one of the 105 +#: exactly the base's generic implementation, same as every one of the 107 #: :data:`GENERATED_SAMPLE`-shaped registries below. What excludes it is #: simply that this whole census, and its #: ``class \w+(EnumRegistry, IntEnum):`` regex a few lines down, is scoped to @@ -794,7 +794,7 @@ class _Str(EnumRegistry, StrEnum): class GeneratedTemplateSourceInheritsTests(unittest.TestCase): - """The 105 default-template registries must inherit the protocol too, + """The 107 default-template registries must inherit the protocol too, not just the six bespoke ones :class:`GeneratedSourceInheritsTests` above (tier 2) already covers.""" @@ -820,11 +820,11 @@ def test_generated_sample_carries_no_copy_of_the_protocol(self) -> None: self.assertIn("def _missing_(cls, value: 'int')", source) def test_every_generated_const_module_is_accounted_for(self) -> None: - """The full census, measured on this batch rather than assumed: 122 + """The full census, measured on this batch rather than assumed: 124 modules under :mod:`pcapkit.const`, splitting exactly three ways -- the 6 :data:`CONVERTED` bespoke ones tier 2 already handled, the 11 :data:`EXCLUDED_FROM_INTENUM_CENSUS` deliberately left alone, and the - remaining 105 this tier converts. Measured by me on the prior head + remaining 107 this tier converts. Measured by me on the prior head (commit ``05468a06b``, this batch's own base): of those 121, 6 carried :class:`~pcapkit.corekit.enum.EnumRegistry` and 111 carried the literal "Backport support for original codes." docstring (including @@ -836,8 +836,17 @@ def test_every_generated_const_module_is_accounted_for(self) -> None: ``pcapkit/const/pcapng/tls_key_label.py``, and the 10 excluded became 11 to hold it -- see the comment directly above :data:`EXCLUDED_FROM_INTENUM_CENSUS` for why it lands there rather - than among the 105. 105 itself does not move: the new file is + than among the 105. 105 itself does not move there: the new file is absorbed by the excluded side of the split, not the generated side. + + 122 became 124 with GitHub issue #880's + ``pcapkit/const/ngap/procedure_code.py`` and + ``pcapkit/const/ngap/protocol_ie.py``. Unlike #886's file, both are + the generated shape outright -- plain ``EnumRegistry`` + ``IntEnum``, + no bespoke ``get()``/``_missing_``-carrying ``__new__`` -- so neither + joins :data:`EXCLUDED_FROM_INTENUM_CENSUS`; they land on the + *generated* side instead. 6 and 11 hold; 105 becomes 107, and 122 + becomes 124. """ const_root = REPO_ROOT / 'pcapkit' / 'const' all_files = sorted( @@ -849,8 +858,8 @@ def test_every_generated_const_module_is_accounted_for(self) -> None: generated = [path for path in all_files if path not in converted_relpaths and path not in EXCLUDED_FROM_INTENUM_CENSUS] - self.assertEqual(len(all_files), 122) - self.assertEqual(len(generated), 105) + self.assertEqual(len(all_files), 124) + self.assertEqual(len(generated), 107) for relpath in generated: with self.subTest(module=relpath): @@ -967,7 +976,7 @@ def test_transtype_no_longer_mints_its_declared_unassigned_range(self) -> None: class RegisterAlreadyRegisteredNowRaisesOnAGeneratedRegistryTests(unittest.TestCase): """The residue #855 disclosed and this tier's own instructions name: ``register()`` on an already-registered value used to silently alias - rather than mint or raise, on every one of the 105 generated registries + rather than mint or raise, on every one of the 107 generated registries (they had no guard of their own -- only the six bespoke ones tier 2 already fixed did). Measured directly, before and after, in the docstring below rather than only asserted. diff --git a/tests/protocols/application/test_ngap_unit.py b/tests/protocols/application/test_ngap_unit.py index 917f42913..4301f7c26 100644 --- a/tests/protocols/application/test_ngap_unit.py +++ b/tests/protocols/application/test_ngap_unit.py @@ -118,15 +118,48 @@ def test_criticality_resolves_names_and_rejects_a_fourth_value(self) -> None: with self.assertRaises(ValueError): Criticality(3) - def test_unknown_procedure_and_ie_extend_rather_than_raise(self) -> None: + def test_unknown_procedure_and_ie_resolve_without_raising_or_aliasing(self) -> None: + """GitHub issue #880: an in-range value neither enumeration names must + report rather than explode, but two *different* unrecognised values + must never alias onto the same member the way the old shared-``-1`` + ``extend_enum`` mint used to. Since GitHub issue #880's fix, + :class:`~pcapkit.const.ngap.procedure_code.ProcedureCode` and + :class:`~pcapkit.const.ngap.protocol_ie.ProtocolIE` answer such a + value with a throwaway, non-registering member (:meth:`pcapkit. + corekit.enum.EnumRegistry._unregistered_member`) named ``Unassigned`` + -- bare, not value-suffixed, matching every other converted registry + under :mod:`pcapkit.const` (see :mod:`tests.const. + test_const_enum_no_mint`) -- rather than a permanent, + value-suffixed member sharing one mutable default with every other + unrecognised key. + """ from pcapkit.protocols.application.ngap import ProcedureCode, ProtocolIE - # A pycrate carrying a newer NGAP than the pasted block decodes IEs - # this package does not name; they must report, not explode. - self.assertEqual(ProcedureCode(200).name, 'Unassigned_200') - self.assertEqual(int(ProcedureCode(200)), 200) - self.assertEqual(ProtocolIE(113).name, 'Unassigned_113') # a real gap - self.assertEqual(ProtocolIE(9000).name, 'Unassigned_9000') + # A pycrate carrying a newer NGAP than this release names decodes IEs + # this package does not know; they must report, not explode. + before_proc = len(ProcedureCode.__members__) + member_200 = ProcedureCode(200) + self.assertEqual(member_200.name, 'Unassigned') + self.assertEqual(int(member_200), 200) + # Non-registering: __members__ does not grow, and a second lookup at + # the same value returns an equal but distinct object rather than the + # first call's own member. + self.assertEqual(len(ProcedureCode.__members__), before_proc) + self.assertNotIn(200, ProcedureCode._value2member_map_) # type: ignore[attr-defined] + self.assertIsNot(ProcedureCode(200), member_200) + self.assertEqual(ProcedureCode(200), member_200) + + before_ie = len(ProtocolIE.__members__) + member_113 = ProtocolIE(113) # a real gap + member_9000 = ProtocolIE(9000) + self.assertEqual(member_113.name, 'Unassigned') + self.assertEqual(member_9000.name, 'Unassigned') + self.assertEqual(int(member_113), 113) + self.assertEqual(int(member_9000), 9000) + # The defect this pins: two distinct unrecognised values must resolve + # to two distinct members, never alias onto one shared sentinel. + self.assertNotEqual(member_113, member_9000) + self.assertEqual(len(ProtocolIE.__members__), before_ie) # ProcedureCode is INTEGER (0..255) and ProtocolIE-ID is (0..65535). with self.assertRaises(ValueError): @@ -134,6 +167,30 @@ def test_unknown_procedure_and_ie_extend_rather_than_raise(self) -> None: with self.assertRaises(ValueError): ProtocolIE(65536) + def test_get_of_an_unresolvable_string_key_raises_rather_than_minting(self) -> None: + """GitHub issue #880's own repro, re-run against the fixed classes: + ``get()`` on an unrecognised name must raise rather than mint a + member at a shared default -- the defect that let a second unknown + name alias onto the first's member. Real callers in :meth:`NGAP.read` + and :meth:`NGAP.make` only ever pass an :class:`int` or an existing + member (see the module's own ``ProtocolIE.get``/``ProcedureCode.get`` + call sites), so this exercises the public ``str`` path those callers + never take, which is exactly the path GitHub issue #880 measured the + aliasing defect on. + """ + from pcapkit.protocols.application.ngap import ProcedureCode, ProtocolIE + + for enum in (ProcedureCode, ProtocolIE): + with self.subTest(enum=enum.__name__): + before = len(enum.__members__) + with self.assertRaises(KeyError): + enum.get('bogus_one') + with self.assertRaises(KeyError): + enum.get('bogus_two') + self.assertEqual(len(enum.__members__), before) + self.assertNotIn('bogus_one', enum.__members__) + self.assertNotIn('bogus_two', enum.__members__) + ########################################################################## # Registration and metadata. No pycrate needed. ########################################################################## diff --git a/tests/test_tier_guard.py b/tests/test_tier_guard.py index b5cf5a83a..fb1835ab7 100644 --- a/tests/test_tier_guard.py +++ b/tests/test_tier_guard.py @@ -1002,14 +1002,19 @@ def test_the_extras_read_back_what_pyproject_declares(self) -> None: def test_a_bracket_inside_a_requirement_does_not_end_the_array(self) -> None: """``vendor`` is the case a "slice to the first ``]``" parse gets wrong. - ``vendor = [ "requests[socks]", "beautifulsoup4[html5lib]" ]`` holds two - closing brackets before its own, so a naive scan stops inside the first - requirement and reports an extra with one malformed entry. + ``vendor = [ "requests[socks]", "beautifulsoup4[html5lib]", "pycrate" ]`` + holds two closing brackets before its own, so a naive scan stops inside + the first requirement and reports an extra with one malformed entry. + ``pycrate`` (added for GitHub issue #880's NGAP crawlers -- the owner's + ruling was to declare it in this extra rather than install it ad hoc in + a workflow) carries no brackets of its own; it is here so the third + entry does not silently vanish from the sweep the way a truncated parse + would drop it too. """ declared = _dependency_gates.declared_requirements() self.assertEqual({requirement.name for requirement in declared['vendor']}, - {'requests', 'beautifulsoup4'}) + {'requests', 'beautifulsoup4', 'pycrate'}) def test_the_core_dependencies_are_read_from_project_not_build_system(self) -> None: """``[build-system] requires`` also holds ``setuptools``; this is not it.""" diff --git a/tests/vendor/test_vendor_ngap_unit.py b/tests/vendor/test_vendor_ngap_unit.py new file mode 100644 index 000000000..e6844eab7 --- /dev/null +++ b/tests/vendor/test_vendor_ngap_unit.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +"""Unit tests for :mod:`pcapkit.vendor.ngap`'s pycrate-sourced crawlers. + +GitHub issue #880's owner ruling for the ``ngap.py`` half of the defect: unlike +the four closed :mod:`pcapkit.protocols.internet.mh` helper enums #877 ruled +on, :class:`~pcapkit.const.ngap.procedure_code.ProcedureCode` and +:class:`~pcapkit.const.ngap.protocol_ie.ProtocolIE` are genuinely open per +3GPP TS 38.413, and the fix is to source their real assignment from |pycrate|_ +through a vendor crawler rather than hand-maintaining the list or minting a +value at lookup time. This module tests the crawler half of that: that +:meth:`~pcapkit.vendor.ngap.procedure_code.ProcedureCode._request` reads +|pycrate|_'s own compiled specification correctly, that its output matches the +generated :mod:`pcapkit.const.ngap` modules exactly, that two independent +calls agree (the property that makes ``pcapkit-vendor``'s regeneration +reproducible), and that the two registries partition |pycrate|_'s +``NGAP_Constants`` disjointly. + +This never fetches anything over the network -- see the module docstring on +:mod:`pcapkit.vendor.ngap.procedure_code` for why: |pycrate|_ is an +already-installed optional dependency, not a network registry, so +:meth:`_request` only ever imports it. + +.. |pycrate| replace:: ``pycrate`` +.. _pycrate: https://github.com/pycrate-org/pycrate + +""" +from __future__ import annotations + +import importlib.util +import sys +import unittest +from unittest import mock + +HAS_PYCRATE = importlib.util.find_spec('pycrate_asn1dir') is not None + + +@unittest.skipUnless(HAS_PYCRATE, "the optional 'pycrate' dependency is not installed") +class NGAPVendorCrawlerTests(unittest.TestCase): + """:meth:`_request` against the real, installed |pycrate|_ package.""" + + def test_procedure_code_request_matches_the_generated_const_module(self) -> None: + from pcapkit.const.ngap.procedure_code import ProcedureCode as Generated + from pcapkit.vendor.ngap.procedure_code import ProcedureCode as Crawler + + # ``object.__new__`` bypasses ``Vendor.__init__`` -- and so its file + # write -- entirely; ``_request`` needs no instance state. + entries = object.__new__(Crawler)._request() # type: ignore[misc] + + self.assertEqual(len(entries), 81) + + names = [name for name, _ in entries] + self.assertEqual(len(names), len(set(names)), 'pycrate named a duplicate identifier') + values = [value for _, value in entries] + self.assertEqual(len(values), len(set(values)), 'pycrate assigned a duplicate value') + + self.assertEqual(len(Generated.__members__), len(entries)) + for name, value in entries: + with self.subTest(name=name): + self.assertIn(name, Generated.__members__) + self.assertEqual(int(Generated[name]), value) # type: ignore[misc] + + def test_protocol_ie_request_matches_the_generated_const_module(self) -> None: + from pcapkit.const.ngap.protocol_ie import ProtocolIE as Generated + from pcapkit.vendor.ngap.protocol_ie import ProtocolIE as Crawler + + entries = object.__new__(Crawler)._request() # type: ignore[misc] + + self.assertEqual(len(entries), 438) + + names = [name for name, _ in entries] + self.assertEqual(len(names), len(set(names)), 'pycrate named a duplicate identifier') + values = [value for _, value in entries] + self.assertEqual(len(values), len(set(values)), 'pycrate assigned a duplicate value') + + self.assertEqual(len(Generated.__members__), len(entries)) + for name, value in entries: + with self.subTest(name=name): + self.assertIn(name, Generated.__members__) + self.assertEqual(int(Generated[name]), value) # type: ignore[misc] + + def test_request_is_reproducible_across_two_independent_calls(self) -> None: + """Byte-reproducibility starts here: if two calls disagreed, the + generated const file could not be either -- ``pcapkit-vendor`` run + twice would produce two different files from the same installed + |pycrate|_. + """ + from pcapkit.vendor.ngap.procedure_code import ProcedureCode as ProcCrawler + from pcapkit.vendor.ngap.protocol_ie import ProtocolIE as IECrawler + + for crawler_cls in (ProcCrawler, IECrawler): + with self.subTest(crawler=crawler_cls.__qualname__): + first = object.__new__(crawler_cls)._request() # type: ignore[misc] + second = object.__new__(crawler_cls)._request() # type: ignore[misc] + self.assertEqual(first, second) + self.assertIsNot(first, second) + + def test_procedure_code_and_protocol_ie_partition_ngap_constants_disjointly(self) -> None: + """Each crawler claims its own ``NGAP-CommonDataTypes`` open type; no + identifier should ever be read by both.""" + from pcapkit.vendor.ngap.procedure_code import ProcedureCode as ProcCrawler + from pcapkit.vendor.ngap.protocol_ie import ProtocolIE as IECrawler + + proc_names = {name for name, _ in object.__new__(ProcCrawler)._request()} # type: ignore[misc] + ie_names = {name for name, _ in object.__new__(IECrawler)._request()} # type: ignore[misc] + + self.assertEqual(proc_names & ie_names, set()) + self.assertEqual(len(proc_names), 81) + self.assertEqual(len(ie_names), 438) + + def test_count_needs_no_duplicate_bookkeeping(self) -> None: + """Neither crawler's :meth:`~pcapkit.vendor.default.Vendor.rename` + path is ever reached (pycrate names each assignment once), so + :meth:`count` is overridden to a trivial, always-empty + :class:`collections.Counter` rather than the CSV-oriented base + implementation, which would fail outright on this crawler's + ``(name, value)`` tuples.""" + import collections + + from pcapkit.vendor.ngap.procedure_code import ProcedureCode as ProcCrawler + from pcapkit.vendor.ngap.protocol_ie import ProtocolIE as IECrawler + + for crawler_cls in (ProcCrawler, IECrawler): + with self.subTest(crawler=crawler_cls.__qualname__): + crawler = object.__new__(crawler_cls) # type: ignore[misc] + data = crawler._request() + self.assertEqual(crawler.count(data), collections.Counter()) + + def test_context_renders_the_current_generated_file_byte_for_byte(self) -> None: + """:meth:`context` is what :meth:`~pcapkit.vendor.default.Vendor. + __init__` writes to disk; comparing its output against the committed + generated file directly (rather than re-running ``pcapkit-vendor`` and + diffing files) proves the same reproducibility without ever touching + the filesystem.""" + import pathlib + + from pcapkit.vendor.ngap.procedure_code import ProcedureCode as ProcCrawler + from pcapkit.vendor.ngap.protocol_ie import ProtocolIE as IECrawler + + repo_root = pathlib.Path(__file__).resolve().parents[2] + for crawler_cls, relpath in ( + (ProcCrawler, 'pcapkit/const/ngap/procedure_code.py'), + (IECrawler, 'pcapkit/const/ngap/protocol_ie.py'), + ): + with self.subTest(crawler=crawler_cls.__qualname__): + crawler = object.__new__(crawler_cls) # type: ignore[misc] + crawler.NAME = crawler_cls.__name__ + crawler.DOCS = crawler_cls.__doc__ + data = crawler._request() + rendered = crawler.context(data).strip() + + on_disk = (repo_root / relpath).read_text().strip() + self.assertEqual(rendered, on_disk) + + +class NGAPVendorCrawlerMissingDependencyTests(unittest.TestCase): + """:meth:`_request` without |pycrate|_ installed -- simulated via + ``sys.modules``, run unconditionally regardless of whether this + environment actually has it, since the failure path itself needs no + |pycrate|_ to exercise.""" + + def test_procedure_code_request_reports_the_missing_optional_dependency(self) -> None: + from pcapkit.vendor.ngap.procedure_code import ProcedureCode as Crawler + + crawler = object.__new__(Crawler) # type: ignore[misc] + with mock.patch.dict(sys.modules, {'pycrate_asn1dir.NGAP': None}): + with self.assertRaises(ImportError) as caught: + crawler._request() + self.assertIn('pypcapkit[NGAP]', str(caught.exception)) + self.assertIn('#880', str(caught.exception)) + self.assertIsInstance(caught.exception.__cause__, ImportError) + + def test_protocol_ie_request_reports_the_missing_optional_dependency(self) -> None: + from pcapkit.vendor.ngap.protocol_ie import ProtocolIE as Crawler + + crawler = object.__new__(Crawler) # type: ignore[misc] + with mock.patch.dict(sys.modules, {'pycrate_asn1dir.NGAP': None}): + with self.assertRaises(ImportError) as caught: + crawler._request() + self.assertIn('pypcapkit[NGAP]', str(caught.exception)) + self.assertIn('#880', str(caught.exception)) + self.assertIsInstance(caught.exception.__cause__, ImportError) + + +if __name__ == '__main__': + unittest.main()