diff --git a/docs/source/conf.py b/docs/source/conf.py index f637e454c..3325961e5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -307,6 +307,40 @@ def maybe_skip_member(app: 'Sphinx', what: str, name: str, # pylint: disable=un return skip +def skip_enum_value_hook(app: 'Sphinx', what: str, name: str, # pylint: disable=unused-argument + obj: 'Any', skip: bool, options: 'Dict[str, Any]') -> 'Optional[bool]': # pylint: disable=unused-argument + """Skip the enum machinery's own ``_generate_next_value_`` hook. + + The enum metaclass copies ``_generate_next_value_`` into the namespace of + every enumeration it creates, so ``:members:`` finds it as the documented + class's own member. ``exclude-members`` cannot remove it: autodoc applies + that first and then lets ``autodoc-skip-member`` overrule it, and + napoleon's handler, with ``napoleon_include_private_with_doc`` on, includes + a private member that has a docstring and was defined in a class body -- + true of the standard library's hooks and of ``aenum``'s ``StrEnum`` one. + None of them is part of any pcapkit enumeration's interface. + + Args: + app: Sphinx application. + what: Type of the object whose member is being considered. + name: Name of the member. + obj: The member itself. + skip: Whether autodoc would skip it. + options: Directive options. + + Returns: + :obj:`True` for a ``_generate_next_value_`` that pcapkit does not + define itself, and :obj:`None` for anything else, which leaves the + decision to the other handlers. + + """ + if name == '_generate_next_value_': + module = getattr(obj, '__module__', None) or '' + if module.partition('.')[0] != 'pcapkit': + return True + return None + + def bind_type_checking_names(app: 'Sphinx') -> None: """Execute every ``pcapkit`` module's ``if TYPE_CHECKING:`` block. @@ -555,6 +589,10 @@ def restore_enum_member_docstring(app: 'Sphinx', what: str, name: str, # pylint def setup(app: 'Sphinx') -> None: app.connect('builder-inited', bind_type_checking_names) app.connect('autodoc-skip-member', maybe_skip_member) + # NB: before napoleon's ``autodoc-skip-member`` handler, which connects at the + # default 500 and returns ``False`` for this hook; the first answer that is + # not ``None`` is the one autodoc uses. + app.connect('autodoc-skip-member', skip_enum_value_hook, priority=400) # NB: below ``sphinx_autodoc_typehints``, which connects at the default 500 and # would otherwise win the tie on registration order -- conf.py's ``setup`` runs # after the extensions in ``extensions`` have been set up. diff --git a/docs/source/pcapkit/protocols/application/application.rst b/docs/source/pcapkit/protocols/application/application.rst index 952e3e2a2..8d5b72cc8 100644 --- a/docs/source/pcapkit/protocols/application/application.rst +++ b/docs/source/pcapkit/protocols/application/application.rst @@ -5,10 +5,9 @@ Base Protocol :mod:`pcapkit.protocols.application.application` contains only :class:`~pcapkit.protocols.application.application.Application`, -which is a base class for application layer protocols, eg. -:class:`HTTP/1.* `, -:class:`HTTP/2 ` -and etc. +which is a base class for application layer protocols, e.g. +:class:`HTTP/1.* ` and +:class:`HTTP/2 `. .. versionchanged:: 1.5.0 diff --git a/docs/source/pcapkit/protocols/application/http.rst b/docs/source/pcapkit/protocols/application/http.rst index f1769e34a..b3f644f27 100644 --- a/docs/source/pcapkit/protocols/application/http.rst +++ b/docs/source/pcapkit/protocols/application/http.rst @@ -6,7 +6,7 @@ HTTP - Hypertext Transfer Protocol :mod:`pcapkit.protocols.application.http` contains :class:`~pcapkit.protocols.application.http.HTTP`, which is a base class for Hypertext Transfer -Protocol (HTTP) [*]_ family, eg. +Protocol (HTTP) [*]_ family, e.g. :class:`HTTP/1.* ` and :class:`HTTP/2 `, and :func:`~pcapkit.protocols.application.http.test_start_line`, diff --git a/docs/source/pcapkit/protocols/application/ngap.rst b/docs/source/pcapkit/protocols/application/ngap.rst index ffcbeeb2c..efe0ce09f 100644 --- a/docs/source/pcapkit/protocols/application/ngap.rst +++ b/docs/source/pcapkit/protocols/application/ngap.rst @@ -6,7 +6,7 @@ NGAP - NG Application Protocol :mod:`pcapkit.protocols.application.ngap` contains :class:`~pcapkit.protocols.application.ngap.NGAP`, which implements extractor for the NG Application Protocol -(NGAP) [*]_, as specified in 3GPP TS 38.413, and the four enumerations its +(NGAP) [*]_, as specified in 3GPP TS 38.413, and the enumerations its data models report with: :class:`~pcapkit.protocols.application.ngap.PDUKind`, :class:`~pcapkit.protocols.application.ngap.Criticality`, @@ -35,10 +35,23 @@ unregistered PPID would, because :func:`~pcapkit.corekit.decorators.beholder` and falls back to :class:`~pcapkit.protocols.misc.raw.Raw`. +.. autoclass:: pcapkit.protocols.application.ngap.NGAP + :no-members: + :show-inheritance: + + .. autoproperty:: name + .. autoproperty:: length + + .. automethod:: read + .. automethod:: make + + .. automethod:: __length_hint__ + .. automethod:: _make_data + Decoder Dependency ------------------ -|pycrate|_ is used rather than a PER codec of our own, for two reasons. It +|pycrate|_ is used rather than a PER codec of our own. It **ships NGAP already compiled**, at ``pycrate_asn1dir/NGAP.py``, so the 3GPP ASN.1 source does not have to be vendored here and tracked across releases; and it is **pure Python**, with no compiled extension to build on any platform. @@ -52,19 +65,18 @@ Generic Conversion The decoded value tree is mapped into :class:`~pcapkit.corekit.infoclass.Info` objects **structurally**, by ASN.1 shape rather than by procedure: -=============================== ========================================================== -ASN.1 / |pycrate|_ shape :mod:`pcapkit` model -=============================== ========================================================== -``SEQUENCE`` / ``SET`` (a dict) :class:`~pcapkit.protocols.data.application.ngap.Sequence` -``SEQUENCE OF`` (a list) :obj:`list` -``CHOICE`` / open type :class:`~pcapkit.protocols.data.application.ngap.Choice` -``BIT STRING`` :class:`~pcapkit.protocols.data.application.ngap.BitString` -``INTEGER``, ``OCTET STRING``, kept as :obj:`int`, :obj:`bytes`, :obj:`str` -``ENUMERATED``, ``BOOLEAN`` -=============================== ========================================================== - -That is a deliberate trade. Every one of the 81 elementary procedures and 438 -protocol IEs works on the day it is decoded, and a new 3GPP release needs no +========================================================== ========================================================== +ASN.1 / |pycrate|_ shape :mod:`pcapkit` model +========================================================== ========================================================== +``SEQUENCE`` / ``SET`` (a dict) :class:`~pcapkit.protocols.data.application.ngap.Sequence` +``SEQUENCE OF`` (a list) :obj:`list` +``CHOICE`` / open type :class:`~pcapkit.protocols.data.application.ngap.Choice` +``BIT STRING`` :class:`~pcapkit.protocols.data.application.ngap.BitString` +``INTEGER``, ``OCTET STRING``, ``ENUMERATED``, ``BOOLEAN`` kept as :obj:`int`, :obj:`bytes`, :obj:`str`, :obj:`bool` +========================================================== ========================================================== + +That is a deliberate trade. Every elementary procedure and protocol IE +works on the day it is decoded, and a new 3GPP release needs no code change here; what is given up is per-IE typing, so an IE's value is reported in the specification's own shape rather than as a :mod:`pcapkit`-specific model. The fields worth reading at a glance -- the PDU @@ -82,8 +94,8 @@ Known Limitations degrades to :class:`~pcapkit.protocols.misc.raw.Raw`. * **The specification version is that of the installed decoder, not this package's.** The IE and procedure enumerations were generated from - ``NGAP_Constants`` of |pycrate|_ 0.8.1 (Release-18-era: 81 procedure codes, - 438 protocol IE IDs, highest 443). A |pycrate|_ that carries a newer NGAP will decode IEs that + ``NGAP_Constants`` of |pycrate|_ 0.8.1 (Release-18-era). A |pycrate|_ that + carries a newer NGAP will decode IEs that :class:`~pcapkit.const.ngap.procedure_code.ProcedureCode` and :class:`~pcapkit.const.ngap.protocol_ie.ProtocolIE` do not name. Such a value resolves to a non-registering member called ``Unassigned`` rather than @@ -95,19 +107,6 @@ Known Limitations vendor defined, so the generic conversion reports whatever ASN.1 shape the encoding declares and cannot name the fields. -.. autoclass:: pcapkit.protocols.application.ngap.NGAP - :no-members: - :show-inheritance: - - .. autoproperty:: name - .. autoproperty:: length - - .. automethod:: read - .. automethod:: make - - .. automethod:: __length_hint__ - .. automethod:: _make_data - Auxiliary Functions ------------------- diff --git a/docs/source/pcapkit/protocols/application/ospf.rst b/docs/source/pcapkit/protocols/application/ospf.rst index 496b95941..52c9fd6ee 100644 --- a/docs/source/pcapkit/protocols/application/ospf.rst +++ b/docs/source/pcapkit/protocols/application/ospf.rst @@ -41,7 +41,12 @@ as below: .. versionremoved:: 1.5.0 ``pcapkit.protocols.link.ospf``, ``pcapkit.protocols.data.link.ospf`` and - ``pcapkit.protocols.schema.link.ospf``, with no alias (:issue:`719`). + ``pcapkit.protocols.schema.link.ospf``, the ``OSPF`` name that + ``pcapkit.protocols.link``, ``pcapkit.protocols.data.link`` and + ``pcapkit.protocols.schema.link`` each exported, and the + ``OSPF_CrytographicAuthentication`` name that + ``pcapkit.protocols.data.link`` and ``pcapkit.protocols.schema.link`` + exported, with no alias (:issue:`719`, :issue:`1090`). .. versionchanged:: 1.5.0 diff --git a/docs/source/pcapkit/protocols/application/rarp.rst b/docs/source/pcapkit/protocols/application/rarp.rst index a638d9065..1000cf3ab 100644 --- a/docs/source/pcapkit/protocols/application/rarp.rst +++ b/docs/source/pcapkit/protocols/application/rarp.rst @@ -35,7 +35,8 @@ Octets Bits Name Description .. versionremoved:: 1.5.0 - ``pcapkit.protocols.link.rarp``, with no alias (:issue:`719`). + ``pcapkit.protocols.link.rarp``, and the ``RARP`` and ``DRARP`` names + that ``pcapkit.protocols.link`` exported, with no alias (:issue:`719`). .. autoclass:: pcapkit.protocols.application.rarp.RARP :no-members: diff --git a/docs/source/pcapkit/protocols/misc/pcap.rst b/docs/source/pcapkit/protocols/misc/pcap.rst index e4c0a82d3..32fa1d337 100644 --- a/docs/source/pcapkit/protocols/misc/pcap.rst +++ b/docs/source/pcapkit/protocols/misc/pcap.rst @@ -113,7 +113,7 @@ whose structure is described as below: .. versionchanged:: 1.5.0 - ``bytes(frame)`` and :attr:`~pcapkit.protocols.misc.pcap.frame.Frame.packet` + ``bytes(frame)`` and :attr:`~pcapkit.protocols.protocol.Protocol.packet` hold this frame's own octets, where they held the next frame's (:issue:`357`), and the parsed ``time`` is timezone-aware UTC. The parsed ``len`` is the on-wire length and ``cap_len`` the captured one, where the diff --git a/docs/source/pcapkit/protocols/misc/pcapng.rst b/docs/source/pcapkit/protocols/misc/pcapng.rst index 4a2ee0439..f589fc9ec 100644 --- a/docs/source/pcapkit/protocols/misc/pcapng.rst +++ b/docs/source/pcapkit/protocols/misc/pcapng.rst @@ -42,6 +42,7 @@ which implements extractor for PCAP-NG file format [*]_, and the .. autoproperty:: ts_timezone .. autoproperty:: linktype .. autoproperty:: block + .. autoproperty:: packet .. automethod:: register .. automethod:: register_block @@ -225,9 +226,9 @@ IANA registry. .. versionremoved:: 1.5.0 - ``pcapkit.protocols.misc.pcapng.TLSKeyLabel``, now - :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` (:issue:`886`, - :issue:`1744`). + ``pcapkit.protocols.misc.pcapng.TLSKeyLabel``, with no alias; use + :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` instead + (:issue:`886`, :issue:`1744`). .. autoclass:: pcapkit.protocols.misc.pcapng.WireGuardKeyLabel :members: diff --git a/docs/source/pcapkit/protocols/protocol.rst b/docs/source/pcapkit/protocols/protocol.rst index 49bfff69a..07955fcb8 100644 --- a/docs/source/pcapkit/protocols/protocol.rst +++ b/docs/source/pcapkit/protocols/protocol.rst @@ -130,11 +130,15 @@ starts at the first layer it dissects. One chain of extension headers is also bo its own, by :data:`~pcapkit.utilities.config.EXTENSION_HEADER_LIMIT`. The ``pyshark``, ``scapy`` and ``pypcapfile`` engines dissect with their own parsers, which do not apply the frame's layer bound (:issue:`1609`). The -``dpkt`` toolkit reads what :mod:`dpkt` decodes, which applies neither bound. +``dpkt`` toolkit reads what ``dpkt`` decodes, which applies neither bound. Data Models ----------- +.. autoclass:: pcapkit.protocols.data.protocol.Protocol + :members: + :show-inheritance: + .. autoclass:: pcapkit.protocols.data.protocol.Packet :members: :show-inheritance: @@ -189,6 +193,7 @@ Internal Definitions _make_data _make_index _make_payload + _packing_target _lookup_registry _lookup_next_layer _decode_next_layer @@ -214,6 +219,7 @@ Internal Definitions :meth:`~Protocol.expand_comp`, :meth:`~Protocol._read_packet`, :meth:`~Protocol._get_payload`, :meth:`~Protocol._make_data`, :meth:`~Protocol._make_index`, :meth:`~Protocol._make_payload`, + :meth:`~Protocol._packing_target`, :meth:`~Protocol._lookup_registry`, :meth:`~Protocol._lookup_next_layer`, :meth:`~Protocol._decode_next_layer`, :meth:`~Protocol._import_next_layer`, diff --git a/pcapkit/protocols/__init__.py b/pcapkit/protocols/__init__.py index 2327942d6..544ddb65f 100644 --- a/pcapkit/protocols/__init__.py +++ b/pcapkit/protocols/__init__.py @@ -5,7 +5,7 @@ .. module:: pcapkit.protocols -:mod:`pcapkit.protocols` is collection of all protocol families, +:mod:`pcapkit.protocols` is a collection of all protocol families, with detailed implementation and methods. """ diff --git a/pcapkit/protocols/application/__init__.py b/pcapkit/protocols/application/__init__.py index ee8ed0343..aed8aee96 100644 --- a/pcapkit/protocols/application/__init__.py +++ b/pcapkit/protocols/application/__init__.py @@ -13,7 +13,7 @@ # NNTP, NTP, ONC:RPC, POP, RIP, RTP, SIP, SMTP, SNMP, # SSH, TELNET, TLS/SSL, XMPP. # -# NB: NGAP below is the one protocol here whose decoder is an optional +# NB: NGAP's decoder below is an optional # dependency, c.f. pcapkit.protocols.application.ngap. Importing the module # is free -- ``pycrate`` is imported inside the parse path, not here. diff --git a/pcapkit/protocols/application/application.py b/pcapkit/protocols/application/application.py index 5a4dcf941..d4d071298 100644 --- a/pcapkit/protocols/application/application.py +++ b/pcapkit/protocols/application/application.py @@ -144,13 +144,13 @@ def _decode_next_layer(self, dict_: '_PT', proto: 'Optional[int]' = None, length proto: next layer protocol index; only the ``-1`` sentinel (the rest, undissected) is accepted length: valid (*non-padding*) length - packet: packet info (passed from :meth:`self.unpack `) + packet: packet info (passed from :meth:`self.unpack `) Returns: Current protocol with the undissected remainder as payload. Raises: - UnsupportedCall: ``proto`` is anything but ``-1``, i.e. a real + UnsupportedCall: If ``proto`` is anything but ``-1``, i.e. a real protocol dispatch, which this protocol doesn't support. """ @@ -167,14 +167,14 @@ def _import_next_layer(self, proto: 'int', length: 'Optional[int]' = None, *, # proto: next layer protocol index; only the ``-1`` sentinel (the rest, undissected) is accepted length: valid (*non-padding*) length - packet: packet info (passed from :meth:`self.unpack `) + packet: packet info (passed from :meth:`self.unpack `) Returns: Instance of :class:`~pcapkit.protocols.misc.raw.Raw`, or of :class:`~pcapkit.protocols.misc.null.NoPayload` when nothing remains. Raises: - UnsupportedCall: ``proto`` is anything but ``-1``, i.e. a real + UnsupportedCall: If ``proto`` is anything but ``-1``, i.e. a real protocol dispatch, which this protocol doesn't support. """ diff --git a/pcapkit/protocols/application/http.py b/pcapkit/protocols/application/http.py index e16030a1c..54653dcef 100644 --- a/pcapkit/protocols/application/http.py +++ b/pcapkit/protocols/application/http.py @@ -7,7 +7,7 @@ :mod:`pcapkit.protocols.application.http` contains :class:`~pcapkit.protocols.application.http.HTTP`, which is a base class for Hypertext Transfer -Protocol (HTTP) [*]_ family, eg. +Protocol (HTTP) [*]_ family, e.g. :class:`HTTP/1.* ` and :class:`HTTP/2 `, and :func:`~pcapkit.protocols.application.http.test_start_line`, @@ -84,7 +84,7 @@ def _test_http2_frame(data: 'bytes') -> 'bool': sending" (:rfc:`9113#section-4.1`), and each stream rule is one a receiver "MUST treat as a connection error" when broken (e.g. ``DATA`` on stream 0, :rfc:`9113#section-6.1`), so no frame a conforming peer - sends fails here. Frame types outside the ten that :rfc:`9113` defines + sends fails here. Frame types :rfc:`9113` does not define are legal and ignored by a receiver (:rfc:`9113#section-5.5`), but on a payload with no other evidence they are what arbitrary binary looks like most of the time, so they are not guessed. @@ -153,7 +153,7 @@ def test_start_line(data: 'bytes') -> 'bool': :meth:`HTTP._read_http_header ` applies -- ``_RE_METHOD`` with ``_RE_VERSION`` for a request, ``_RE_VERSION`` - with ``_RE_STATUS`` for a response -- which is why those three patterns + with ``_RE_STATUS`` for a response -- which is why those patterns are defined beside this predicate and :mod:`~pcapkit.protocols.application.httpv1` imports them from here. The two must accept the same start lines: a predicate looser than the parser @@ -228,7 +228,7 @@ class HTTP(Application[_PT, _ST], Generic[_PT, _ST]): #: according to that value, so the correct set of names depends on an #: argument. :obj:`None` therefore opts out of the construction keyword check #: that :meth:`ProtocolBase.__init__ - #: ` performs; the two + #: ` performs; the #: versioned classes are checked normally when constructed directly. __keywords__ = None @@ -338,7 +338,7 @@ def make(self, Args: version: Version of HTTP. preface: Whether the HTTP/2 connection preface goes ahead of the - frame; :meth:`pack` writes it. + frame; :meth:`~pcapkit.protocols.protocol.Protocol.pack` writes it. **kwargs: Arbitrary keyword arguments. Returns: @@ -491,8 +491,8 @@ def _guess_version(self, length: 'int', **kwargs: 'Any') -> 'HTTP': arm, or from that HTTP/2 trial, propagates unconverted. Note: - Two things this deliberately does **not** decide, because one payload - with no flow context cannot: + This deliberately does **not** decide the following, because one + payload with no flow context cannot: * **An** ``Upgrade: h2c`` **exchange** (:rfc:`7540#section-3.2`, deprecated but not removed by :rfc:`9113#section-3.1`) stays @@ -514,7 +514,7 @@ def _guess_version(self, length: 'int', **kwargs: 'Any') -> 'HTTP': which is precisely how garbage text was classified HTTP/2 to begin with. A bare frame is parsed as HTTP/2 only by the fall-through below, and only if its header is one a conforming sender could - write (see :func:`_test_http2_frame`): a frame type :rfc:`9113` + write (see ``_test_http2_frame()``): a frame type :rfc:`9113` defines, no undefined flag, and a stream that type may be sent on. Nine zero octets -- a ``DATA`` frame on stream 0, a protocol error under :rfc:`9113#section-6.1` -- are not guessed as HTTP/2. @@ -596,7 +596,7 @@ def _guess_version(self, length: 'int', **kwargs: 'Any') -> 'HTTP': # and no preface, and a self-consistent HTTP/2 frame is still the best # answer available for one. It is the last resort, not the whole method. # - # The two arms suppress different sets, deliberately. Only the *last* + # The arms of this trial suppress different sets, deliberately. Only the *last* # arm also suppresses :exc:`struct.error`, as defence in depth: a payload # too short for HTTP/2's nine-octet frame header, or whose frame-specific # fields exceed what the buffer holds, must not escape as a stdlib diff --git a/pcapkit/protocols/application/httpv2.py b/pcapkit/protocols/application/httpv2.py index f8aaafc4c..94891e742 100644 --- a/pcapkit/protocols/application/httpv2.py +++ b/pcapkit/protocols/application/httpv2.py @@ -159,8 +159,7 @@ class HTTP(HTTPBase[Data_HTTP, Schema_HTTP], # Defaults. ########################################################################## - #: DefaultDict[Enum_Frame, str | tuple[FrameParser, FrameConstructor]]: Frame - #: code to method mapping. A :obj:`str` value names the + #: Frame code to method mapping. A :obj:`str` value names the #: ``_read_http_${name}`` and ``_make_http_${name}`` methods, which fall back #: to ``_read_http_none`` and ``_make_http_none`` when not defined, as an #: unregistered code does; any other value is a ``(parser, constructor)`` @@ -356,6 +355,12 @@ def make(self, # type: ignore[override] Returns: Constructed packet data. + .. versionchanged:: 1.5.0 + + ``reserved`` is a new parameter before ``sid``, so a 1.4.x + positional argument meant for ``sid`` or ``frame`` lands one + parameter early (:issue:`1246`). + """ type_val = cast('Enum_Frame', self._make_index(type, type_default, namespace=type_namespace, diff --git a/pcapkit/protocols/application/ngap.py b/pcapkit/protocols/application/ngap.py index b0b3c6a79..70e50d3b1 100644 --- a/pcapkit/protocols/application/ngap.py +++ b/pcapkit/protocols/application/ngap.py @@ -36,7 +36,7 @@ Decoder Dependency ------------------ -|pycrate|_ is used rather than a PER codec of our own, for two reasons. It +|pycrate|_ is used rather than a PER codec of our own. It **ships NGAP already compiled**, at ``pycrate_asn1dir/NGAP.py``, so the 3GPP ASN.1 source does not have to be vendored here and tracked across releases; and it is **pure Python**, with no compiled extension to build on any platform. @@ -50,19 +50,18 @@ The decoded value tree is mapped into :class:`~pcapkit.corekit.infoclass.Info` objects **structurally**, by ASN.1 shape rather than by procedure: -=============================== ========================================================== -ASN.1 / |pycrate|_ shape :mod:`pcapkit` model -=============================== ========================================================== -``SEQUENCE`` / ``SET`` (a dict) :class:`~pcapkit.protocols.data.application.ngap.Sequence` -``SEQUENCE OF`` (a list) :obj:`list` -``CHOICE`` / open type :class:`~pcapkit.protocols.data.application.ngap.Choice` -``BIT STRING`` :class:`~pcapkit.protocols.data.application.ngap.BitString` -``INTEGER``, ``OCTET STRING``, kept as :obj:`int`, :obj:`bytes`, :obj:`str` -``ENUMERATED``, ``BOOLEAN`` -=============================== ========================================================== - -That is a deliberate trade. Every one of the 81 elementary procedures and 438 -protocol IEs works on the day it is decoded, and a new 3GPP release needs no +========================================================== ========================================================== +ASN.1 / |pycrate|_ shape :mod:`pcapkit` model +========================================================== ========================================================== +``SEQUENCE`` / ``SET`` (a dict) :class:`~pcapkit.protocols.data.application.ngap.Sequence` +``SEQUENCE OF`` (a list) :obj:`list` +``CHOICE`` / open type :class:`~pcapkit.protocols.data.application.ngap.Choice` +``BIT STRING`` :class:`~pcapkit.protocols.data.application.ngap.BitString` +``INTEGER``, ``OCTET STRING``, ``ENUMERATED``, ``BOOLEAN`` kept as :obj:`int`, :obj:`bytes`, :obj:`str`, :obj:`bool` +========================================================== ========================================================== + +That is a deliberate trade. Every elementary procedure and protocol IE +works on the day it is decoded, and a new 3GPP release needs no code change here; what is given up is per-IE typing, so an IE's value is reported in the specification's own shape rather than as a :mod:`pcapkit`-specific model. The fields worth reading at a glance -- the PDU @@ -81,8 +80,8 @@ * **The specification version is that of the installed decoder, not this package's.** :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_Constants`` of |pycrate|_ 0.8.1 (Release-18-era). A |pycrate|_ that + carries a newer NGAP will decode values these enumerations do not name. Such a value resolves to a non-registering member called ``Unassigned`` rather than failing or adding a member to either enumeration -- see @@ -127,9 +126,10 @@ __all__ = ['NGAP', 'PDUKind', 'Criticality'] #: Cached ``NGAP-PDU`` object, c.f. :func:`load_pycrate`. :data:`NotImplemented` -#: means the import has not been attempted yet, and :data:`None` that it was -#: attempted and |pycrate|_ is not installed -- three states, so a capture full -#: of NGAP packets does not pay for a failing import on every frame. +#: means the import has not been attempted yet, :data:`None` that it was +#: attempted and |pycrate|_ is not installed, and anything else is the loaded +#: object. "Not installed" is kept apart from "not attempted" so that a capture +#: full of NGAP packets does not pay for a failing import on every frame. _PYCRATE = NotImplemented # type: Any #: Guards the module-level ``NGAP-PDU`` object returned by :func:`load_pycrate`. @@ -284,7 +284,7 @@ def _convert(value: 'Any') -> 'Any': :class:`~pcapkit.protocols.data.application.ngap.BitString`. Notes: - The two 2-tuple shapes are told apart by their first element, which is + The 2-tuple shapes are told apart by their first element, which is unambiguous: |pycrate|_ spells a ``CHOICE`` as ``(name, value)`` with a :obj:`str` name and a ``BIT STRING`` as ``(bits, length)`` with two :obj:`int`. Any other tuple is passed through with its members @@ -398,8 +398,8 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_NGAP': with _PDU_LOCK: try: # NOTE: `reset_val()` must not be called here, tempting though it - # looks before a decode into a reused object. It walks all 318 - # submodules of the compiled specification and costs ~105 ms, + # looks before a decode into a reused object. It walks every + # submodule of the compiled specification and costs ~105 ms, # some 700x the ~0.15 ms of the decode itself; and `from_aper()` # overwrites the stored value outright, so it would buy nothing # even if it were free. @@ -455,7 +455,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_NGAP': raise except Exception as exc: # NOTE: `Exception` rather than something narrower on purpose. - # pycrate signals a bad encoding from two unrelated hierarchies + # pycrate signals a bad encoding from unrelated hierarchies # -- `pycrate_asn1rt.err.ASN1Err` and `pycrate_core.charpy. # CharpyErr` -- and a truncated payload can also surface as # `KeyError` or `IndexError` from inside the codec. None of them diff --git a/pcapkit/protocols/application/ospf.py b/pcapkit/protocols/application/ospf.py index 49c0f985a..e2cb8767b 100644 --- a/pcapkit/protocols/application/ospf.py +++ b/pcapkit/protocols/application/ospf.py @@ -44,7 +44,12 @@ .. versionremoved:: 1.5.0 ``pcapkit.protocols.link.ospf``, ``pcapkit.protocols.data.link.ospf`` and - ``pcapkit.protocols.schema.link.ospf``, with no alias (:issue:`719`). + ``pcapkit.protocols.schema.link.ospf``, the ``OSPF`` name that + ``pcapkit.protocols.link``, ``pcapkit.protocols.data.link`` and + ``pcapkit.protocols.schema.link`` each exported, and the + ``OSPF_CrytographicAuthentication`` name that + ``pcapkit.protocols.data.link`` and ``pcapkit.protocols.schema.link`` + exported, with no alias (:issue:`719`, :issue:`1090`). .. versionchanged:: 1.5.0 @@ -273,6 +278,12 @@ def make(self, :obj:`bytes` that are not exactly 8 octets long; or if ``packet_length`` is not an :obj:`int` in ``0..65535``. + .. versionchanged:: 1.5.0 + + ``packet_length`` is a new parameter before ``router_id``, so a + 1.4.x positional argument meant for ``router_id`` or a later + parameter lands one parameter early (:issue:`1249`). + """ type_ = self._make_index(type, type_default, namespace=type_namespace, reversed=type_reversed, pack=False) @@ -405,7 +416,7 @@ def _make_id_numbers(self, id: 'IPv4Address | str | bytes | bytearray') -> 'byte def _read_encrypt_auth(self, schema: 'Schema_CryptographicAuthentication') -> 'Data_CryptographicAuthentication': """Read Authentication field when Cryptographic Authentication is employed, - i.e. :attr:`~OSPF.autype` is ``2``. + i.e. :attr:`~pcapkit.protocols.data.application.ospf.OSPF.autype` is ``2``. Structure of Cryptographic Authentication [:rfc:`2328`]: diff --git a/pcapkit/protocols/application/rarp.py b/pcapkit/protocols/application/rarp.py index 22b035483..a1af47e65 100644 --- a/pcapkit/protocols/application/rarp.py +++ b/pcapkit/protocols/application/rarp.py @@ -38,7 +38,8 @@ .. versionremoved:: 1.5.0 - ``pcapkit.protocols.link.rarp``, with no alias (:issue:`719`). + ``pcapkit.protocols.link.rarp``, and the ``RARP`` and ``DRARP`` names + that ``pcapkit.protocols.link`` exported, with no alias (:issue:`719`). """ from typing import TYPE_CHECKING @@ -61,7 +62,7 @@ class RARP(Application, ARP, schema=Schema_ARP, data=Data_ARP): # pylint: disab :rfc:`1122#section-1.1.3` lists RARP in the application layer, among the "support protocols, used for host name mapping, booting, and management", while putting ARP in the Link Layer chapter at :rfc:`1122#section-2.3.2` -- - two sibling protocols sharing one frame format and one EtherType, placed on + two sibling protocols sharing one frame format, placed on function alone. Hence the two bases: the layer base :class:`~pcapkit.protocols.application.application.Application` first, for ``layer == 'Application'``, then the protocol family base @@ -69,11 +70,11 @@ class RARP(Application, ARP, schema=Schema_ARP, data=Data_ARP): # pylint: disab :doc:`/contributing/conventions/protocol-layer-placement`. Note: - The order is load-bearing, not stylistic. :class:`ARP`'s chain reaches + The order is load-bearing, not stylistic. :class:`~pcapkit.protocols.link.arp.ARP`'s chain reaches :class:`~pcapkit.protocols.link.link.Link`, which *owns* ``__layer__``, so ``class RARP(ARP, Application)`` would report ``'Link'``. - The subpackage does not track the dispatch tier either: RARP is + The subpackage does not track the dispatch tier: RARP is dispatched from :attr:`Link.__proto__ ` at :attr:`~pcapkit.const.reg.ethertype.EtherType.Reverse_Address_Resolution_Protocol`, diff --git a/pcapkit/protocols/data/application/httpv2.py b/pcapkit/protocols/data/application/httpv2.py index ac050cf9a..208cc7c74 100644 --- a/pcapkit/protocols/data/application/httpv2.py +++ b/pcapkit/protocols/data/application/httpv2.py @@ -84,7 +84,7 @@ class DataFrame(HTTP): #: Flags. flags: 'DataFrameFlags' - #: Padded length. + #: Padding length. pad_len: 'int' #: Frame payload. data: 'bytes' @@ -118,7 +118,7 @@ class HeadersFrame(HTTP): #: Flags. flags: 'HeadersFrameFlags' - #: Padded length. + #: Padding length. pad_len: 'int' #: Exclusive dependency. excl_dependency: 'bool' @@ -237,7 +237,7 @@ class PushPromiseFrame(HTTP): #: Flags. flags: 'PushPromiseFrameFlags' - #: Padded length. + #: Padding length. pad_len: 'int' #: Reserved bit of the promised stream ID. promised_reserved: 'int' diff --git a/pcapkit/protocols/data/misc/pcapng.py b/pcapkit/protocols/data/misc/pcapng.py index b16ea2fa6..0b3847a5b 100644 --- a/pcapkit/protocols/data/misc/pcapng.py +++ b/pcapkit/protocols/data/misc/pcapng.py @@ -543,7 +543,7 @@ def __post_init__(self) -> 'None': #: Protocol chain. protocols: 'str' - #: Interface ID, always 0: the block refers to the section's first + #: Interface ID, always 0, since the block refers to the section's first #: interface. interface_id: 'int' #: Timestamp, always the UNIX epoch, as the block carries none. diff --git a/pcapkit/protocols/misc/__init__.py b/pcapkit/protocols/misc/__init__.py index 65e395ed2..ae44a05fb 100644 --- a/pcapkit/protocols/misc/__init__.py +++ b/pcapkit/protocols/misc/__init__.py @@ -6,7 +6,7 @@ .. module:: pcapkit.protocols.misc :mod:`pcapkit.protocols.misc` contains the auxiliary protocol implementations. -Such includes the :class:`~pcapkit.protocols.misc.raw.Raw` class for not-supported +These include the :class:`~pcapkit.protocols.misc.raw.Raw` class for not-supported protocols, the :class:`~pcapkit.protocols.misc.null.NoPayload` class for indication of empty payload, and the PCAP and PCAP-NG file format classes. diff --git a/pcapkit/protocols/misc/pcap/frame.py b/pcapkit/protocols/misc/pcap/frame.py index 98b05103c..0938ac161 100644 --- a/pcapkit/protocols/misc/pcap/frame.py +++ b/pcapkit/protocols/misc/pcap/frame.py @@ -23,7 +23,7 @@ .. versionchanged:: 1.5.0 - ``bytes(frame)`` and :attr:`~pcapkit.protocols.misc.pcap.frame.Frame.packet` + ``bytes(frame)`` and :attr:`~pcapkit.protocols.protocol.Protocol.packet` hold this frame's own octets, where they held the next frame's (:issue:`357`), and the parsed ``time`` is timezone-aware UTC. The parsed ``len`` is the on-wire length and ``cap_len`` the captured one, where the @@ -100,7 +100,7 @@ class Frame(ProtocolBase[Data_Frame, Schema_Frame], # Defaults. ########################################################################## - #: DefaultDict[Enum_LinkType, ModuleDescriptor[ProtocolBase] | ~typing.Type[ProtocolBase]]: Protocol index mapping for + #: Protocol index mapping for #: decoding next layer, c.f. :meth:`self._decode_next_layer ` #: & :meth:`self._import_next_layer `. #: Each value is a :class:`~pcapkit.corekit.module.ModuleDescriptor` naming the @@ -144,8 +144,8 @@ def register(cls, code: 'Enum_LinkType', protocol: 'ModuleDescriptor[ProtocolBas r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{protocol.module}.{protocol.name}``. + The fully qualified class name of the new protocol class + should be ``{protocol.module}.{protocol.name}``. Arguments: code: protocol code as in :class:`~pcapkit.const.reg.linktype.LinkType` @@ -255,8 +255,8 @@ def read(self, length: 'Optional[int]' = None, *, _read: 'bool' = True, \_read: If the class is called in a parsing scenario. \_seek_set: File offset of the record's own first octet, i.e. where the stream stood before :meth:`self.unpack ` consumed - the record; see the note in the parsing branch below for why it - cannot be recovered from the stream once we get here. + the record. It cannot be recovered from the stream here, since + that unpack has consumed the whole record, packet data included. **kwargs: Arbitrary keyword arguments. Returns: @@ -450,7 +450,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Opt **kwargs: Arbitrary keyword arguments. See Also: - For construction argument, please refer to :meth:`make`. + For construction arguments, please refer to :meth:`make`. """ #: int: frame index number @@ -502,7 +502,7 @@ def _check_record_floor(self, length: 'Optional[int]') -> 'None': end of the stream instead lets :meth:`Extractor.record_frames ` stop with the frames read so far, as the PCAP-NG reader does for a truncated block - (:issue:`678`). See GitHub issue :issue:`1265`. + (:issue:`678`). See :issue:`1265`. """ if length is None: @@ -524,11 +524,12 @@ def __index__(self: 'Optional[Frame]' = None) -> 'int': # type: ignore[override self: :class:`Frame` object or :obj:`None`. Returns: - If the object is initiated, i.e. ``self._fnum`` exists, returns the - frame index number of itself; else raises :exc:`UnsupportedCall`. + If the object is initialised, i.e. ``self._fnum`` exists, returns the + frame index number of itself; else raises + :exc:`~pcapkit.utilities.exceptions.UnsupportedCall`. Raises: - UnsupportedCall: This protocol has no registry entry. + UnsupportedCall: If called on the class rather than on a frame. """ if self is None: @@ -613,7 +614,7 @@ def _decode_next_layer(self, dict_: 'Data_Frame', proto: 'Optional[int]' = None, Notes: We added a new key ``__next_type__`` to ``dict_`` to store the next layer protocol type, and a new key ``__next_name__`` to - store the next layer protocol name. These two keys will **NOT** + store the next layer protocol name. These keys will **NOT** be included when :meth:`Info.to_dict ` is called. We also added a new key ``protocols`` to ``dict_`` to store the diff --git a/pcapkit/protocols/misc/pcap/header.py b/pcapkit/protocols/misc/pcap/header.py index a4bccf661..f1d5c1709 100644 --- a/pcapkit/protocols/misc/pcap/header.py +++ b/pcapkit/protocols/misc/pcap/header.py @@ -131,7 +131,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_Header' * ``d4 c3 b2 a1`` -- Little-endian microsecond-timestamp PCAP file. * ``a1 b2 c3 d4`` -- Big-endian microsecond-timestamp PCAP file. * ``4d 3c b2 a1`` -- Little-endian nanosecond-timestamp PCAP file. - * ``a1 b2 3c 4d`` -- Big-endian nano-timestamp PCAP file. + * ``a1 b2 3c 4d`` -- Big-endian nanosecond-timestamp PCAP file. Args: length: Length of data to be read. @@ -248,11 +248,12 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, **kwargs: Arbitrary keyword arguments. See Also: - For construction argument, please refer to :meth:`make`. + For construction arguments, please refer to :meth:`make`. Notes: - A header the data ends inside keeps that in its :attr:`info`, so - that :meth:`from_data` rebuilds only what was captured + A header the data ends inside keeps that in its + :attr:`~pcapkit.protocols.protocol.Protocol.info`, so that + :meth:`~pcapkit.protocols.protocol.ProtocolBase.from_data` rebuilds only what was captured (:issue:`1470`). """ diff --git a/pcapkit/protocols/misc/pcapng.py b/pcapkit/protocols/misc/pcapng.py index 64eb932fb..02c3323ad 100644 --- a/pcapkit/protocols/misc/pcapng.py +++ b/pcapkit/protocols/misc/pcapng.py @@ -16,9 +16,9 @@ .. versionremoved:: 1.5.0 - ``pcapkit.protocols.misc.pcapng.TLSKeyLabel``, now - :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` (:issue:`886`, - :issue:`1744`). + ``pcapkit.protocols.misc.pcapng.TLSKeyLabel``, with no alias; use + :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` instead + (:issue:`886`, :issue:`1744`). .. versionchanged:: 1.5.0 @@ -340,7 +340,7 @@ class PacketDirection(EnumLookup, enum.IntEnum): """Packet direction for ``epb_flags`` options. Shares the :class:`~pcapkit.corekit.enum.EnumLookup` lookup contract that - every non-registry enumeration follows (GitHub issue :issue:`877`). Its + every non-registry enumeration follows (:issue:`877`). Its ``_missing_`` resolves the one value the specification leaves undefined, ``0b11``, to an unassigned member, so a capture carrying it still parses and rebuilds (:issue:`1382`). @@ -377,7 +377,7 @@ class PacketReception(EnumLookup, enum.IntEnum): """Reception type for ``epb_flags`` options. Shares the :class:`~pcapkit.corekit.enum.EnumLookup` lookup contract that - every non-registry enumeration follows (GitHub issue :issue:`877`). Its + every non-registry enumeration follows (:issue:`877`). Its ``_missing_`` resolves the values the specification leaves undefined, ``0b101`` to ``0b111``, to an unassigned member, so a capture carrying one still parses and rebuilds (:issue:`1382`). @@ -418,7 +418,7 @@ def _missing_(cls, value: 'object') -> 'PacketReception': # registry ("TLS SSLKEYLOGFILE Labels", Specification Required) rather than a # closed, hand-picked set, so the TLS key label enumeration is the generated # :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`, imported above as -# ``Enum_TLSKeyLabel`` like its seven :mod:`pcapkit.const.pcapng` siblings. This +# ``Enum_TLSKeyLabel`` like its :mod:`pcapkit.const.pcapng` siblings. This # module does not re-export it under the unaliased name (:issue:`1744`). # :class:`WireGuardKeyLabel` below stays hand-written: it is a closed set # (draft-ietf-opsawg-pcapng-06 section 4.7's "is one of" four names), not a @@ -431,7 +431,7 @@ class WireGuardKeyLabel(EnumLookup, StrEnum): """WireGuard key log label. Shares the :class:`~pcapkit.corekit.enum.EnumLookup` lookup contract that - every non-registry enumeration follows (GitHub issue :issue:`877`); it + every non-registry enumeration follows (:issue:`877`); it defines neither ``get`` nor ``_missing_`` of its own. """ @@ -690,7 +690,7 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], #: PCAP-NG block type. _type: Enum_BlockType - #: PCAP-NG block byteorder. + #: PCAP-NG block byte order. _byte: 'Literal["little", "big"]' PACKET_TYPES = (Enum_BlockType.Enhanced_Packet_Block, @@ -727,7 +727,7 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], # Defaults. ########################################################################## - #: DefaultDict[Enum_LinkType, ModuleDescriptor[ProtocolBase] | ~typing.Type[ProtocolBase]]: Protocol index mapping for + #: Protocol index mapping for #: decoding next layer, c.f. :meth:`self._decode_next_layer ` #: & :meth:`self._import_next_layer `. #: Each value is a :class:`~pcapkit.corekit.module.ModuleDescriptor` naming the @@ -743,8 +743,7 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], }, ) # type: DefaultDict[Enum_LinkType | int, ModuleDescriptor[ProtocolBase] | Type[ProtocolBase]] - #: DefaultDict[Enum_BlockType, str | tuple[BlockParser, BlockConstructor]]: Block - #: type to method mapping. Method names are expected to be referred + #: Block type to method mapping. Method names are expected to be referred #: to the class by ``_read_block_${name}`` and/or ``_make_block_${name}``, #: and if such name not found, the value should then be a method that can #: parse the block by itself. @@ -765,8 +764,7 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], }, ) # type: DefaultDict[Enum_BlockType | int, str | tuple[BlockParser, BlockConstructor]] - #: DefaultDict[Enum_OptionType, str | tuple[OptionParser, OptionConstructor]]: Block - #: option type to method mapping. Method names are expected to be + #: Block option type to method mapping. Method names are expected to be #: referred to the class by ``_read_option_${name}`` and/or ``_make_option_${name}``, #: and if such name not found, the value should then be a method that can #: parse the option by itself. @@ -816,8 +814,7 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], }, ) # type: DefaultDict[Union[Enum_OptionType, Tuple[str, int]], str | tuple[OptionParser, OptionConstructor]] - #: DefaultDict[Enum_RecordType, str | tuple[RecordParser, RecordConstructor]]: Name - #: Resolution Block record type to method mapping. Method names are expected + #: Name Resolution Block record type to method mapping. Method names are expected #: to be referred to the class by ``_read_record_${name}`` and/or ``_make_record_${name}``, #: and if such name not found, the value should then be a method that can #: parse the record by itself. @@ -830,8 +827,7 @@ class PCAPNG(ProtocolBase[Data_PCAPNG, Schema_PCAPNG], }, ) # type: DefaultDict[Enum_RecordType | int, str | tuple[RecordParser, RecordConstructor]] - #: DefaultDict[Enum_SecretsType, str | tuple[SecretsParser, SecretsConstructor]]: Decryption - #: secrets type to method mapping. Method names are expected to + #: Decryption secrets type to method mapping. Method names are expected to #: be referred to the class by ``_read_secrets_${name}`` and/or ``_make_secrets_${name}``, #: and if such name not found, the value should then be a method that can #: parse the decryption secrets by itself. @@ -905,8 +901,8 @@ def packet(self) -> 'Data_Packet': The payload is therefore the block schema's :attr:`~pcapkit.protocols.schema.schema.Schema.__payload__` field, which is where the schema layer put the captured octets, and the header is the - block octets ahead of it. The three block types that carry captured - octets -- :attr:`PACKET_TYPES `, i.e. the Enhanced + block octets ahead of it. The block types that carry captured + octets -- ``PACKET_TYPES``, i.e. the Enhanced Packet Block, the Simple Packet Block and the obsolete Packet Block -- each place the payload at a different offset, so the offset is summed from the octets the schema actually unpacked rather than hard-coded per @@ -954,12 +950,12 @@ def context(self) -> 'Context': @property def byteorder(self) -> 'Literal["big", "little"]': - """Byteorder of the current block.""" + """Byte order of the current block.""" return self._byte @property def nanosecond(self) -> 'bool': - """Whether the timestamp is in nanosecond.""" + """Whether the timestamp is in nanoseconds.""" return self.ts_resolution > RESOLUTION[False] @property @@ -1032,8 +1028,8 @@ def register(cls, code: 'Enum_LinkType', protocol: 'ModuleDescriptor[ProtocolBas r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{protocol.module}.{protocol.name}``. + The fully qualified class name of the new protocol class + should be ``{protocol.module}.{protocol.name}``. Arguments: code: protocol code as in :class:`~pcapkit.const.reg.linktype.LinkType` @@ -1093,7 +1089,7 @@ def register_block(cls, code: 'Enum_BlockType', meth: 'str | tuple[BlockParser, @classmethod def register_option(cls, code: 'Enum_OptionType', meth: 'str | tuple[OptionParser, OptionConstructor]') -> 'None': - """Register a option parser. + """Register an option parser. Args: code: PCAP-NG option type code. @@ -1469,7 +1465,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Opt such that we can be sure that the first block is the section header block. See Also: - For construction argument, please refer to :meth:`make`. + For construction arguments, please refer to :meth:`make`. """ #: int: Section index number. @@ -1522,12 +1518,14 @@ def __index__(self: 'Optional[PCAPNG]' = None) -> 'int': # type: ignore[overrid self: :class:`PCAPNG` object or :obj:`None`. Returns: - If the object is initiated, i.e. ``self._fnum`` exists, and is of a + If the object is initialised, i.e. ``self._fnum`` exists, and is of a packet block (EPB, SPB or Packet), returns the block index number of - itself; else raises :exc:`UnsupportedCall`. + itself; else raises + :exc:`~pcapkit.utilities.exceptions.UnsupportedCall`. Raises: - UnsupportedCall: This protocol has no registry entry. + UnsupportedCall: If called on the class, or on a block that is not a + packet block. """ if self is None or self._info.type not in self.PACKET_TYPES: @@ -1848,7 +1846,7 @@ def _read_timestamp(self, timestamp_high: 'int', timestamp_low: 'int', *, timestamp_raw = (timestamp_high << 32) | timestamp_low with localcontext(prec=64): # NOTE: This is the UTC epoch the docstring above promises, with no - # ``tzone.utcoffset(None)`` added, for two reasons. The block + # ``tzone.utcoffset(None)`` added. The block # timestamp is an offset from the UNIX epoch, so it is # timezone-independent by definition (draft-ietf-opsawg-pcapng-02 # ยง4.3 spells out the arithmetic and has no timezone term), and the @@ -1867,7 +1865,7 @@ def _read_timestamp(self, timestamp_high: 'int', timestamp_low: 'int', *, # datetime. It never moves the instant itself. ts_datetime = datetime.datetime.fromtimestamp(ts_ratio[0] / ts_ratio[1], tzone) # an epoch past what ``datetime`` or the platform's ``time_t`` holds, - # e.g. from an extreme ``if_tsoffset``, raises any of these three + # e.g. from an extreme ``if_tsoffset``, raises one of these except (ValueError, OverflowError, OSError): warn(f'PCAP-NG: [Block {self._type}] invalid timestamp: {timestamp_epoch}', ProtocolWarning, stacklevel=stacklevel()) @@ -2124,7 +2122,7 @@ def _decode_next_layer(self, dict_: 'Data_PCAPNG', proto: 'Optional[int]' = None Notes: We added a new key ``__next_type__`` to ``dict_`` to store the next layer protocol type, and a new key ``__next_name__`` to - store the next layer protocol name. These two keys will **NOT** + store the next layer protocol name. These keys will **NOT** be included when :meth:`Info.to_dict ` is called. We also added a new key ``protocols`` to ``dict_`` to store the @@ -2988,7 +2986,7 @@ def _read_option_unknown(self, schema: 'Schema_UnknownOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ option = Data_UnknownOption( @@ -3007,7 +3005,7 @@ def _read_option_endofopt(self, schema: 'Schema_EndOfOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._opt[schema.type] > 0: @@ -3031,7 +3029,7 @@ def _read_option_comment(self, schema: 'Schema_CommentOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ option = Data_CommentOption( @@ -3065,7 +3063,7 @@ def _read_option_custom(self, schema: 'Schema_CustomOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. Raises: ProtocolError: If the option is shorter than its 4-octet private @@ -3092,7 +3090,7 @@ def _read_option_if_name(self, schema: 'Schema_IF_NameOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3118,7 +3116,7 @@ def _read_option_if_description(self, schema: 'Schema_IF_DescriptionOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3144,7 +3142,7 @@ def _read_option_if_ipv4(self, schema: 'Schema_IF_IPv4AddrOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3169,7 +3167,7 @@ def _read_option_if_ipv6(self, schema: 'Schema_IF_IPv6AddrOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3194,7 +3192,7 @@ def _read_option_if_mac(self, schema: 'Schema_IF_MACAddrOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3222,7 +3220,7 @@ def _read_option_if_eui(self, schema: 'Schema_IF_EUIAddrOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3250,7 +3248,7 @@ def _read_option_if_speed(self, schema: 'Schema_IF_SpeedOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3278,7 +3276,7 @@ def _read_option_if_tsresol(self, schema: 'Schema_IF_TSResolOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3306,7 +3304,7 @@ def _read_option_if_tzone(self, schema: 'Schema_IF_TZoneOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3344,7 +3342,7 @@ def _read_option_if_filter(self, schema: 'Schema_IF_FilterOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3373,7 +3371,7 @@ def _read_option_if_os(self, schema: 'Schema_IF_OSOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3399,7 +3397,7 @@ def _read_option_if_fcslen(self, schema: 'Schema_IF_FCSLenOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3427,7 +3425,7 @@ def _read_option_if_tsoffset(self, schema: 'Schema_IF_TSOffsetOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3455,7 +3453,7 @@ def _read_option_if_hardware(self, schema: 'Schema_IF_HardwareOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3481,7 +3479,7 @@ def _read_option_if_txspeed(self, schema: 'Schema_IF_TxSpeedOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3509,7 +3507,7 @@ def _read_option_if_rxspeed(self, schema: 'Schema_IF_RxSpeedOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Description_Block: @@ -3537,7 +3535,7 @@ def _read_option_epb_flags(self, schema: 'Schema_EPB_FlagsOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Enhanced_Packet_Block: @@ -3579,7 +3577,7 @@ def _read_option_epb_hash(self, schema: 'Schema_EPB_HashOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Enhanced_Packet_Block: @@ -3603,7 +3601,7 @@ def _read_option_epb_dropcount(self, schema: 'Schema_EPB_DropCountOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Enhanced_Packet_Block: @@ -3631,7 +3629,7 @@ def _read_option_epb_packetid(self, schema: 'Schema_EPB_PacketIDOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Enhanced_Packet_Block: @@ -3659,7 +3657,7 @@ def _read_option_epb_queue(self, schema: 'Schema_EPB_QueueOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Enhanced_Packet_Block: @@ -3687,7 +3685,7 @@ def _read_option_epb_verdict(self, schema: 'Schema_EPB_VerdictOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Enhanced_Packet_Block: @@ -3713,7 +3711,7 @@ def _read_option_ns_dnsname(self, schema: 'Schema_NS_DNSNameOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Name_Resolution_Block: @@ -3739,7 +3737,7 @@ def _read_option_ns_dnsipv4(self, schema: 'Schema_NS_DNSIP4AddrOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Name_Resolution_Block: @@ -3767,7 +3765,7 @@ def _read_option_ns_dnsipv6(self, schema: 'Schema_NS_DNSIP6AddrOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Name_Resolution_Block: @@ -3795,7 +3793,7 @@ def _read_option_isb_starttime(self, schema: 'Schema_ISB_StartTimeOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Statistics_Block: @@ -3827,7 +3825,7 @@ def _read_option_isb_endtime(self, schema: 'Schema_ISB_EndTimeOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Statistics_Block: @@ -3859,7 +3857,7 @@ def _read_option_isb_ifrecv(self, schema: 'Schema_ISB_IFRecvOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Statistics_Block: @@ -3887,7 +3885,7 @@ def _read_option_isb_ifdrop(self, schema: 'Schema_ISB_IFDropOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Statistics_Block: @@ -3915,7 +3913,7 @@ def _read_option_isb_filteraccept(self, schema: 'Schema_ISB_FilterAcceptOption', options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Statistics_Block: @@ -3943,7 +3941,7 @@ def _read_option_isb_osdrop(self, schema: 'Schema_ISB_OSDropOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Statistics_Block: @@ -3971,7 +3969,7 @@ def _read_option_isb_usrdeliv(self, schema: 'Schema_ISB_UsrDelivOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Interface_Statistics_Block: @@ -3999,7 +3997,7 @@ def _read_option_pack_flags(self, schema: 'Schema_PACK_FlagsOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Packet_Block: @@ -4041,7 +4039,7 @@ def _read_option_pack_hash(self, schema: 'Schema_PACK_HashOption', *, options: Parsed PCAP-NG options. Returns: - Constructed option data. + Parsed option data. """ if self._type != Enum_BlockType.Packet_Block: @@ -4104,7 +4102,7 @@ def _read_record_unknown(self, schema: 'Schema_UnknownRecord', *, records: Parsed PCAP-NG records. Returns: - Constructed name resolution record data. + Parsed name resolution record data. """ record = Data_UnknownRecord( @@ -4123,7 +4121,7 @@ def _read_record_end(self, schema: 'Schema_EndRecord', *, records: Parsed PCAP-NG records. Returns: - Constructed name resolution record data. + Parsed name resolution record data. """ if schema.length != 0: @@ -4144,7 +4142,7 @@ def _read_record_ipv4(self, schema: 'Schema_IPv4Record', *, records: Parsed PCAP-NG records. Returns: - Constructed name resolution record data. + Parsed name resolution record data. """ record = Data_IPv4Record( @@ -4164,7 +4162,7 @@ def _read_record_ipv6(self, schema: 'Schema_IPv6Record', *, records: Parsed PCAP-NG records. Returns: - Constructed name resolution record data. + Parsed name resolution record data. """ record = Data_IPv6Record( @@ -4218,7 +4216,7 @@ def _read_secrets_wireguard(self, schema: 'Schema_WireGuardKeyLog', *, block: Parsed PCAP-NG decryption secrets block. Returns: - Constructed decryption secrets data. + Parsed decryption secrets data. """ secrets = Data_WireGuardKeyLog( @@ -4268,7 +4266,7 @@ def _read_secrets_zigbee_nwk(self, schema: 'Schema_ZigBeeNWKKey', *, block: Parsed PCAP-NG decryption secrets block. Returns: - Constructed decryption secrets data. + Parsed decryption secrets data. """ secrets = Data_ZigBeeNWKKey( @@ -4317,7 +4315,7 @@ def _read_secrets_zigbee_aps(self, schema: 'Schema_ZigBeeAPSKey', *, block: Parsed PCAP-NG decryption secrets block. Returns: - Constructed decryption secrets data. + Parsed decryption secrets data. """ secrets = Data_ZigBeeAPSKey( diff --git a/pcapkit/protocols/misc/raw.py b/pcapkit/protocols/misc/raw.py index ee599f024..3ac5e69de 100644 --- a/pcapkit/protocols/misc/raw.py +++ b/pcapkit/protocols/misc/raw.py @@ -120,12 +120,14 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, Args: file: Source packet stream. length: Length of packet data. - error (Optional[Exception]): Parsing errors if any (for parsing). - alias (Optional[int]): Original enumeration of the unknown protocol. + error: Parsing error if any (for parsing), an :class:`Exception` + instance or :obj:`None`. + alias: Original enumeration of the unknown protocol, an :obj:`int` + or :obj:`None`. **kwargs: Arbitrary keyword arguments. See Also: - For construction argument, please refer to :meth:`make`. + For construction arguments, please refer to :meth:`make`. """ if file is None: diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index 0dd44595c..f4b7d5217 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -306,7 +306,7 @@ def _declared_keywords(cls: 'type') -> 'Optional[frozenset[str]]': # its keywords because it forwards them to whichever of # :class:`HTTPv1 ` and # :class:`HTTPv2 ` the - # ``version`` names, but those two declare theirs in full, and + # ``version`` names, but each of those declares its own in full, and # inheriting the opt-out would exempt the very classes that can be # checked. A subclass that dispatches in turn says so itself. if klass is cls: @@ -529,7 +529,7 @@ class ProtocolBase(Generic[_PT, _ST], metaclass=ProtocolMeta): #: of declaring as a parameter, e.g. with ``kwargs.get('spam')`` in #: :meth:`read` -- as :meth:`ESP.read ` #: does with ``packet``. - #: :func:`~pcapkit.protocols.protocol._declared_keywords` finds keywords by + #: ``_declared_keywords()`` finds keywords by #: reading signatures, which cannot see such a name, so a protocol that #: consumes one names it here and the construction check of :meth:`__init__` #: accepts it. The union over the MRO is used, so a subclass need not repeat @@ -553,8 +553,8 @@ class ProtocolBase(Generic[_PT, _ST], metaclass=ProtocolMeta): #: unknown keyword. Unlike a set, the :obj:`None` is **not** inherited: a #: subclass of a dispatcher is checked normally unless it dispatches too and #: says so, because ``HTTPv1`` and ``HTTPv2`` declare their keywords in full - #: and exempting them along with their base would forgo the check on the only - #: two classes here that can have it. + #: and exempting them along with their base would forgo the check on the very + #: classes that can have it. __keywords__: 'Optional[frozenset[str]]' = frozenset() #: Whether this instance is being rebuilt by :meth:`from_data` from a parsed @@ -642,7 +642,7 @@ def protochain(self) -> 'ProtoChain': # packet data @cached_property def packet(self) -> 'Data_Packet': - """Data_Packet data of the protocol. + """Packet data of the protocol. Note: The split relies on :attr:`self.length ` being the length of @@ -877,7 +877,7 @@ def decode(byte: bytes, *, encoding: 'Optional[str]' = None, errors: 'Literal["strict", "ignore", "replace"]' = 'strict') -> 'str': """Decode :obj:`bytes` into :obj:`str`. - Should decoding failed using ``encoding``, the method will try again decoding + Should decoding fail using ``encoding``, the method will try again decoding the :obj:`bytes` as ``'unicode_escape'`` with ``'replace'`` for error handling. See Also: @@ -909,7 +909,7 @@ def unquote(url: str, *, encoding: 'str' = 'utf-8', errors: 'Literal["strict", "ignore", "replace"]' = 'replace') -> 'str': """Unquote URLs into readable format. - Should decoding failed , the method will try again replacing ``'%'`` with ``'\\x'`` then + Should decoding fail, the method will try again replacing ``'%'`` with ``'\\x'`` then decoding the ``url`` as ``'unicode_escape'`` with ``'replace'`` for error handling. See Also: @@ -942,7 +942,7 @@ def expand_comp(value: 'str | ProtocolBase | Type[ProtocolBase]') -> 'tuple': protocol class, and the protocol names in upper case obtained from :meth:`Protocol.id `. 2. If ``value`` is a protocol class, the method will return the - protocol class itself, and the protocols names in upper case + protocol class itself, and the protocol names in upper case obtained from :meth:`Protocol.id `. 3. If ``value`` is :obj:`str`, the method will attempt to search for the existing registered protocol class from @@ -1003,8 +1003,8 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor | Type[ProtocolBase]' r"""Register a new protocol class. Notes: - The full qualified class name of the new protocol class - should be as ``{protocol.module}.{protocol.name}``. + The fully qualified class name of the new protocol class + should be ``{protocol.module}.{protocol.name}``. Arguments: code: protocol code @@ -1039,7 +1039,7 @@ class object, or a :class:`~pcapkit.corekit.module.ModuleDescriptor` A descriptor equal to the one ``code`` shipped with restores that shipped object, unresolved, so undoing an override leaves the entry - as shipped (:issue:`1504`); see :meth:`_next_layer_entry`, which + as shipped (:issue:`1504`); see ``_next_layer_entry()``, which every layer's ``register`` shares. """ @@ -1211,19 +1211,25 @@ def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional (:attr:`self._exlayer `). While parsing, the un-prefixed ``layer`` is accepted as well -- see the note below. - _protocol (Union[str, Protocol, Type[Protocol]]): Parse packet until ``_protocol`` - (:attr:`self._exproto `). + _protocol: Parse packet until ``_protocol`` + (:attr:`self._exproto `), + given as a protocol name (:obj:`str`), a protocol class or a + protocol instance. While parsing, the un-prefixed ``protocol`` is accepted as well -- see the note below. - packet (dict[str, Any]): Packet context of the enclosing layer, as + packet: Packet context of the enclosing layer, a :obj:`dict` as handed over by :meth:`self._import_next_layer `. While parsing, it is republished as ``__packet__`` so that :meth:`self.unpack ` -- and through it the schema -- can see it; see the note below. - __context__ (Union[ContextRegistry, ProtocolContext, Mapping[str, ProtocolContext], Iterable[ProtocolContext]]): - Caller supplied parsing context (:attr:`self._exctx `), - c.f. :mod:`pcapkit.corekit.context`. It is consumed here rather + __context__: Caller supplied parsing context (:attr:`self._exctx `), + c.f. :mod:`pcapkit.corekit.context`: a + :class:`~pcapkit.corekit.context.ContextRegistry`, a + :class:`~pcapkit.corekit.context.ProtocolContext`, a mapping of + names to :class:`~pcapkit.corekit.context.ProtocolContext`, or an + iterable of :class:`~pcapkit.corekit.context.ProtocolContext`. + It is consumed here rather than being forwarded to :meth:`self.read `, and is propagated to nested layers by :meth:`self._import_next_layer `. @@ -1238,15 +1244,20 @@ def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional given) is unaffected. Note: - Three of the keywords above are *out-of-band*: they configure the - parse rather than describing the packet, and every one of them is - consumed here, at the one point each of a protocol's producers passes - through. That is deliberate, and the normalisation below relies on it: fixing - a spelling here fixes it for the engines, for all four + The ``_layer``, ``_protocol`` and ``__context__`` keywords are + *out-of-band*: they configure the parse rather than describing the + packet, and every one of them is consumed here, at the one point + each of a protocol's producers passes through. That is deliberate, + and the normalisation below relies on it: fixing a spelling here + fixes it for the engines, for every :meth:`_import_next_layer ` - implementations, and for any third party protocol that copied their + implementation, and for any third party protocol that copied their shape, rather than one call site at a time. + ``packet`` is read but not consumed: it stays in the keywords + forwarded to :meth:`__post_init__`, and while parsing a copy is + republished beside it as ``__packet__``. + """ #logger.debug('%s(file, %s, **%s)', type(self).__name__, length, kwargs) @@ -1260,9 +1271,9 @@ def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional #: int: File pointer. self._seekset = io.SEEK_SET # type: int - #: str: Parse packet until such layer. + #: Parse packet until such layer. self._exlayer = kwargs.pop('_layer', None) # type: Optional[str] - #: str: Parse packet until such protocol. + #: Parse packet until such protocol. self._exproto = kwargs.pop('_protocol', None) # type: Optional[str | ProtocolBase | Type[ProtocolBase]] # NOTE: The parse limits are documented as ``_layer`` and ``_protocol``, @@ -1304,7 +1315,7 @@ def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional __context__ = kwargs.pop('__context__', None) self._exctx = (__context__ if isinstance(__context__, ContextRegistry) else ContextRegistry.make(__context__)) # type: ContextRegistry - #: bool: If terminate parsing next layer of protocol. + #: bool: Whether to stop parsing the next layer of protocol. self._sigterm = self._check_term_threshold() # NOTE: The enclosing layer's packet context arrives as ``packet=`` -- the @@ -1340,7 +1351,7 @@ def __init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional # does not know, and this closes the asymmetry from the other end. # # Parsing is left alone. There, the keywords are not field values but - # whatever the engines and the four ``_import_next_layer`` + # whatever the engines and the ``_import_next_layer`` # implementations forward -- ``alias``, ``packet``, and the limits # normalised above -- and a protocol has no way to know which of its # ancestors' keywords its parent chose to pass on. Nothing is lost that @@ -1816,7 +1827,7 @@ def _read_packet(self, length: 'Optional[int]' = None, *, header: 'Optional[int] payload: length of the packet payload discard: flag if discard header data - * If ``header`` omits, returns the whole packet data in :obj:`bytes`. + * If ``header`` is omitted, returns the whole packet data in :obj:`bytes`. * If ``discard`` is set as :data:`True`, returns the packet body (in :obj:`bytes`) only. * Otherwise, returns the header and payload data as @@ -1950,7 +1961,7 @@ def _make_index(cls, name: 'str | int | StdlibEnum | AenumEnum', default: 'Optio the namespace is inferred from the calling ``make`` method: it is the enumeration class of the default value of the argument that ``name`` was passed as, found from the call in the - caller's source (see :meth:`_make_index_namespace`). + caller's source (see ``_make_index_namespace()``). """ if isinstance(name, (enum.Enum, aenum.Enum)): @@ -2105,7 +2116,7 @@ def _make_payload(cls, data: 'Data', exclude: 'tuple[str, ...]' = ()) -> 'Protoc :meth:`Info.to_dict ` leaves both keys out, so ``data`` rebuilt from such a :obj:`dict` carries neither. The payload is then found from the structure of ``data`` - instead, by :meth:`_lookup_payload`. + instead, by ``_lookup_payload()``. The payload is built one layer further into the frame than this protocol, as :meth:`_parse_next_layer` counts it, since each layer built @@ -2118,7 +2129,7 @@ def _make_payload(cls, data: 'Data', exclude: 'tuple[str, ...]' = ()) -> 'Protoc Args: data: protocol data exclude: keys of ``data`` that are not the payload, though they may - look like it, passed on to :meth:`_lookup_payload` + look like it, passed on to ``_lookup_payload()`` Returns: Payload for protocol construction. @@ -2321,9 +2332,9 @@ def _decode_next_layer(self, dict_: '_PT', proto: 'int', length: 'Optional[int]' Notes: We added a new key ``__next_type__`` to ``dict_`` to store the next layer protocol type, and a new key ``__next_name__`` to - store the next layer protocol name. These two keys will **NOT** + store the next layer protocol name. These keys will **NOT** be included when :meth:`Info.to_dict ` is called; - :meth:`_lookup_payload` finds the payload of such a :obj:`dict` instead. + ``_lookup_payload()`` finds the payload of such a :obj:`dict` instead. """ next_ = cast('ProtocolBase', self._import_next_layer(proto, length, packet=packet)) # type: ignore[misc,call-arg,redundant-cast] diff --git a/pcapkit/protocols/schema/application/httpv2.py b/pcapkit/protocols/schema/application/httpv2.py index 754e2fa88..7618f18b9 100644 --- a/pcapkit/protocols/schema/application/httpv2.py +++ b/pcapkit/protocols/schema/application/httpv2.py @@ -141,7 +141,7 @@ class Flags(EnumLookup, enum.IntFlag): """Flags enumeration for HTTP/2 frames. Built on :class:`~pcapkit.corekit.enum.EnumLookup`, the lookup contract - shared by every non-registry enumeration. The six concrete per-frame + shared by every non-registry enumeration. The concrete per-frame subclasses below each declare ``class Flags(FrameType.Flags):`` with no base list of their own, so they inherit :class:`~pcapkit.corekit.enum.EnumLookup` transitively rather than needing it repeated. @@ -166,13 +166,13 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Schema': # -- routine in HTTP/2, not an edge case -- leaves an ``int`` behind and # a membership test against it raises :exc:`TypeError` rather than # answering. This mirrors the construct path, which seeds - # ``Flags(0)`` at every one of its six sites. + # ``Flags(0)`` at every site that builds one. # # ``FrameType.Flags`` itself declares no members, and from Python 3.11 # the enum rewrite makes a memberless :class:`enum.Flag` subclass refuse # ``Flags(0)`` outright with ``TypeError: has no members # defined`` (3.10 and earlier hand back a pseudo-member instead), so - # the five frame schemas that inherit it unchanged -- ``UnassignedFrame``, + # the frame schemas that inherit it unchanged -- ``UnassignedFrame``, # ``PriorityFrame``, ``RSTStreamFrame``, ``GoawayFrame`` and # ``WindowUpdateFrame`` -- keep the plain ``int``, which is what their # data objects' bare ``Flags`` carries as ``__value__``. @@ -217,8 +217,6 @@ class Flags(FrameType.Flags): UInt8Field(), lambda pkt: pkt['flags']['bit_3'], # PADDED ) - #: Data. - # # NOTE: the conditional subtracts the padding length, it does not *replace* # the whole expression. A conditional expression binds looser than ``-``, so # writing ``pkt['__length__'] - pkt['pad_len'] if else 0`` groups as @@ -232,6 +230,7 @@ class Flags(FrameType.Flags): # ``pkt['pad_len']`` from being read at all when ``PADDED`` is clear, where # the :class:`~pcapkit.corekit.fields.misc.ConditionalField` above has left # it as :data:`~pcapkit.corekit.sentinels.NO_VALUE`. + #: Data. data: 'bytes' = BytesField(length=lambda pkt: pkt['__length__'] - ( pkt['pad_len'] if pkt['flags']['bit_3'] else 0 )) @@ -275,12 +274,11 @@ class Flags(FrameType.Flags): UInt8Field(), lambda pkt: pkt['flags']['bit_5'], # PRIORITY ) - #: Header block fragment. - # # NOTE: the conditional subtracts the padding length rather than replacing # the whole expression -- see :attr:`DataFrame.data` for why the outer # parentheses are load-bearing. Without them an unpadded ``HEADERS`` frame # would read its header block fragment as ``b''``. + #: Header block fragment. fragment: 'bytes' = BytesField(length=lambda pkt: pkt['__length__'] - ( pkt['pad_len'] if pkt['flags']['bit_3'] else 0 )) @@ -373,12 +371,11 @@ class Flags(FrameType.Flags): 'reserved': (0, 1), 'sid': (1, 31), }) - #: Header block fragment. - # # NOTE: the conditional subtracts the padding length rather than replacing # the whole expression -- see :attr:`DataFrame.data` for why the outer # parentheses are load-bearing. Without them an unpadded ``PUSH_PROMISE`` # frame would read its header block fragment as ``b''``. + #: Header block fragment. fragment: 'bytes' = BytesField(length=lambda pkt: pkt['__length__'] - ( pkt['pad_len'] if pkt['flags']['bit_3'] else 0 )) diff --git a/pcapkit/protocols/schema/application/ospf.py b/pcapkit/protocols/schema/application/ospf.py index 58e482acf..a6ac4932f 100644 --- a/pcapkit/protocols/schema/application/ospf.py +++ b/pcapkit/protocols/schema/application/ospf.py @@ -29,9 +29,10 @@ def ospf_auth_data_selector(pkt: 'dict[str, Any]') -> 'Field': pkt: Packet data. Returns: - * If :attr:`OSPF.auth_type` is 2, a :class:`~pcapkit.corekit.fields.misc.SchemaField` - wrapped :class:`~pcapkit.protocols.schema.application.ospf.CryptographicAuthentication` instance. - * Otherwise, a :class:`~pcapkit.corekit.fields.strings.BytesField` instance. + A :class:`~pcapkit.corekit.fields.misc.SchemaField` wrapped + :class:`~pcapkit.protocols.schema.application.ospf.CryptographicAuthentication` + instance if :attr:`OSPF.auth_type` is 2, and a + :class:`~pcapkit.corekit.fields.strings.BytesField` instance otherwise. """ if pkt['auth_type'] == Enum_Authentication.Cryptographic_authentication: diff --git a/pcapkit/protocols/schema/misc/pcap/frame.py b/pcapkit/protocols/schema/misc/pcap/frame.py index 20508286a..177953855 100644 --- a/pcapkit/protocols/schema/misc/pcap/frame.py +++ b/pcapkit/protocols/schema/misc/pcap/frame.py @@ -30,7 +30,7 @@ class Frame(Schema): #: Timestamp seconds. ts_sec: 'int' = UInt32Field(callback=byteorder_callback) - #: Timestamp microseconds. + #: Timestamp microseconds, or nanoseconds in a nanosecond-resolution file. ts_usec: 'int' = UInt32Field(callback=byteorder_callback) #: Number of octets of packet saved in file. incl_len: 'int' = UInt32Field(callback=byteorder_callback) diff --git a/pcapkit/protocols/schema/misc/pcapng.py b/pcapkit/protocols/schema/misc/pcapng.py index 71ca04a30..0154ecb92 100644 --- a/pcapkit/protocols/schema/misc/pcapng.py +++ b/pcapkit/protocols/schema/misc/pcapng.py @@ -75,9 +75,9 @@ from typing_extensions import TypedDict class ByteorderTest(TypedDict): - """Test for byteorder.""" + """Test for byte order.""" - #: Byteorder magic number. + #: Byte-order magic number. byteorder: int class ResolutionData(TypedDict): @@ -179,8 +179,8 @@ class PACKFlags(TypedDict): #: reader sees a little-endian section's. _SWAPPED_BYTE_ORDER_MAGIC = int.from_bytes(BYTE_ORDER_MAGIC.to_bytes(4, 'little'), 'big') -#: The octets every block's fixed fields span: Block Type and both copies of -#: Block Total Length, and so the shortest Block Total Length of any block, and +#: The octets every block's fixed fields span -- Block Type and both copies of +#: Block Total Length -- and so the shortest Block Total Length of any block, and #: the whole of it for a block not in :data:`BLOCK_MINIMUM`. The one home of #: this value (:issue:`1739`); the format fixes it, so it is never configurable. #: @@ -302,7 +302,7 @@ def nonnegative(length: 'Callable[[dict[str, Any]], int]') -> 'Callable[[dict[st ``calcsize`` does -- where checking against the remainder would have refused a perfectly good option. - That distinction is what keeps the three decryption-secrets payloads -- + That distinction is what keeps the decryption-secrets payloads -- :attr:`UnknownSecrets.data`, :attr:`TLSKeyLog.data` and :attr:`WireGuardKeyLog.data` -- out of this. They read ``__length__`` *whole* rather than subtracting from it, and ``Schema.pack`` leaves it at @@ -438,7 +438,7 @@ def bounded_area(length: 'Callable[[dict[str, Any]], int]') -> 'Callable[[dict[s octets of payload on a block that holds none. Note: - The five non-packet blocks' option areas -- Section Header, Interface + The non-packet blocks' option areas -- Section Header, Interface Description, Name Resolution, Interface Statistics and Decryption Secrets -- are deliberately left unclamped *against the block* here, since each computes its span with a different offset and the equality @@ -511,9 +511,9 @@ def pcapng_block_selector(packet: 'dict[str, Any]') -> 'Field': packet: Packet data. Returns: - Returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` - wrapped :class:`~pcapkit.protocols.schema.misc.pcapng.BlockType` - subclass instance. + A :class:`~pcapkit.corekit.fields.misc.SchemaField` wrapped + :class:`~pcapkit.protocols.schema.misc.pcapng.BlockType` subclass + instance. See Also: * :class:`pcapkit.const.pcapng.block_type.BlockType` @@ -567,14 +567,10 @@ def dsb_secrets_selector(packet: 'dict[str, Any]') -> 'Field': packet: Packet data. Returns: - * If ``secrets_type`` is unknown, returns a - :class:`~pcapkit.corekit.fields.strings.BytesField` instance. - * If ``secret_type`` is :attr:`~pcapkit.const.pcapng.secrets_type.SecretsType.TLS_Key_Log` - and/or :attr:`~pcapkit.const.pcapng.secrets_type.SecretsType.WireGuard_Key_Log`, - returns a :class:`~pcapkit.corekit.fields.strings.StringField` instance. - * Otherwise, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField` - wrapped :class:`~pcapkit.protocols.schema.misc.pcapng.DSBSecrets` - subclass instance. + A :class:`~pcapkit.corekit.fields.misc.SchemaField` wrapped instance of + the :class:`~pcapkit.protocols.schema.misc.pcapng.DSBSecrets` subclass + registered for ``secrets_type``, or of + :class:`~pcapkit.protocols.schema.misc.pcapng.UnknownSecrets` if none is. See Also: * :class:`pcapkit.const.pcapng.secrets_type.SecretsType` @@ -753,7 +749,7 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Enum_ :attr:`~pcapkit.const.pcapng.option_type.OptionType.opt_value` are never set -- which is *why* the override exists, choosing to render correctly over round-tripping correctly. - :meth:`EnumField._unregistered_member` is the only one of the three + :meth:`EnumField._unregistered_member` is the only one of these that gets both right. :func:`copy.deepcopy` is not the difference: it succeeds on both kinds, since the standard library's :class:`enum.Enum` (which :class:`aenum.Enum` subclasses) defines @@ -785,7 +781,7 @@ def post_process(self, value: 'int | bytes', packet: 'dict[str, Any]') -> 'Enum_ @schema_final class PCAPNG(Schema): - """Header schema for PCAP-NG file blocks.""" + """Header schema for a PCAP-NG file block: its type, then its body.""" #: Block type. type: 'Enum_BlockType' = EnumField(length=4, namespace=Enum_BlockType, callback=byteorder_callback) @@ -799,7 +795,7 @@ def __init__(self, type: 'Enum_BlockType', block: 'BlockType | bytes') -> 'None' class BlockType(EnumSchema[Enum_BlockType]): - """Header schema for PCAP-NG file blocks.""" + """Base header schema for the body of each PCAP-NG block type.""" __default__ = lambda: UnknownBlock @@ -874,7 +870,7 @@ def __init_subclass__(cls, /, code: 'Optional[Enum_OptionType | Iterable[Enum_Op or a list of option type enumerations. ns: Namespace of option type enumeration. If not given, the value will be inferred from the option type code. Spelled ``ns`` rather - than ``namespace`` because :meth:`abc.ABCMeta.__new__` names its + than ``namespace`` because ``abc.ABCMeta.__new__()`` names its own fourth parameter ``namespace``, and before Python 3.11 that parameter is positional-or-keyword rather than positional-only -- so a class keyword literally called ``namespace`` binds it twice. @@ -1063,7 +1059,7 @@ def __init__(self, type: 'int', length: 'int', pen: 'int', data: 'bytes') -> 'No class SectionHeaderBlock(BlockType, code=Enum_BlockType.Section_Header_Block): """Header schema for PCAP-NG Section Header Block (SHB).""" - #: Fast forward field to test the byteorder. + #: Fast-forward field to test the byte order. match: 'ByteorderTest' = ForwardMatchField(BitField(length=8, namespace={ 'byteorder': (32, 32), })) @@ -1118,7 +1114,7 @@ def pre_pack(self, packet: 'dict[str, Any]') -> 'None': def post_process(self, packet: 'dict[str, Any]') -> 'SectionHeaderBlock': """Revise ``schema`` data after unpacking process. - This method calculates the byteorder value based on + This method calculates the byte order value based on the parsed schema. Args: @@ -1152,7 +1148,7 @@ def post_process(self, packet: 'dict[str, Any]') -> 'SectionHeaderBlock': return self if TYPE_CHECKING: - #: Byteorder. + #: Byte order. byteorder: Literal['big', 'little'] def __init__(self, length: 'int', magic: 'Literal[0x1A2B3C4D]', major: 'int', @@ -2047,8 +2043,8 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Self': A line of nothing but NUL octets at the end of the entry is the block's own 32-bit padding and ends it. - The walk is strict (:issue:`1406`, :issue:`1413`, the #1325 ruling - that :class:`OptionAreaField` applies to option areas). Any of the + The walk is strict (:issue:`1406`, :issue:`1413`, and :issue:`1325`, + under which :class:`OptionAreaField` applies to option areas). Any of the following raises :exc:`~pcapkit.utilities.exceptions.ProtocolError`, and the whole entry is then kept as the octets captured, as the one :obj:`bytes` item of :attr:`data`, which the block's ``make`` writes @@ -2058,7 +2054,7 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Self': newline, e.g. a bare ``KEY`` at the end of the entry; * a binary field whose 64-bit length prefix is cut short, or declares more octets than the entry has left -- at ``2**63`` and above - :meth:`io.BytesIO.read` would refuse it with a bare + ``io.BytesIO.read()`` would refuse it with a bare :exc:`OverflowError`; * a binary field's value not followed by the newline that terminates it, including one that ends the entry; @@ -2416,9 +2412,9 @@ class CustomBlock(BlockType, code=[Enum_BlockType.Custom_Block_that_rewriters_ca length: 'int' = UInt32Field(callback=byteorder_callback) #: Private enterprise number. pen: 'int' = UInt32Field(callback=byteorder_callback) - #: Custom data (incl. padding and options). # NOTE: both custom block types span the same fixed fields, so the one # entry stands for both. + #: Custom data (incl. padding and options). data: 'bytes' = BytesField(length=nonnegative( lambda pkt: pkt['length'] - BLOCK_MINIMUM[Enum_BlockType.Custom_Block_that_rewriters_can_copy_into_new_files])) #: Block total length. diff --git a/pcapkit/protocols/schema/schema.py b/pcapkit/protocols/schema/schema.py index 7924216a6..a0f934503 100644 --- a/pcapkit/protocols/schema/schema.py +++ b/pcapkit/protocols/schema/schema.py @@ -88,7 +88,7 @@ class marked with a user's own ``from typing import final`` on 3.10 is """ # NOTE: keyed on ``__finalised__`` rather than on ``final``'s ``__final__``, # and read out of ``cls.__dict__`` rather than through ``getattr``, for the - # two reasons given in :func:`pcapkit.corekit.infoclass.info_final`, which + # reasons given in :func:`pcapkit.corekit.infoclass.info_final`, which # makes both choices: only ``__finalised__`` records *this function* having # run, so a ``__final__`` test would make ``@schema_final`` over ``@final`` # skip the generation while the opposite order did the work; and @@ -210,7 +210,7 @@ class SchemaMeta(abc.ABCMeta): .. seealso:: - This is implemented thru setting up the initial field dictionary + This is implemented through setting up the initial field dictionary in the |prepare|_ method, and then inherit the field dictionaries from the base classes. @@ -230,7 +230,7 @@ class SchemaMeta(abc.ABCMeta): .. note:: - This is implemented thru the :meth:`~object.__new__` method, which + This is implemented through the :meth:`~object.__new__` method, which will inherit the additional and excluded field names from the base classes, as well as populating the additional and excluded field from the subclass attributes. @@ -274,12 +274,12 @@ def __prepare__(cls, name: 'str', bases: 'tuple[type, ...]', /, **kwds: 'Any') - #: Class keywords that collide with a parameter this class does not #: control, so a schema class declared with one fails with an opaque #: ``TypeError`` from several frames away instead of a clear message here. - #: Two different collisions, both reserved: + #: Collisions of different kinds, all reserved: #: #: ``mcls``, ``name``, ``bases``, ``namespace`` collide with #: :meth:`abc.ABCMeta.__new__` -- a *different* function from this one, one #: level up the ``super().__new__(...)`` call below. Before Python 3.11 - #: those four are positional-or-keyword there (from 3.11 they are + #: those are positional-or-keyword there (from 3.11 they are #: positional-only, ``def __new__(mcls, name, bases, namespace, /, #: **kwargs)``), so a class keyword spelled the same as any of them binds #: that parameter twice: ``TypeError: ABCMeta.__new__() got multiple @@ -303,7 +303,7 @@ def __prepare__(cls, name: 'str', bases: 'tuple[type, ...]', /, **kwds: 'Any') - #: :func:`inspect.signature(abc.ABCMeta.__new__) ` at #: import time: the signature's positional-only marker differs across #: Python versions, introspecting a CPython internal's exact shape to - #: guard against a CPython internal's exact shape is circular, and five + #: guard against a CPython internal's exact shape is circular, and #: names that will not change are cheaper to read than the machinery to #: recompute them. #: @@ -426,7 +426,7 @@ def __init_subclass__(cls, /, *args: 'Any', **kwargs: 'Any') -> 'None': # NOTE: the whole ancestry rather than ``cls.__bases__``, and # ``base.__dict__`` rather than ``getattr`` -- see # :meth:`pcapkit.corekit.infoclass.Info.__init_subclass__`, which makes - # the same two choices for the same two reasons. + # the same choices for the same reasons. for base in cls.__mro__[1:]: if base.__dict__.get('__final__'): raise SchemaError(f'{cls.__name__}: cannot subclass {base.__name__}, ' @@ -449,7 +449,7 @@ def __new__(cls, *args: '_VT', **kwargs: '_VT') -> 'Self': # pylint: disable=un Note: Whether ``cls`` still needs finalising is read from its *own* - ``__dict__``, never inherited (GitHub issue :issue:`1498`). Each + ``__dict__``, never inherited (:issue:`1498`). Each class is finalised on its first construction, whatever its ancestors' state, so a subclass of a :attr:`~pcapkit.corekit.infoclass.FinalisedState.BASE` ancestor @@ -726,7 +726,7 @@ def to_dict(self) -> 'dict[str, _VT]': they are the direct value of the :class:`Schema` object's attribute. Should such :class:`Schema` objects be nested within other data, types, such as :obj:`list`, :obj:`tuple`, :obj:`set`, etc., we - shall not convert them into :obj:`dict` and remain them intact. + leave them intact rather than converting them into :obj:`dict`. Note: A schema unpacked as the value of a @@ -1032,7 +1032,7 @@ def unpack(cls, data: 'bytes | IO[bytes]', key. See that function for the exact lookup, write and iteration semantics. - And an ``__option_padding__`` key in the ``packet`` to record how + We also use an ``__option_padding__`` key in ``packet`` to record how much of an :class:`~pcapkit.corekit.fields.collections.OptionField`'s declared area it did not consume. What follows that area decides what the diff --git a/tests/project/test_docs_enum_value_hook_unit.py b/tests/project/test_docs_enum_value_hook_unit.py new file mode 100644 index 000000000..ac12b66e8 --- /dev/null +++ b/tests/project/test_docs_enum_value_hook_unit.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +"""Pins the docs build's skip of the enum machinery's ``_generate_next_value_``. + +The enum metaclass copies ``_generate_next_value_`` into the namespace of every +enumeration it creates, so ``:members:`` lists it on every enum page. An +``:exclude-members:`` entry cannot take it out again: autodoc lets the +``autodoc-skip-member`` event overrule that option, and napoleon's handler +answers ``False`` -- include -- for a private member that has a docstring. +``skip_enum_value_hook`` in :file:`docs/source/conf.py` answers first. + +The tests call the hook directly, and replay the event through Sphinx's own +:class:`~sphinx.events.EventManager` with napoleon's real handler, in the order +the build connects them. They need Sphinx and skip without it, but run no build. + +""" + +from __future__ import annotations + +import enum +import inspect +import types +import unittest +from typing import TYPE_CHECKING + +from tests.project.test_docs_ambiguous_xrefs_1775_unit import HAS_SPHINX, connections, load_conf + +if TYPE_CHECKING: + from typing import Any + +#: The member the hook skips. +HOOK = '_generate_next_value_' + +#: The event it answers. +EVENT = 'autodoc-skip-member' + + +class Colour(enum.Enum): + """A standard library enumeration, whose hook is :mod:`enum`'s.""" + + RED = 1 + + +@unittest.skipUnless(HAS_SPHINX, 'Sphinx and sphinx-autodoc-typehints are not installed') +class EnumValueHookTests(unittest.TestCase): + """``skip_enum_value_hook`` skips the inherited hook, and nothing else.""" + + conf = None # type: Any + + @classmethod + def setUpClass(cls) -> 'None': + cls.conf = load_conf() + + def setUp(self) -> 'None': + # NB: an ``aenum`` ``StrEnum`` with a ``_missing_`` of its own. Its hook is + # ``aenum``'s ``StrEnum._generate_next_value_``, which napoleon includes + # because that class owns it; an ``aenum`` ``IntEnum``'s hook is a + # module-level function, which napoleon leaves alone, so a registry of + # that kind would pass the build test below with or without the fix. + from pcapkit.const.ftp.command import Command # pylint: disable=import-outside-toplevel + self.registry = Command + + def skip(self, name: 'str', obj: 'Any') -> 'Any': + """The hook's own answer for ``name``, as autodoc would ask it.""" + return self.conf.skip_enum_value_hook(None, 'class', name, obj, False, {}) + + def emit(self, name: 'str', obj: 'Any') -> 'Any': + """What the build's ``autodoc-skip-member`` handlers decide for ``name``. + + Napoleon's ``setup`` connects its handler with no priority, and runs + before :file:`conf.py`'s, so it is connected first, at + :meth:`Sphinx.connect `'s default; + :file:`conf.py`'s handlers follow with the priorities its ``setup`` + gives them. ``skip`` is :obj:`True`, as autodoc passes it for a private + member that ``private-members`` does not ask for. + + """ + # pylint: disable=import-outside-toplevel + from sphinx.application import Sphinx + from sphinx.events import EventManager + from sphinx.ext import napoleon + + settings = {key: getattr(self.conf, key) for key in dir(self.conf) if key.startswith('napoleon_')} + app = types.SimpleNamespace(pdb=False, config=napoleon.Config(**settings)) + events = EventManager(app) # type: ignore[arg-type] + events.add(EVENT) + default = inspect.signature(Sphinx.connect).parameters['priority'].default + events.connect(EVENT, napoleon._skip_member, default) # pylint: disable=protected-access + for event, handler, priority in connections(self.conf): + if event == EVENT: + events.connect(event, handler, priority) + return events.emit_firstresult(EVENT, 'class', name, obj, True, {}) + + def test_stdlib_hook_is_skipped(self) -> 'None': + """A standard library enumeration's hook is :mod:`enum`'s, not pcapkit's.""" + self.assertIs(self.skip(HOOK, getattr(Colour, HOOK)), True) + + def test_aenum_hook_is_skipped(self) -> 'None': + """A ``pcapkit.const`` registry's hook is :mod:`aenum`'s.""" + obj = getattr(self.registry, HOOK) + self.assertEqual(obj.__module__.partition('.')[0], 'aenum') + self.assertIs(self.skip(HOOK, obj), True) + + def test_other_members_are_left_to_the_other_handlers(self) -> 'None': + """``_missing_`` implements a registry's lookup contract and stays documented.""" + for name in ('_missing_', 'get'): + with self.subTest(name=name): + self.assertIsNone(self.skip(name, getattr(self.registry, name))) + + def test_pcapkit_override_is_kept(self) -> 'None': + """A ``_generate_next_value_`` that pcapkit defines itself is documented.""" + def override(name: 'str', start: 'int', count: 'int', last_values: 'list[Any]') -> 'Any': + return count + override.__module__ = 'pcapkit.x' + self.assertIsNone(self.skip(HOOK, override)) + + def test_setup_connects_it_ahead_of_napoleon(self) -> 'None': + """At napoleon's priority the hook would run after it, and napoleon's ``False`` wins.""" + from sphinx.application import Sphinx # pylint: disable=import-outside-toplevel + default = inspect.signature(Sphinx.connect).parameters['priority'].default + wired = [(event, priority) for event, handler, priority in connections(self.conf) + if handler is self.conf.skip_enum_value_hook] + self.assertEqual([event for event, _ in wired], [EVENT]) + self.assertLess(wired[0][1], default) + + def test_build_skips_the_hook(self) -> 'None': + """Through the event, the hook is skipped on either kind of enumeration.""" + for owner in (Colour, self.registry): + with self.subTest(owner=owner.__name__): + self.assertIs(self.emit(HOOK, getattr(owner, HOOK)), True) + + def test_build_still_documents_missing(self) -> 'None': + """Through the event, a registry's own ``_missing_`` is still included.""" + self.assertIs(self.emit('_missing_', getattr(self.registry, '_missing_')), False) + + +if __name__ == '__main__': + unittest.main()