diff --git a/docs/source/pcapkit/protocols/application/ngap.rst b/docs/source/pcapkit/protocols/application/ngap.rst index a4a17cfad2..a71f4197fc 100644 --- a/docs/source/pcapkit/protocols/application/ngap.rst +++ b/docs/source/pcapkit/protocols/application/ngap.rst @@ -17,8 +17,11 @@ NGAP is the control plane between a 5G RAN node (gNB or ng-eNB) and an AMF. It runs over SCTP and is named by the DATA chunk's *payload protocol identifier* rather than by a port, so :class:`NGAP` is registered on :attr:`SCTP.__proto__ ` under -PPID 60 (``NG_Application_Protocol``) and PPID 66 -(``NGAP_over_DTLS_over_SCTP``); see +PPID 60 +(:attr:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier.PayloadProtocolIdentifier_3GPP_NG_Application_Protocol`) +and PPID 66 +(:attr:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier.PayloadProtocolIdentifier_3GPP_NGAP_over_DTLS_over_SCTP`); +see :func:`pcapkit.foundation.registry.protocols.register_sctp`. An SCTP DATA chunk that names NGAP carries exactly one ``NGAP-PDU``, encoded @@ -87,7 +90,7 @@ surfaced as first-class fields on Known Limitations ----------------- -* **PPID 66 payloads are not decoded.** ``NGAP_over_DTLS_over_SCTP`` wraps the +* **PPID 66 payloads are not decoded.** NGAP over DTLS over SCTP wraps the ``NGAP-PDU`` in a DTLS record, and :mod:`pcapkit` implements no DTLS, so the bytes reaching :meth:`NGAP.read` are not an APER encoding. The PPID is registered so that it is *named* rather than anonymous; the payload itself diff --git a/pcapkit/protocols/application/http.py b/pcapkit/protocols/application/http.py index 996beb7327..c82061f9fd 100644 --- a/pcapkit/protocols/application/http.py +++ b/pcapkit/protocols/application/http.py @@ -39,8 +39,9 @@ #: HTTP/2 connection -- with prior knowledge, over TLS, or after an upgrade -- #: by sending exactly these 24 octets, and the sequence is *designed* to be #: identifiable without parsing: it is a well-formed HTTP/1.1 request line whose -#: method ``PRI`` is reserved and permanently unregistered, so no valid HTTP/1 -#: message can begin with it and a prefix compare cannot false-positive on one. +#: method ``PRI`` is registered as one no actual client uses +#: (:rfc:`7540#section-11.6`), so no genuine HTTP/1 message begins with it and a +#: prefix compare cannot false-positive on one. #: That makes it a positive identification rather than a heuristic, which is why #: :meth:`HTTP._guess_version` tests it before attempting any parse. _HTTP2_PREFACE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' @@ -338,7 +339,7 @@ def make(self, Args: version: Version of HTTP. preface: Whether the HTTP/2 connection preface goes ahead of the - frame; :meth:`~pcapkit.protocols.protocol.Protocol.pack` writes it. + frame; :meth:`pack` writes it. **kwargs: Arbitrary keyword arguments. Returns: @@ -470,7 +471,7 @@ def _guess_version(self, length: 'int', **kwargs: 'Any') -> 'HTTP': Trying each version in turn and taking whichever parser does not object answers "did a parser accept this?" where the question is "what is this?": the HTTP/2 preface reads as a frame whose declared length is - 5,265,993 (``b'PRI'`` as a 24-bit integer), and text that is not HTTP at + 5,263,945 (``b'PRI'`` as a 24-bit integer), and text that is not HTTP at all can be accepted the same way. Args: diff --git a/pcapkit/protocols/application/ngap.py b/pcapkit/protocols/application/ngap.py index 48425427a7..356a0ecd3a 100644 --- a/pcapkit/protocols/application/ngap.py +++ b/pcapkit/protocols/application/ngap.py @@ -15,8 +15,11 @@ It runs over SCTP and is named by the DATA chunk's *payload protocol identifier* rather than by a port, so :class:`NGAP` is registered on :attr:`SCTP.__proto__ ` under -PPID 60 (``NG_Application_Protocol``) and PPID 66 -(``NGAP_over_DTLS_over_SCTP``); see +PPID 60 +(:attr:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier.PayloadProtocolIdentifier_3GPP_NG_Application_Protocol`) +and PPID 66 +(:attr:`~pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier.PayloadProtocolIdentifier_3GPP_NGAP_over_DTLS_over_SCTP`); +see :func:`pcapkit.foundation.registry.protocols.register_sctp`. An SCTP DATA chunk that names NGAP carries exactly one ``NGAP-PDU``, encoded @@ -72,7 +75,7 @@ Known Limitations ----------------- -* **PPID 66 payloads are not decoded.** ``NGAP_over_DTLS_over_SCTP`` wraps the +* **PPID 66 payloads are not decoded.** NGAP over DTLS over SCTP wraps the ``NGAP-PDU`` in a DTLS record, and :mod:`pcapkit` implements no DTLS, so the bytes reaching :meth:`NGAP.read` are not an APER encoding. The PPID is registered so that it is *named* rather than anonymous; the payload itself diff --git a/pcapkit/protocols/application/ospf.py b/pcapkit/protocols/application/ospf.py index ecbeb18202..f1cda117b7 100644 --- a/pcapkit/protocols/application/ospf.py +++ b/pcapkit/protocols/application/ospf.py @@ -270,9 +270,10 @@ def make(self, Constructed packet data. Raises: - ProtocolError: If ``auth_data`` is of an invalid type, or is - :obj:`bytes` that are not exactly 8 octets long; or if - ``packet_length`` is not an :obj:`int` in ``0..65535``. + ProtocolError: If ``auth_data`` is of an invalid type, is + :obj:`bytes` that are not exactly 8 octets long, or is a + :obj:`dict` missing a required key; or if ``packet_length`` + is not an :obj:`int` in ``0..65535``. .. versionchanged:: 1.5.0 @@ -455,8 +456,9 @@ def _make_encrypt_auth(self, Authentication schema. Raises: - ProtocolError: If ``auth_data`` is of an invalid type, or is - :obj:`bytes` that are not exactly 8 octets long. + ProtocolError: If ``auth_data`` is of an invalid type, is + :obj:`bytes` that are not exactly 8 octets long, or is a + :obj:`dict` missing a required key. """ if isinstance(auth_data, Schema_CryptographicAuthentication): diff --git a/pcapkit/protocols/data/misc/pcapng.py b/pcapkit/protocols/data/misc/pcapng.py index 0437d156bb..983798f847 100644 --- a/pcapkit/protocols/data/misc/pcapng.py +++ b/pcapkit/protocols/data/misc/pcapng.py @@ -802,7 +802,8 @@ def __init__(self, type: 'Enum_BlockType', length: 'int', interface_id: 'int', t class SystemdJournalExportBlock(PCAPNG): """Data model for PCAP-NG :manpage:`systemd(1)` Journal Export Block.""" - #: Journal entry. + #: Journal entries; empty for a malformed block, whose entry is then kept + #: as captured under ``entry_raw`` (:issue:`1406`). data: 'tuple[OrderedMultiInfo[str, str | bytes], ...]' if TYPE_CHECKING: diff --git a/pcapkit/protocols/misc/null.py b/pcapkit/protocols/misc/null.py index 5de98c1f0a..440b1c24c3 100644 --- a/pcapkit/protocols/misc/null.py +++ b/pcapkit/protocols/misc/null.py @@ -121,7 +121,8 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, # pylint: d #: pcapkit.protocols.misc.null.NoPayload: Payload of current instance. self._next = self - #: pcapkit.corekit.protochain.ProtoChain: Protocol chain of current instance. + #: None: Protocol chain of current instance, which an empty payload + #: does not have. self._protos = None # type: ignore[assignment] @classmethod diff --git a/pcapkit/protocols/misc/pcap/header.py b/pcapkit/protocols/misc/pcap/header.py index 255ff92c40..a92a90d920 100644 --- a/pcapkit/protocols/misc/pcap/header.py +++ b/pcapkit/protocols/misc/pcap/header.py @@ -141,7 +141,11 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_Header' Parsed packet data. Raises: - FileError: If the magic number is invalid. + FileError: If the magic number is invalid. Parsing does not get + this far with one: the schema's + :func:`~pcapkit.protocols.schema.misc.pcap.header.magic_number_callback` + raises :exc:`~pcapkit.utilities.exceptions.ProtocolError` for it + while the header is unpacked. """ if TYPE_CHECKING: diff --git a/pcapkit/protocols/misc/pcapng.py b/pcapkit/protocols/misc/pcapng.py index 052fec224b..6f7e5f7b6b 100644 --- a/pcapkit/protocols/misc/pcapng.py +++ b/pcapkit/protocols/misc/pcapng.py @@ -419,9 +419,9 @@ def _missing_(cls, value: 'object') -> 'PacketReception': # ``Enum_TLSKeyLabel`` like its :mod:`pcapkit.const.pcapng` siblings. This # module does not re-export it under the unaliased name (#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 -# registry, so filing it under :mod:`pcapkit.const` would misrepresent it as -# one. The ``Enum_*`` alias convention covers imports of const enums, so it does +# (draft-ietf-opsawg-pcapng-06 section 4.7, Decryption Secrets Block, says +# the label "is one of" four names), not a registry, so filing it under +# :mod:`pcapkit.const` would misrepresent it as one. The ``Enum_*`` alias convention covers imports of const enums, so it does # not apply to a local definition. @@ -982,7 +982,7 @@ def byteorder(self) -> 'Literal["big", "little"]': @property def nanosecond(self) -> 'bool': - """Whether the timestamp is in nanoseconds.""" + """Whether the timestamp resolution is finer than microseconds.""" return self.ts_resolution > RESOLUTION[False] @property @@ -1397,11 +1397,9 @@ def read(self, length: 'Optional[int]' = None, *, _read: 'bool' = True, # :class:`~pcapkit.corekit.fields.misc.SchemaField`, which fails with # a bare ``ValueError: read length must be non-negative or -1``. That # would cost the whole extraction rather than the one truncated - # block, the opposite of the `#431 - # `__ accommodation - # for damaged input. ``_read_fileng`` stops at the end of the file, so - # the octets it returned are the authority on where the block really - # finishes. + # block, the opposite of the #431 accommodation for damaged input. + # ``_read_fileng`` stops at the end of the file, so the octets it + # returned are the authority on where the block really finishes. read = len(self._data) if read < schema.block.length: warn(f'PCAP-NG: [Block {schema.type}] block length ' @@ -1835,10 +1833,11 @@ def _get_timezone(self, interface_id: 'int' = 0) -> 'timezone': """ # NOTE: UTC, not the host's timezone, is the default in both branches # below. A capture carries no timezone of its own unless it says so, and - # draft-ietf-opsawg-pcapng-02 §4.3 defines the block timestamp as an - # offset from 1970-01-01 00:00:00 UTC with no timezone term at all, so - # substituting the *reading* machine's zone would make the same file - # parse to different instants on different hosts. ``_get_resolution`` + # draft-ietf-opsawg-pcapng-02 section 4.3, Enhanced Packet Block, + # defines the block timestamp as an offset from 1970-01-01 00:00:00 UTC + # with no timezone term at all, so substituting the *reading* machine's + # zone would make the same file parse to different instants on different + # hosts. ``_get_resolution`` # and ``_get_offset`` fall back to the format's own defaults in this # situation; this is the matching one. if self._ctx is None: @@ -1897,8 +1896,9 @@ def _read_timestamp(self, timestamp_high: 'int', timestamp_low: 'int', *, # ``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 - # same draft §4.2 says of ``if_tzone`` that it "SHOULD NOT be used" + # section 4.3, Enhanced Packet Block, spells out the arithmetic and + # has no timezone term), and the same draft's section 4.2, Interface + # Description Block, says of ``if_tzone`` that it "SHOULD NOT be used" # and of ``if_tsoffset`` that it is "not intended to be used as an # offset between local time and UTC". A shifted epoch would also # contradict ``ts_datetime`` below, which is built from the @@ -4135,7 +4135,7 @@ def _read_nrb_records(self, records_schema: 'list[Schema_NameResolutionRecord]') # NOTE: The padding to 32 bits, if it is not all zeros (#1437). self._read_padding(schema, data) - # record option data + # record name resolution record data records.add(type, data) # break when ``nrb_record_end`` is reached @@ -4232,6 +4232,9 @@ def _read_secrets_unknown(self, schema: 'Schema_UnknownSecrets', *, schema: Parsed secret schema. block: Parsed PCAP-NG decryption secrets block. + Returns: + Parsed decryption secrets data. + """ secrets = Data_UnknownSecrets( data=schema.data, @@ -4246,6 +4249,9 @@ def _read_secrets_tls(self, schema: 'Schema_TLSKeyLog', *, schema: Parsed secret schema. block: Parsed PCAP-NG decryption secrets block. + Returns: + Parsed decryption secrets data. + """ secrets = Data_TLSKeyLog( # NOTE: each label's entries as an OrderedMultiInfo, which ``Info`` @@ -4878,8 +4884,9 @@ def _make_block_cb(self, block: 'Optional[Data_CustomBlock]' = None, *, data = block.data options = cast('Option', getattr(block, 'options', None)) - # NOTE: Unlike every other block, a custom block has no ``OptionField`` - # to name an option registry: :class:`~pcapkit.protocols.schema.misc.pcapng.CustomBlock` + # NOTE: Unlike every other block that carries options, a custom block + # has no ``OptionField`` to name an option registry: + # :class:`~pcapkit.protocols.schema.misc.pcapng.CustomBlock` # spans the custom data, its padding and the options as one opaque blob, # since only the owner of the private enterprise number knows where the # custom data ends. Its options are therefore resolved in the generic @@ -5443,7 +5450,8 @@ def _make_option_if_tzone(self, type: 'Enum_OptionType', option: 'Optional[Data_ Args: type: Option type. option: Option data model. - tzone: Timezone offset, in seconds. + tzone: Timezone offset from UTC, as a :class:`~datetime.timezone`, + a :class:`~datetime.timedelta` or an :obj:`int` of seconds. **kwargs: Arbitrary keyword arguments. Returns: diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index 894efb1786..c144fbdc22 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -263,8 +263,9 @@ def _declared_keywords(cls: 'type') -> 'Optional[frozenset[str]]': ` found there, plus :data:`OUT_OF_BAND_KEYWORDS`. :obj:`None` if ``cls`` *itself* sets ``__keywords__`` to :obj:`None`, meaning its keywords cannot be enumerated - and are not to be checked -- inherited :obj:`None` does not count, for the - reason given at the read below. + and are not to be checked -- inherited :obj:`None` does not count, as + :attr:`ProtocolBase.__keywords__ + ` explains. The union is deliberately wider than the signature of ``cls.make``, because a keyword reaching ``make`` is not necessarily *for* ``make``: @@ -766,9 +767,9 @@ def pack(self, **kwargs: 'Any') -> 'bytes': Constructed packet data. Notes: - We used a special keyword argument ``__packet__`` to pass the - global packet data to underlying methods. This is useful when - the packet data is not available in the current instance. + The special keyword argument ``__packet__`` passes the global + packet data to underlying methods. This is useful when the packet + data is not available in the current instance. The octets are built from ``kwargs`` alone, through :meth:`make`; the instance's own parsed or constructed state is not read. A bare @@ -835,9 +836,9 @@ def unpack(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> '_PT': Parsed packet data. Notes: - We used a special keyword argument ``__packet__`` to pass the - global packet data to underlying methods. This is useful when - the packet data is not available in the current instance. + The special keyword argument ``__packet__`` passes the global + packet data to underlying methods. This is useful when the packet + data is not available in the current instance. When :attr:`self.__header__ ` is already set, by :meth:`from_schema` or by the :meth:`pack` call that @@ -942,7 +943,7 @@ def expand_comp(value: 'str | ProtocolBase | Type[ProtocolBase]') -> 'tuple': 3. If ``value`` is :obj:`str`, the method will attempt to search for the existing registered protocol class from :data:`pcapkit.protocols.__proto__` and follow **step 2**; otherwise, - return the value itself. + return the value in upper case alone. Args: value: Protocol class or name. @@ -1421,9 +1422,10 @@ def __init_subclass__(cls, /, schema: 'Optional[Type[_ST]]' = None, Raises: UnsupportedCall: If any unrecognised class keyword is given. - This method is called when a subclass of :class:`Protocol` is defined. + This method is called when a subclass of :class:`ProtocolBase` is defined. It is used to set the :attr:`self.__schema__ ` - attribute of the subclass, and, if ``code`` is given, to register the + and :attr:`self.__data__ ` + attributes of the subclass, and, if ``code`` is given, to register the subclass into the next-layer dispatch :term:`registry` (or registries) that ``code`` names -- e.g. :attr:`Link.__proto__ `. @@ -1764,7 +1766,9 @@ def _read_unpack(self, size: 'int' = 1, *, signed: 'bool' = False, Unpacked data upon success Raises: - StructError: If unpack (:func:`struct.unpack`) failed, and :exc:`struct.error` raised. + StructError: If the buffer is empty, whatever ``quiet`` says, or if + unpack (:func:`struct.unpack`) failed, and :exc:`struct.error` + raised, without ``quiet``. """ endian = '<' if lilendian else '>' @@ -2251,9 +2255,9 @@ def _decode_next_layer(self, dict_: '_PT', proto: 'int', length: 'Optional[int]' Current protocol with next layer extracted. 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. :meth:`Info.to_dict + The method adds a ``__next_type__`` key to ``dict_`` to store the + next layer protocol type, and a ``__next_name__`` key to store the + next layer protocol name. :meth:`Info.to_dict ` carries both, so that :meth:`_make_payload` dispatches a :obj:`dict` rebuild on them, while the dumpers and ``repr`` leave them out (:issue:`2003`). @@ -2450,7 +2454,8 @@ def __init_subclass__(cls, /, schema: 'Optional[Type[_ST]]' = None, This method is called when a subclass of :class:`Protocol` is defined. It is used to set the :attr:`self.__schema__ ` - attribute of the subclass. + and :attr:`self.__data__ ` + attributes of the subclass. Notes: When ``schema`` and/or ``data`` is not specified, the method will first diff --git a/pcapkit/protocols/schema/application/httpv2.py b/pcapkit/protocols/schema/application/httpv2.py index 8a06e8fff8..cabf5307b4 100644 --- a/pcapkit/protocols/schema/application/httpv2.py +++ b/pcapkit/protocols/schema/application/httpv2.py @@ -173,8 +173,8 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Schema': # # ``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 + # ``Flags(0)`` outright with a ``TypeError`` that it has no members + # (3.10 and earlier hand back a pseudo-member instead), so # the frame schemas that inherit it unchanged -- ``UnassignedFrame``, # ``PriorityFrame``, ``RSTStreamFrame``, ``GoawayFrame`` and # ``WindowUpdateFrame`` -- keep the plain ``int``, which is what their diff --git a/pcapkit/protocols/schema/misc/pcapng.py b/pcapkit/protocols/schema/misc/pcapng.py index 00ef5d6b4c..278132e034 100644 --- a/pcapkit/protocols/schema/misc/pcapng.py +++ b/pcapkit/protocols/schema/misc/pcapng.py @@ -269,12 +269,14 @@ def nonnegative(length: 'Callable[[dict[str, Any]], int]') -> 'Callable[[dict[st ` builds its :mod:`struct` template as ``f'{length}s'`` unconditionally, so a negative length becomes the format ``'-8s'`` and :func:`struct.calcsize` raises a bare - :exc:`struct.error`; a negative :class:`~pcapkit.corekit.fields.misc.SchemaField` - length reaches :meth:`io.RawIOBase.read` and raises a bare :exc:`ValueError`. - Neither is one of :mod:`pcapkit.utilities.exceptions`, so a caller cannot tell - either from a bug in its own code, and neither is an :exc:`EOFError`, so - neither is caught by the frame loop -- one malformed block would therefore - cost the whole extraction. + :exc:`struct.error`. That is not one of :mod:`pcapkit.utilities.exceptions`, + so a caller cannot tell it from a bug in its own code, and it is not an + :exc:`EOFError`, so the frame loop does not catch it -- one malformed block + would therefore cost the whole extraction. A negative + :class:`~pcapkit.corekit.fields.misc.SchemaField` length fails silently + instead: :meth:`SchemaField.unpack + ` reads it as no declared + length, and sizes the nested schema by whatever is left of the stream. Two shapes reach here. A Block Total Length below the block's own fixed-field floor -- 28 octets for a Section Header Block, 20 for an Interface @@ -708,9 +710,10 @@ def pcapng_block_selector(packet: 'dict[str, Any]') -> 'Field': or not those four octets were there to read -- :meth:`FieldBase.unpack ` zero-pads a short read rather than refusing it. A tail of one, two or - three octets therefore arrives here negative, and - :meth:`io.RawIOBase.read` would raise a bare ``ValueError``. The floor is a - backstop: :meth:`PCAPNG._check_block_floor + three octets therefore arrives here negative, which + :meth:`SchemaField.unpack ` + would read as no declared length, sizing the block by whatever is left + of the stream. The floor is a backstop: :meth:`PCAPNG._check_block_floor ` reports that tail as end-of-stream before it gets here, and on the packing path :meth:`Schema.pack ` seeds @@ -900,7 +903,7 @@ class OptionEnumField(EnumField): Important: This class is specifically designed for :class:`~pcapkit.const.pcapng.option_type.OptionType` - as it is actually a :class:`~enum.StrEnum` class. + as it is actually an ``aenum.StrEnum`` class. """ if TYPE_CHECKING: @@ -1023,8 +1026,8 @@ class BlockType(EnumSchema[Enum_BlockType]): def post_process(self, packet: 'dict[str, Any]') -> 'Schema': """Revise ``schema`` data after unpacking process. - This method validates the two block lengths and raises - :exc:`~pcapkit.utilities.exceptions.ProtocolError` if they are not + This method compares the two block lengths and issues a + :class:`~pcapkit.utilities.warnings.ProtocolWarning` if they are not equal. Args: @@ -1484,7 +1487,8 @@ def __init__(self, type: 'Enum_OptionType', length: 'int', speed: 'int') -> 'Non class IF_TSResolOption(_IF_Option, code=Enum_OptionType.if_tsresol): """Header schema for PCAP-NG file ``if_tsresol`` options.""" - #: Interface timestamp resolution, in units per second. + #: Interface timestamp resolution, as the option encodes it: the base flag + #: and the exponent, from which :attr:`resolution` is computed. tsresol: 'ResolutionData' = BitField(length=1, namespace={ 'flag': (0, 1), 'resolution': (1, 7), diff --git a/pcapkit/protocols/schema/schema.py b/pcapkit/protocols/schema/schema.py index ba76d7a9d2..dd15fcefd1 100644 --- a/pcapkit/protocols/schema/schema.py +++ b/pcapkit/protocols/schema/schema.py @@ -284,7 +284,7 @@ def __prepare__(cls, name: 'str', bases: 'tuple[type, ...]', /, **kwds: 'Any') - #: 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 - #: values for argument '...'``. ``namespace`` collided this way, which is why + #: values for argument '...'``. ``namespace`` collides this way, which is why #: :mod:`pcapkit.protocols.schema.misc.pcapng`'s :class:`~pcapkit.protocols.schema.misc.pcapng.Option` subclasses #: spell it ``ns=`` instead. #: @@ -445,8 +445,8 @@ def __new__(cls, *args: '_VT', **kwargs: '_VT') -> 'Self': # pylint: disable=un """Create a new instance. The class will try to automatically generate ``__init__`` method with - the same signature as specified in class variables' type annotations, - which is inspired by :pep:`557` (:mod:`dataclasses`). + one keyword parameter per field in :attr:`__fields__`, which is + inspired by :pep:`557` (:mod:`dataclasses`). Note: Whether ``cls`` still needs finalising is read from its *own* @@ -702,12 +702,12 @@ def from_dict(cls, dict_: 'Optional[Mapping[str, _VT] | Iterable[tuple[str, _VT] **kwargs: '_VT') -> 'Self': r"""Create a new instance. - * If ``dict_`` is present and has a ``.keys()`` method, then does: + * If ``dict_`` is a mapping, then does: ``for k in dict_: self[k] = dict_[k]``. - * If ``dict_`` is present and has no ``.keys()`` method, then does: + * If ``dict_`` is present and not a mapping, then does: ``for k, v in dict_: self[k] = v``. - * If ``dict_`` is not present, then does: - ``for k, v in kwargs.items(): self[k] = v``. + * In either case, and if ``dict_`` is not present, this is followed + by: ``for k, v in kwargs.items(): self[k] = v``. Args: dict\_: Source data. @@ -725,7 +725,7 @@ def to_dict(self) -> 'dict[str, _VT]': Important: We only convert nested :class:`Schema` objects into :obj:`dict` if they are the direct value of the :class:`Schema` object's attribute. - Should such :class:`Schema` objects be nested within other data, + Should such :class:`Schema` objects be nested within other data types, such as :obj:`list`, :obj:`tuple`, :obj:`set`, etc., we leave them intact rather than converting them into :obj:`dict`. @@ -1018,9 +1018,9 @@ def unpack(cls, data: 'bytes | IO[bytes]', Unpacked data as :class:`Schema`. Notes: - We used a ``__length__`` key in ``packet`` to record the length - of the remaining data, which is used to determine the length of - the payload field. + A ``__length__`` key in ``packet`` records the length of the + remaining data, which is used to determine the length of the + payload field. When this schema is nested -- unpacked through a :class:`~pcapkit.corekit.fields.misc.SchemaField` rather than @@ -1483,8 +1483,9 @@ def keep_short_read(protocol: 'ProtocolBase') -> 'None': Args: protocol: Protocol instance, just parsed. - Called by the layer base classes once a parse is done. When the data ended - inside one of the header's fields, ``info`` gains the ``__short_read__`` + Called by the layer base classes, and by the PCAP global + :class:`~pcapkit.protocols.misc.pcap.header.Header`, once a parse is done. + When the data ended inside one of the header's fields, ``info`` gains the ``__short_read__`` record :meth:`Schema.unpack` made, so that :func:`replay_short_read` can cut the rebuild back to what was captured (:issue:`1458`). The key is listed in :attr:`Data.__included__ `,