Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/source/pcapkit/protocols/application/ngap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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__ <pcapkit.protocols.transport.sctp.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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions pcapkit/protocols/application/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
9 changes: 6 additions & 3 deletions pcapkit/protocols/application/ngap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ <pcapkit.protocols.transport.sctp.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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions pcapkit/protocols/application/ospf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down
3 changes: 2 additions & 1 deletion pcapkit/protocols/data/misc/pcapng.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion pcapkit/protocols/misc/null.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion pcapkit/protocols/misc/pcap/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
46 changes: 27 additions & 19 deletions pcapkit/protocols/misc/pcapng.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
# <https://github.com/JarryShaw/PyPCAPKit/issues/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 '
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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``
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
37 changes: 21 additions & 16 deletions pcapkit/protocols/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,9 @@ def _declared_keywords(cls: 'type') -> 'Optional[frozenset[str]]':
<pcapkit.protocols.protocol.ProtocolBase.__keywords__>` 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__
<pcapkit.protocols.protocol.ProtocolBase.__keywords__>` explains.

The union is deliberately wider than the signature of ``cls.make``, because
a keyword reaching ``make`` is not necessarily *for* ``make``:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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__ <Protocol.__header__>` is already set,
by :meth:`from_schema` or by the :meth:`pack` call that
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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__ <pcapkit.protocols.protocol.Protocol.__schema__>`
attribute of the subclass, and, if ``code`` is given, to register the
and :attr:`self.__data__ <pcapkit.protocols.protocol.Protocol.__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__
<pcapkit.protocols.link.link.Link.__proto__>`.
Expand Down Expand Up @@ -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 '>'
Expand Down Expand Up @@ -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
<pcapkit.corekit.infoclass.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`).
Expand Down Expand Up @@ -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__ <pcapkit.protocols.protocol.Protocol.__schema__>`
attribute of the subclass.
and :attr:`self.__data__ <pcapkit.protocols.protocol.Protocol.__data__>`
attributes of the subclass.

Notes:
When ``schema`` and/or ``data`` is not specified, the method will first
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/protocols/schema/application/httpv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: <flag 'Flags'> 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
Expand Down
Loading
Loading