Root Protocol

pcapkit.protocols.protocol contains Protocol, an abstract base class with pre-defined utility arguments and methods. Inherit it directly only to create a new protocol stack; a new protocol in an existing layer subclasses that layer’s base class – Link, Internet, Transport or Application – instead, c.f. Customisation & Extensions. The built-in protocol families, those layer classes included, derive from ProtocolBase, whose metaclass is ProtocolMeta; both are listed under Internal Definitions below.

Changed in version 1.5.0: Constructing a protocol with a keyword that none of its signatures declares raises UnsupportedCall, where it was discarded (#617). A next layer captured shorter than its header is kept as Raw, where it was parsed with zero-filled fields (#1170).

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

Bases: ProtocolBase, Generic[_PT, _ST]

User-facing abstract base class for third-party protocols.

Inherit it directly only to create a new protocol stack; a new protocol in an existing layer subclasses that layer’s base class instead, c.f. Customisation & Extensions. The built-in protocol families do not derive from this class but from ProtocolBase, whose metaclass is ProtocolMeta.

Changed in version 1.3.0: The built-in protocol families derive from ProtocolBase, so a built-in protocol is not an instance of this class, where every one was; and a subclass is registered in pcapkit.protocols.__proto__ when it is defined.

abstract property name: str

Name of current protocol.

property alias: str

Acronym of current protocol.

property info_name: str

Key name of the info dict.

property info: _PT

Info dict of current instance.

property data: bytes

Binary packet data of current instance.

abstract property length: int

Header length of current protocol.

property payload: ProtocolBase

Payload of current instance.

property protocol: str | None

Name of next layer protocol (if any).

property protochain: ProtoChain

Protocol chain of current instance.

property packet: Packet

Data_Packet data of the protocol.

Note

The split relies on self.length being the length of the octets preceding the payload, and on the payload running from there to the end of the buffer. Both hold for a protocol laid out as a header followed by its payload, which is nearly all of them.

A protocol laid out otherwise has to override this: one whose length counts something else, or one carrying a trailer after the payload, gets a header that eats the payload and a payload of b''. PCAPNG is the example – its length is the wire’s Block Total Length and the captured octets sit ahead of the option list and the trailing length field. The payload matters because ProtocolBase.__init__() injects it into every parsed _info, which the dumpers write out.

property schema: _ST

Schema data of the protocol.

classmethod id()

Index ID of the protocol.

Return type:

tuple[str, ...]

Returns:

By default, it returns the name of the protocol. In certain cases, the method may return multiple values.

classmethod register(code, protocol)

Register a new protocol class.

Notes

The full qualified class name of the new protocol class should be as {protocol.module}.{protocol.name}.

Parameters:
Raises:

pcapkit.utilities.exceptions.RegistryError – If protocol is not a class, or not a ProtocolBase subclass.

Warns:

pcapkit.utilities.warnings.RegistryWarning – If code is already registered, naming the displaced entry and its replacement so a caller can tell what was lost rather than only that something was. Fires only when the incumbent differs from the replacement; an unresolved ModuleDescriptor incumbent counts as different from the class it names.

Note

The guard matches register_protocol’s: it fires only when the incumbent differs from the replacement, so handing back the entry already stored under code – the same class object, or a ModuleDescriptor equal to the stored one – is a silent no-op rather than a warning about nothing displaced, and the stored entry is kept as it is (#1364). The comparison runs before protocol is resolved and resolves nothing, so a stored descriptor stays deferred, and an incumbent left as an unresolved descriptor still reports as different from the class it names.

A descriptor equal to the one code shipped with restores that shipped object, unresolved, so undoing an override leaves the entry as shipped (#1504); see _next_layer_entry(), which every layer’s register shares.

classmethod analyze(proto, payload, **kwargs)

Analyse packet payload.

Parameters:
  • proto (int) – Protocol registry number.

  • payload (bytes) – Packet payload.

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

Return type:

ProtocolBase

Returns:

Parsed payload as a Protocol instance.

classmethod from_schema(schema)

Create protocol instance from schema.

Parameters:

schema (TypeVar(_ST, bound= Schema) | dict[str, Any]) – Protocol schema.

Return type:

Self

Returns:

Protocol instance.

classmethod from_data(data, **kwargs)

Create protocol instance from data.

Parameters:
  • data (TypeVar(_PT, bound= Data) | dict[str, Any]) – Protocol data.

  • **kwargs (Any) – Construction keywords that data does not carry, such as the num and header of Frame, or the num, sct and ctx of PCAPNG. They take precedence over the keywords _make_data() returns.

Return type:

Self

Returns:

Protocol instance.

Raises:

UnsupportedCall – If any of kwargs matches no parameter of the protocol, as for a direct construction.

abstractmethod read(length=None, **kwargs)

Read (parse) packet data.

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

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

Return type:

TypeVar(_PT, bound= Data)

Returns:

Parsed packet data.

abstractmethod make(**kwargs)

Make (construct) packet data.

Parameters:

**kwargs (Any) – Arbitrary keyword arguments.

Return type:

TypeVar(_ST, bound= Schema)

Returns:

Curated protocol schema data.

Note

The **kwargs here absorbs the keywords that ProtocolBase.__post_init__ hands to the parse as well as to the construction, so an implementation is not expected to declare every keyword it is called with. It is not a place for a caller to put a keyword no signature declares: building a protocol through its constructor with such a keyword raises UnsupportedCall from ProtocolBase.__init__ (#617).

Warning

Calling this method directly is not checked, and still discards an undeclared keyword in silence. The check lives in ProtocolBase.__init__, so it covers SomeProtocol(...) and the pack() it leads to, but not SomeProtocol.make(...) on an instance obtained some other way – object.__new__(cls).make(**kwargs) is the idiom, used by this package’s own tests and by HTTP.make to reach its versioned implementation. Covering it would mean interposing on every make in the tree rather than on the one place their keywords converge, so it is deliberately not done. Construct through the constructor to get the check.

unpack(length=None, **kwargs)

Unpack (parse) packet data.

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

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

Return type:

TypeVar(_PT, bound= Data)

Returns:

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.

When self.__header__ is already set, by from_schema() or by the pack() call that __post_init__() makes to construct the instance, that schema is kept, unless it still holds raw bytes where parsing its packed octets yields a schema – e.g. a raw option given to make() – which read() cannot consume. The header is then replaced by the parsed one (#1312).

Raises:

ProtocolError – If the made header holds raw items and its packed octets do not parse, either as a schema or by read(). The message carries the original error’s, which is also chained as __cause__.

pack(**kwargs)

Pack (construct) packet data.

Parameters:

**kwargs (Any) – Arbitrary keyword arguments.

Return type:

bytes

Returns:

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 octets are built from kwargs alone, through make(); the instance’s own parsed or constructed state is not read. A bare SomeProtocol(...).pack() therefore returns the octets of a default header, not the instance’s: data holds those.

Nor is that state written. Once the instance has been parsed or constructed, the header is made on a shallow copy of it (see _packing_target()), so schema, info and anything make() sets along the way, such as TCP.connection, still describe that packet. Only the call that __post_init__() makes to construct the instance keeps the header made here, as the one unpack() reads back.

Changed in version 1.5.0: Calling it no longer replaces the instance’s __header__, which left schema disagreeing with info (#1769).

static decode(byte, *, encoding=None, errors='strict')

Decode bytes into str.

Should decoding failed using encoding, the method will try again decoding the bytes as 'unicode_escape' with 'replace' for error handling.

See also

The method is a wrapping function for bytes.decode().

Parameters:
  • byte (bytes) – Source bytestring.

  • encoding (str | None) – The encoding with which to decode the bytes. If not provided, pcapkit will first try detecting its encoding using chardet. The fallback encoding is UTF-8.

  • errors (Literal['strict', 'ignore', 'replace']) – The error handling scheme to use for the handling of decoding errors. The default is 'strict' meaning that decoding errors raise a UnicodeDecodeError. Other possible values are 'ignore' and 'replace' as well as any other name registered with codecs.register_error() that can handle UnicodeDecodeError.

Return type:

str

static unquote(url, *, encoding='utf-8', errors='replace')

Unquote URLs into readable format.

Should decoding failed , the method will try again replacing '%' with '\x' then decoding the url as 'unicode_escape' with 'replace' for error handling.

See also

This method is a wrapper function for urllib.parse.unquote().

Parameters:
  • url (str) – URL string.

  • encoding (str) – The encoding with which to decode the bytes.

  • errors (Literal['strict', 'ignore', 'replace']) – The error handling scheme to use for the handling of decoding errors. The default is 'replace', which substitutes a replacement marker for each undecodable sequence. Other possible values are 'strict', under which decoding errors raise a UnicodeDecodeError, and 'ignore', as well as any other name registered with codecs.register_error() that can handle UnicodeDecodeError.

Return type:

str

static expand_comp(value)

Expand protocol class to protocol name.

The method is used to expand protocol class to protocol name, in the following manner:

  1. If value is a protocol instance, the method will return the protocol class, and the protocol names in upper case obtained from Protocol.id.

  2. If value is a protocol class, the method will return the protocol class itself, and the protocols names in upper case obtained from Protocol.id.

  3. If value is str, the method will attempt to search for the existing registered protocol class from pcapkit.protocols.__proto__ and follow step 2; otherwise, return the value itself.

Parameters:

value (str | ProtocolBase | Type[ProtocolBase]) – Protocol class or name.

Return type:

tuple

__layer__: Literal['Link', 'Internet', 'Transport', 'Application'] | None

Layer of protocol, can be one of Link, Internet, Transport and Application. For example, the layer of Ethernet is Link. However, certain protocols are not in any layer, such as Raw, and thus its layer is None.

__proto__: DefaultDict[int, ModuleDescriptor[ProtocolBase] | Type[ProtocolBase]]

Protocol index mapping for decoding next layer, c.f. self._decode_next_layer & self._import_next_layer. Each value is a ModuleDescriptor naming the module and class, or a Protocol subclass.

__keywords__: frozenset[str] | None

Construction keywords this protocol consumes out of **kwargs instead of declaring as a parameter, e.g. with kwargs.get('spam') in read() – as ESP.read does with packet. _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 __init__() accepts it. The union over the MRO is used, so a subclass need not repeat its parents’ entries.

Declaring the parameter is preferable where possible, since that also documents the keyword to the caller and to inspect. This is for the cases where it is not – a keyword handled uniformly for a whole family of names, say. It is opt-in per class, so it can only exempt a name whose author wrote it down (#617).

None means the keywords cannot be enumerated and the check is skipped for this protocol. That is for a dispatcher, whose real signature belongs to a class chosen at call time: HTTP.make declares only version and forwards everything else to HTTPv1.make or HTTPv2.make depending on that value, so no set of names is right for it. Use it only for that shape, since a protocol that skips the check silently discards an unknown keyword. Unlike a set, the 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.

__schema__: Type[_ST]

Protocol header schema definition.

__header__: _ST

Protocol header schema instance.

_read_packet(length=None, *, header=None, payload=None, discard=False)

Read raw packet data.

Overloads:
  • self, length (Optional[int]), header (None) → bytes

  • self, header (int), payload (Optional[int]), discard (Literal[True]) → bytes

  • self, header (int), payload (Optional[int]), discard (Literal[False]) → Data_Packet

Parameters:
  • length (int | None) – length of the packet

  • header (int | None) – length of the packet header

  • payload (int | None) – length of the packet payload

  • discard (bool) – flag if discard header data

  • If header omits, returns the whole packet data in bytes.

  • If discard is set as True, returns the packet body (in bytes) only.

  • Otherwise, returns the header and payload data as Packet object.

_get_payload()

Get payload from self.__header__.

Return type:

bytes

Returns:

Payload of self.__header__ as bytes.

See also

This is a wrapper function for pcapkit.protocols.schema.schema.Schema.get_payload().

classmethod _make_data(data)

Create key-value pairs from data for protocol construction.

Parameters:

data (Data) – protocol data

Return type:

dict[str, Any]

Returns:

Key-value pairs for protocol construction.

classmethod _make_index(name, default=None, *, namespace=None, reversed=False, pack=False, size=4, signed=False, lilendian=False)

Return first index of name from a dict or enumeration.

Overloads:
  • cls, name (int | StdlibEnum | AenumEnum), pack (Literal[False]) → int

  • cls, name (int | StdlibEnum | AenumEnum), pack (Literal[True]), size (int), signed (bool), lilendian (bool) → bytes

  • cls, name (str), default (Optional[int]), namespace (Type[StdlibEnum] | Type[AenumEnum]), pack (Literal[False]) → int

  • cls, name (str), default (Optional[int]), namespace (Type[StdlibEnum] | Type[AenumEnum]), pack (Literal[True]), size (int), signed (bool), lilendian (bool) → bytes

  • cls, name (str), default (Optional[int]), namespace (dict[int, str]), reversed (Literal[False]), pack (Literal[False]) → int

  • cls, name (str), default (Optional[int]), namespace (dict[int, str]), reversed (Literal[False]), pack (Literal[True]), size (int), signed (bool), lilendian (bool) → bytes

  • cls, name (str), default (Optional[int]), namespace (dict[str, int]), reversed (Literal[True]), pack (Literal[False]) → int

  • cls, name (str), default (Optional[int]), namespace (dict[str, int]), reversed (Literal[True]), pack (Literal[True]), size (int), signed (bool), lilendian (bool) → bytes

  • cls, name (str | int | StdlibEnum | AenumEnum), default (Optional[int]), namespace (Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]), reversed (bool), pack (Literal[False]) → int

Parameters:
Returns:

Index of name from a dict or enumeration. If pack is True, returns bytes; otherwise, returns int.

Raises:

ProtocolNotImplemented – If name is NOT in namespace and default is None.

Note

If name is a str and namespace is None, 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 _make_index_namespace()).

classmethod _make_payload(data, exclude=())

Create payload from data for protocol construction.

This method uses __next_type__ and __next_name__ to determine the payload type and name. If either of them is None, a NoPayload instance will be returned. Otherwise, the payload will be constructed by Protocol.from_data.

Info.to_dict leaves both keys out, so data rebuilt from such a dict carries neither. The payload is then found from the structure of data instead, by _lookup_payload().

The payload is built one layer further into the frame than this protocol, as _parse_next_layer() counts it, since each layer built parses its own octets again. So rebuilding a frame stops at FRAME_LAYER_LIMIT where parsing it did, rather than that many layers below each layer of it (#1610). Nor does the payload’s parse warn of the limit: this protocol’s own parse goes over the same octets, so the rebuilt frame warns of it once, as the parsed one did.

Parameters:
  • data (Data) – protocol data

  • exclude (tuple[str, ...]) – keys of data that are not the payload, though they may look like it, passed on to _lookup_payload()

Return type:

ProtocolBase

Returns:

Payload for protocol construction.

_packing_target()

Return the instance that pack() makes its header on.

Return type:

Self

Returns:

The instance itself while it is being constructed, i.e. before __post_init__() has set its info, since its unpack() reads the made header back; a shallow copy of it otherwise, so that neither that header nor an attribute that make() sets is left on the instance (#1769).

Note

A pack() override that does not call this one has to build on this target too, as Frame.pack and PCAPNG.pack do. The copy is shallow, so a make() that mutates a container in place still reaches the instance’s.

static _lookup_registry(registry, code)

Look up a dispatch registry entry without recording a miss.

Parameters:
  • registry (DefaultDict[Any, TypeVar(_VT)]) – dispatch registry to read, i.e. self.__proto__ or one of the per-protocol __option__ / __chunk__ / __block__ family. Passed in rather than read from the class, so that a caller reaching the registry through an instance keeps doing so.

  • code (Any) – registry key to look up, i.e. the wire code being dispatched on

Return type:

TypeVar(_VT)

Returns:

The entry registered for code, or the fallback registry declares when code is not registered.

Important

Every one of these registries is a collections.defaultdict held on a class attribute, shared by every instance of the class in the process. So registry[code] inserts each code it misses, and parsing one packet carrying an unrecognised code grows the registry permanently.

The inserted value is what the default factory would have produced anyway, so the entry buys nothing. It costs a spurious “already registered” warning from the next genuine register call for that code, and makes “is this code registered?” depend on what has been parsed. The fallback is therefore read from the default factory directly rather than through a lookup that records it.

static _lookup_next_layer(registry, proto)

Look up the protocol class registered for a next layer code.

Parameters:
Return type:

Type[ProtocolBase]

Returns:

The class registered for proto, or the fallback registry declares – normally Raw – when proto is not registered.

Important

The lookup itself is self._lookup_registry, so a miss does not grow the shared registry. What this adds is the next-layer-specific resolution step: the entry found – registered, or the fallback – may be a ModuleDescriptor rather than a class, and it is resolved to the class it names.

Nothing is written back, on a hit or a miss. A miss has no key to write under, and recording one would be exactly the insertion self._lookup_registry exists to avoid. A hit memoised under proto would retain a class that importlib.reload() then makes stale, and would make parsing mutate a registry shared by the whole process (#1559). The same holds for a memo in registry’s default factory or in a cache beside the registry (#421, #425, #555).

So every lookup resolves its descriptor again, and what keeps that affordable is ModuleDescriptor.klass reading sys.modules instead of re-entering importlib.import_module() (#574).

_decode_next_layer(dict_, proto, length=None, *, packet=None)

Decode next layer protocol.

Parameters:
  • dict_ (TypeVar(_PT, bound= Data)) – info buffer

  • proto (int) – next layer protocol index

  • length (int | None) – valid (non-padding) length

  • packet (dict[str, Any] | None) – packet info (passed from self.unpack)

Return type:

TypeVar(_PT, bound= Data)

Returns:

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. These two keys will NOT be included when Info.to_dict is called; _lookup_payload() finds the payload of such a dict instead.

_import_next_layer(proto, length=None, *, packet=None)

Import next layer extractor.

Parameters:
Return type:

ProtocolBase

Returns:

Instance of next layer.

static _parse_next_layer(klass, file_, length, **kwargs)

Parse the next layer, or keep it as raw octets if its header was not captured.

Parameters:
  • klass (Type[ProtocolBase]) – next layer protocol class

  • file_ (bytes) – payload of the current layer

  • length (int) – valid (non-padding) length

  • **kwargs (Any) – keyword arguments for the next layer’s constructor

Return type:

ProtocolBase

Returns:

Instance of klass, or of Raw when fewer octets were captured than its header needs, or when it is past FRAME_LAYER_LIMIT.

Note

Schema.unpack zero-fills a field the stream ends inside, so a header parsed from too few octets is filled with values that were never on the wire, and from_data rebuilds them (#1170). Such a payload is kept as Raw instead, as an unparseable one is.

The captured octets are the first length of file_, or all of it when it holds fewer, as a truncated capture does. They are short of the header when there are fewer than the next layer’s __length_hint__, unless the header it parsed, length octets long, fits in them anyway: for a variable-length header such as L2TPv2 the hint is the longest form rather than the shortest. A next layer that reports no hint is kept as parsed.

The warnings of a parse that may be replaced are held until it is known whether it is. They are dropped when it is, since they describe a header the result does not have – packet length < 0 twice for an Ethernet frame of 10 octets (#1580) – and reported as the parse ends when it is kept, still inside the guard that catches them under an error filter. A layer kept with its header cut short still warns. A layer captured at least as long as its hint cannot be replaced, and is parsed holding nothing, even inside one that holds.

A layer past FRAME_LAYER_LIMIT in its frame is not parsed: it and the rest of the frame are kept as Raw, marked _past_layer_limit, with one ProtocolWarning (#1610).

Warns:

ProtocolWarning – If the next layer is past FRAME_LAYER_LIMIT.

_get_context(cls=None)

Get the caller supplied context for this protocol, if any.

The lookup is keyed on self.id, so a protocol finds its own context without knowing how the caller spelled the registry.

Parameters:

cls (Type[TypeVar(_CTX, bound= ProtocolContext)] | None) – Expected context class; when given, a context registered under this protocol’s name but of another type is ignored rather than returned for the implementation to trip over.

Return type:

TypeVar(_CTX, bound= ProtocolContext) | None

Returns:

The matching context, or None when the caller supplied none.

_data: bytes

Raw packet data.

_file: IO[bytes]

Source packet stream.

_info: _PT

Parsed packet data.

_next: ProtocolBase

Next layer protocol instance.

_protos: ProtoChain

Protocol chain instance.

_seekset: int

File pointer.

Type:

int

_sigterm

If terminate parsing next layer of protocol.

Type:

bool

_past_layer_limit: bool = False

Whether this layer is past FRAME_LAYER_LIMIT, and so was kept as Raw by _parse_next_layer() without being parsed. Such a layer carries an error as one that failed to parse does, so this is what tells the two apart: what follows the limit is not to be read on (#1610).

__data__: Type[_PT] = <class 'pcapkit.protocols.data.misc.raw.Raw'>

Protocol packet data definition.

__init__(file=None, length=None, **kwargs)

Initialisation.

Overloads:
  • self, file (IO[bytes] | bytes), length (Optional[int]), kwargs (Any) → None

  • self, kwargs (Any) → None

Parameters:
  • file (IO[bytes] | bytes | None) – Source packet stream.

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

  • _layer (str) – Parse packet until _layer (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 (self._exproto). 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 handed over by self._import_next_layer. While parsing, it is republished as __packet__ so that 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 (self._exctx), c.f. pcapkit.corekit.context. It is consumed here rather than being forwarded to self.read, and is propagated to nested layers by self._import_next_layer.

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

Raises:

UnsupportedCall – When constructing (file is None), if a keyword names no parameter of this protocol’s make(), read(), pack(), unpack(), __post_init__() or __init__(), anywhere in the MRO, and is not listed in __keywords__. Parsing (file is 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 _import_next_layer implementations, and for any third party protocol that copied their shape, rather than one call site at a time.

__post_init__(file=None, length=None, **kwargs)

Post initialisation hook.

Overloads:
  • self, file (IO[bytes] | bytes), length (Optional[int]), kwargs (Any) → None

  • self, kwargs (Any) → None

Parameters:
  • file (IO[bytes] | bytes | None) – Source packet stream.

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

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

See also

For construction arguments, please refer to self.make.

classmethod __init_subclass__(schema=None, data=None, code=None, *args, **kwargs)[source]

Initialisation for subclasses.

Parameters:
  • schema (Type[TypeVar(_ST, bound= Schema)] | None) – Schema class.

  • data (Type[TypeVar(_PT, bound= Data)] | None) – Data class.

  • code (Any) – Next-layer dispatch registration key(s). None (the default) skips registration entirely. See ProtocolBase.__init_subclass__() for the accepted shapes and the enum-type inference rule.

  • *args (Any) – Arbitrary positional arguments.

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

This method is called when a subclass of Protocol is defined. It is used to set the self.__schema__ attribute of the subclass.

Notes

When schema and/or data is not specified, the method will first try to find the corresponding class in the schema and data modules respectively. If the class is not found, the default Raw and Raw classes will be used.

This method also registers the subclass to the protocol registry, i.e., pcapkit.protocols.__proto__. That registration is unconditional – it is the name-keyed identity registry, unrelated to the code keyword – whereas code’s next-layer dispatch registration is opt-in; see ProtocolBase.__init_subclass__() for the latter.

See also

For more information on the registry, please refer to pcapkit.foundation.registry.protocols.register_protocol().

__repr__()

Returns representation of parsed protocol data.

Example

>>> protocol
<Frame frame=<Frame ..., ethernet=Ethernet(...), protocols='Ethernet:IPv6:Raw', packet=b'...'>>
Return type:

str

__str__()

Returns formatted hex representation of source data stream.

Example

>>> protocol
<Frame frame=<Frame ..., ethernet=Ethernet(...), protocols='Ethernet:IPv6:Raw', packet=b'...'>>
>>> print(protocol)
00 00 00 00 00 00 00 a6 87 f9 27 93 16 ee fe 80 00 00 00     ..........'........
00 00 00 1c cd 7c 77 ba c7 46 b7 87 00 0e aa 00 00 00 00     .....|w..F.........
fe 80 00 00 00 00 00 00 1c cd 7c 77 ba c7 46 b7 01 01 a4     ..........|w..F....
5e 60 d9 6b 97                                               ^`.k.
Return type:

str

__getitem__(key)

Subscription (getitem) support.

  • If key is a Protocol object, the method will fetch its indexes (self.id).

  • Later, search the packet’s chain of protocols with the calculated key.

  • If no matches, then raises ProtocolNotFound.

Parameters:

key (str | Protocol | Type[Protocol]) – Indexing key.

Return type:

ProtocolBase

Returns:

The sub-packet from the current packet of indexed protocol.

Raises:

ProtocolNotFound – If key is not in the current packet.

See also

The method calls self.expand_comp to handle the key and expand it for robust searching.

__contains__(name)

Returns if certain protocol is in the instance.

Parameters:

name (str | Protocol | Type[Protocol]) – Name to search

See also

The method calls self.expand_comp to handle the name and expand it for robust searching.

Return type:

bool

abstractmethod classmethod __index__()

Numeral registry index of the protocol.

Return type:

IntEnum | IntEnum

__length_hint__()

Return an estimated length for the object.

Return type:

int | None

_exlayer: str | None

Parse packet until such layer.

Type:

str

_exproto: str | ProtocolBase | Type[ProtocolBase] | None

Parse packet until such protocol.

Type:

str

_exctx: ContextRegistry | None = None

Caller supplied parsing context, c.f. pcapkit.corekit.context. self.__init__ replaces this with a real ContextRegistry; the class-level None is what an instance built without __init__ (e.g. object.__new__(SomeProtocol)) sees, so reading it is always safe.

Layers per Frame

A frame is dissected to at most FRAME_LAYER_LIMIT layers of its protocol chain, counted from the first layer the frame record carries, through tunnels, VLAN tags and IPv6 extension headers alike. What follows is kept as Raw, with one ProtocolWarning (#1610), and is marked _past_layer_limit, so the default engine’s TCP reassembly and flow tracing do not read a TCP segment there back out, as they do one the TCP parser rejected (#1518). A protocol constructed from its own octets is not counted itself, so the count starts at the first layer it dissects. One chain of extension headers is also bounded on its own, by EXTENSION_HEADER_LIMIT. The pyshark, scapy and pypcapfile engines dissect with their own parsers, which do not apply the frame’s layer bound (#1609). The dpkt toolkit reads what dpkt decodes, which applies neither bound.

Data Models

class pcapkit.protocols.data.protocol.Packet(*args: VT, **kwargs: VT)[source]

Bases: Data

Header and payload data.

header: bytes

packet header

payload: bytes

packet payload

Internal Definitions

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

Bases: Generic[_PT, _ST]

Abstract base class for all protocol families.

The built-in protocol families – the layer base classes Link, Internet, Transport and Application, and the miscellaneous protocols in pcapkit.protocols.misc, PCAP and PCAP-NG included – derive from this class directly. Its metaclass is ProtocolMeta.

Note

This class is for internal use only. For customisation, subclass Protocol to create a new protocol stack, or the matching layer base class above for a new protocol in an existing layer.

classmethod __init_subclass__(schema=None, data=None, code=None, *args, **kwargs)[source]

Initialisation for subclasses.

Parameters:
  • schema (Type[TypeVar(_ST, bound= Schema)] | None) – Schema class.

  • data (Type[TypeVar(_PT, bound= Data)] | None) – Data class.

  • code (Any) – Next-layer dispatch registration key(s). None (the default) skips registration entirely – see below.

  • *args (Any) – Arbitrary positional arguments.

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

Raises:

UnsupportedCall – If any unrecognised class keyword is given.

This method is called when a subclass of Protocol is defined. It is used to set the self.__schema__ attribute of the subclass, and, if code is given, to register the subclass into the next-layer dispatch registry (or registries) that code names – e.g. Link.__proto__.

Notes

When schema and/or data is not specified, the method will first try to find the corresponding class in the schema and data modules respectively. If the class is not found, the default Raw and Raw classes will be used.

Dispatch registration is opt-in, like the engine=/protocol=/ fmt= keywords of Engine, Reassembly, TraceFlow and Dumper. Omitting code is a deliberate way to decline registration, and what every built-in protocol class does: the built-in dispatch tables (e.g. Link.__proto__) are populated by literal assignment in each layer module, not by this hook. A subclass that declines can still be registered later via the owning class’s register() classmethod or the matching register_* helper in pcapkit.foundation.registry.protocols.

code accepts:

  • a single enum member, whose type determines the destination registry (or registries) – e.g. any EtherType member always means Link, and any LinkType member means both Frame and PCAPNG;

  • a dict mapping a destination class to a key, for a key that cannot name its own destination – a raw int port, for instance, is ambiguous between TCP and UDP, and must use this form;

  • an iterable mixing either of the above, to register the same class into several registries from a single declaration – e.g. a L2TP subclass reachable both by its IP protocol number and by a UDP port.

The explicit mapping form is accepted even for a key whose type could be inferred: being more explicit than required is never an error.

Inference refuses rather than guesses: an enum member whose type names no known destination raises RegistryError rather than doing nothing or picking an arbitrary registry.

See also

pcapkit.foundation.registry.protocols.register_protocol_code() implements the resolution described above.

Protocol inherits the other members of this class, and they are documented under it above; each group below links to its entries.

property name
property alias
property info_name
property info
property data
property length
property payload
property protocol
property protochain
property packet[source]
property schema[source]

name, alias, info_name, info, data, length, payload, protocol, protochain, packet, schema

id()[source]
register()[source]
analyze()[source]
from_schema()[source]
from_data()[source]
read()[source]
make()[source]
unpack()[source]
pack()[source]
decode()[source]
unquote()[source]
expand_comp()[source]
_read_packet()[source]
_get_payload()[source]
_make_data()[source]
_make_index()[source]
_make_payload()[source]
_lookup_registry()[source]
_lookup_next_layer()[source]
_decode_next_layer()[source]
_import_next_layer()[source]
_parse_next_layer()[source]
_get_context()[source]
__init__()[source]
__post_init__()[source]
__repr__()[source]
__str__()[source]
__getitem__()[source]
__contains__()[source]
__index__()[source]
__length_hint__()[source]

id(), register(), analyze(), from_schema(), from_data(), read(), make(), unpack(), pack(), decode(), unquote(), expand_comp(), _read_packet(), _get_payload(), _make_data(), _make_index(), _make_payload(), _lookup_registry(), _lookup_next_layer(), _decode_next_layer(), _import_next_layer(), _parse_next_layer(), _get_context(), __init__(), __post_init__(), __repr__(), __str__(), __getitem__(), __contains__(), __index__(), __length_hint__()

__layer__
__proto__
__keywords__
__schema__
__header__
_data
_file
_info
_next
_protos
_seekset
_sigterm
_past_layer_limit
__data__
_exlayer
_exproto
_exctx

__layer__, __proto__, __keywords__, __schema__, __header__, _data, _file, _info, _next, _protos, _seekset, _sigterm, _past_layer_limit, __data__, _exlayer, _exproto, _exctx

class pcapkit.protocols.protocol.ProtocolMeta(name, bases, namespace, /, **kwargs)[source]

Bases: ABCMeta

Metaclass of ProtocolBase, and so of every protocol class.

It adds no behaviour of its own over abc.ABCMeta.

Type Variables

pcapkit.protocols.protocol._PT: pcapkit.protocols.data.data.Data
pcapkit.protocols.protocol._ST: pcapkit.protocols.schema.schema.Schema