Numerical Fields

Sized Fields

class pcapkit.corekit.fields.numbers.Int32Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Integer value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as False, contradicting the sign this class fixes.

class pcapkit.corekit.fields.numbers.UInt32Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Unsigned integer value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as True, contradicting the sign this class fixes.

class pcapkit.corekit.fields.numbers.Int16Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Short integer value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as False, contradicting the sign this class fixes.

class pcapkit.corekit.fields.numbers.UInt16Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Unsigned short integer value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as True, contradicting the sign this class fixes.

class pcapkit.corekit.fields.numbers.Int64Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Long integer value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as False, contradicting the sign this class fixes.

class pcapkit.corekit.fields.numbers.UInt64Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Unsigned long integer value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as True, contradicting the sign this class fixes.

class pcapkit.corekit.fields.numbers.Int8Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Byte value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as False, contradicting the sign this class fixes.

class pcapkit.corekit.fields.numbers.UInt8Field(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: NumberField

Unsigned byte value for protocol fields.

Parameters:
Raises:

FieldValueError – If signed is given as True, contradicting the sign this class fixes.

Enumeration Fields

class pcapkit.corekit.fields.numbers.EnumField(length, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, namespace=None, callback=<function EnumField.<lambda>>)[source]

Bases: NumberField[IntEnum | IntEnum]

Enumerated value for protocol fields.

Parameters:
  • length (int | Callable[[dict[str, Any]], int]) – Field size (in bytes); if a callable is given, it should return an integer value and accept the current packet as its only argument.

  • default (IntEnum | IntEnum | NoValueType) – Field default value, if any.

  • signed (bool | None) – Whether the field is signed; None defers to the class-level __signed__, which this class leaves unset and so means unsigned.

  • byteorder (Literal['little', 'big']) – Field byte order.

  • bit_length (int | None) – Field bit length.

  • namespace (Type[IntEnum] | Type[IntEnum] | None) – Field namespace (a enum.IntEnum class).

  • callback (Callable[[Self, dict[str, Any]], None]) – Callback function to be called upon self.__call__.

Notes

A wire value the namespace registry has no member for resolves to a nameless pseudo-member rather than failing the parse – see post_process().

post_process(value, packet)[source]

Process field value after parsing (unpacked).

Parameters:
Return type:

IntEnum | IntEnum

Returns:

Processed field value – the registry member declared for the value, or a nameless pseudo-member carrying the value itself when the registry declares none.

Raises:

BaseError – Whatever in-library error the registry raised for the value, re-raised untouched.

Notes

The registry is consulted through its constructor, which raises for a value no member and no _missing_ rule accounts for. That raise used to propagate, and it is aenum’s own bare ValueError: not one of pcapkit.utilities.exceptions, so a caller cannot tell it from a bug of its own, and not an EOFError, so Extractor.record_frames does not catch it. One unassigned code therefore cost the whole extraction.

It also made the “unknown” reader the formats require unreachable for any genuinely unassigned code – PCAP-NG’s UnknownBlock, and the unassigned option readers of IPv4, TCP, HOPOPT, MH and HIP – because the lookup failed several frames before the dispatch that would have selected it. PCAP-NG repeats a block’s total length at both ends precisely so that a reader can skip a block type it does not recognise; that skip is what this fallback restores. See GitHub issue #701.

The fallback is the same nameless pseudo-member this method already builds for a field carrying no registry at all, so it is a value shape the package already produces and the dump layer already renders – as <unknown>::<unassigned> [28], through render_enum(), not through the name is None branch #648 added, which a member named <unassigned> never takes – and one an int-keyed dispatch registry looks up by value like any declared member. It is built per value rather than grafted onto the registry with aenum.extend_enum(), for two reasons: a capture carrying many distinct unassigned codes would otherwise grow a process-global registry without bound, which is the growth ProtocolBase._lookup_registry exists to avoid; and a stdlib enum.IntEnum registry is then handled exactly like an aenum.IntEnum one.

Only a foreign rejection is absorbed. A registry rejecting a value with one of pcapkit.utilities.exceptions has made a deliberate decision that this layer – which sees only that the value arrived in a field of some width – is in no position to overrule, so an in-library error propagates unchanged and it is only aenum’s and enum’s “no member has this value” that becomes a pseudo-member. That is what keeps the fallback from being an unconditional except ValueError: pass.

No registry under pcapkit.const raises an in-library error from its guard today, and deliberately so: a generated guard raises a bare, unlogged ValueError precisely because the generated get()’s except ValueError fallback has to keep catching it (GitHub issues #584 and #647). The registries that do bound themselves to a width and reject outside it are the bit-flag ones – pcapkit.const.tcp.flags.Flags among them – and none of those is named as the namespace of a plain EnumField anywhere in the package, so no in-library guard loses its force through this method. The distinction is therefore for a registry registered from outside pcapkit.const, which has no such obligation to stay quiet.

_pseudo_member(value)[source]

Build the bounded nameless pseudo-member this method falls back to when value is a foreign miss rather than an in-library rejection.

Return type:

IntEnum | IntEnum

Returns:

A single-member, throwaway enum.IntEnum instance, built fresh per call rather than extend_enum()-ed onto self._namespace, per this method’s own docstring above.

static _unregistered_member(namespace, value, name='<unassigned>', **attrs)[source]

Build a member of namespace, absent from every one of its own lookup tables, for a value a parse – rather than a direct call to the registry’s own get() – resolved without anyone asking for a name.

GitHub issue #575: the owner’s ruling is that an unassigned wire value should resolve to a real member of the registry the field names – isinstance against it and every ancestor holds, and it renders and dispatches exactly like a declared one – provided building it never grows the registry, which is the whole reason the field stopped calling get() unconditionally in the first place. This is what gets there: it calls namespace’s own storage base’s __new__ directly – str or int, whichever namespace derives from – which skips namespace’s own __new__ entirely, and with it the cls.__registry__.add(...) / cls.__members_ns__[...] = ... line every registry in this package uses to record a member it mints. No entry is added to _member_map_ or _value2member_map_ either, since those are only ever touched by the metaclass machinery aenum.extend_enum() drives, which this bypasses completely.

Note

Building a member this way, rather than as some other type altogether, is why post_process() and its siblings need this rather than _pseudo_member(): the result answers isinstance(result, AppType) truthfully, which matters to at least seven isinstance sites elsewhere in pcapkit.protocols (see test_a_member_is_still_an_apptype in tests/const/test_const_apptype_split_unit.py), and a value that fails all of them would be a second defect standing in for the one this fix removes.

The member this returns is absent from _value2member_map_, so a value-keyed lookup on it – self._namespace(value) – still raises exactly as it did before this existed. Nothing on the parse or reconstruction path does that to a value it just resolved this way, which is what keeps this safe to return from post_process. A direct call to get() for the same port is a different matter and deliberately unchanged: asking the registry for a name is an explicit request for a named member, so it still mints one – measured on this tree, AppType.get(54321, proto=tcp) returns PORT_54321_tcp and takes TCP.__members__ from 6147 to 6148, and a second call with the same port returns that member rather than raising. Two unregistered members for the same value also compare equal without being identical, since AppType and OptionType both define __eq__/__hash__ off an attribute (.port / .opt_value) rather than object identity – harmless for every reader in this package, since none compares one with is or keys a mapping on it expecting identity, but worth knowing before reusing this elsewhere.

pickle and copy.copy()/copy.deepcopy() all reduce an Enum member through Enum.__reduce_ex__, which returns (cls, (value,)) – the one lookup this member is deliberately absent from. Left alone that is a genuine regression rather than a pre-existing limitation, because the call sites used to mint, so the member was registered and a round-trip worked. Measured on CPython 3.14.7, resolving port 53406 through PortEnumField: on 83b58ebda pickle.loads(pickle.dumps(member)) returned the member, and with the mint removed and nothing in its place it raised ValueError: 'unknown [53406 - tcp]' is not a valid TCP – while pickle.dumps still succeeded, so the failure surfaced only on read-back rather than where it was caused.

So __reduce_ex__ is set on the member itself, reducing it to _rebuild_unregistered_member() instead of to a value lookup. Both pickle and copy fetch that attribute with getattr() on the object rather than on its type, so a per-instance override is honoured: verified against the C pickle accelerator on every protocol from 0 to 5, against the pure-Python pickle._Pickler, and against copy.copy()/copy.deepcopy() both as they are on 3.11+ and with CPython’s Enum.__copy__/__deepcopy__ deleted to emulate 3.10, where those two do not exist. Rebuilding re-enters this method rather than namespace.__new__, so an unpickled member is unregistered exactly as the original was and the registry does not grow – TCP.__members__ measured at 6147 before and after. On 3.11+ copy/deepcopy still return the member itself, since Enum.__copy__ short-circuits ahead of any reduction; on 3.10 they return an equal rebuilt one, which is the same answer for an immutable value.

Parameters:
  • namespace (Type[IntEnum] | Type[IntEnum]) – The concrete registry class to build the member as an instance of. isinstance holds against it and every ancestor; it never gains an entry in any of its own tables.

  • value (Any) – The value namespace’s own constructor would have wrapped – e.g. the crafted string __new__() builds from a name, a port and a transport, or __new__()’s equivalent – kept the same shape here so a rendered or re-keyed member reads the same either way.

  • name (str) – The member’s own .name; '<unassigned>' matches every other nameless value this package produces.

  • **attrs (Any) – Extra attributes to set on the returned member, matching the shape the caller’s registry gives its real members – AppType’s .port, .svc and .proto, or OptionType’s .opt_name and .opt_value.

Return type:

Any

Returns:

The unregistered member.

Raises:

TypeError – If namespace derives from neither str nor int – every registry this package builds does one or the other, and guessing wrong for some future one would ship a member silently missing whatever its storage base provides, rather than saying plainly that this needs extending first.

Internal Definitions

class pcapkit.corekit.fields.numbers.NumberField(length=None, default=<pcapkit.corekit.fields.field.NoValueType object>, signed=None, byteorder='big', bit_length=None, callback=<function NumberField.<lambda>>)[source]

Bases: Field[int], Generic[_T]

Numerical value for protocol fields.

Parameters:
  • length (int | Callable[[dict[str, Any]], int] | None) – Field size (in bytes); if a callable is given, it should return an integer value and accept the current packet as its only argument.

  • default (int | NoValueType) – Field default value, if any.

  • signed (bool | None) – Whether the field is signed; None defers to the class-level __signed__, which this class leaves unset and so means unsigned.

  • byteorder (Literal['little', 'big']) – Field byte order.

  • bit_length (int | None) – Field bit length.

  • callback (Callable[[Self, dict[str, Any]], None]) – Callback function to be called upon self.__call__.

Raises:
  • IntError – If no length is given and __length__ fixes none either.

  • FieldValueError – If signed contradicts a sign already fixed by __signed__ – never from this class, which fixes none.

  • ProtocolError – If bit_length is given negative. Left alone, (1 << bit_length) - 1 raises a bare, uncatchable ValueError (negative shift count) here, before __call__()’s own negative-length guard (#828/#829) or length’s (#805) ever see anything – this one fires at construction time, on the argument itself rather than on a resolved wire length. See GitHub issue #831.

Notes

A subclass such as UInt32Field fixes the sign through __signed__, so signed there is at best redundant. It used to be discarded outright, in both directions, which meant UInt32Field(signed=True) handed back an unsigned field whose values only looked wrong once the high bit was set – see GitHub issue #545. A contradicting value is now rejected instead; omitting it, or passing the sign the class already fixes, stays legal.

property bit_length: int

Field bit length.

__call__(packet)[source]

Update field attributes.

Parameters:

packet (dict[str, Any]) – Packet data.

Return type:

Self

Returns:

New instance of NumberField.

This method will return a new instance of NumberField instead of updating the current instance.

Raises:

ProtocolError – If the resolved length is negative – e.g. a length callback such as lambda pkt: pkt['len'] - 4 resolving below zero once the wire value it reads is smaller than the subtrahend. Left alone, 1 << (length * 8) raises a bare, uncatchable ValueError (negative shift     count) when bit_length was not supplied, before length (see its own ProtocolError guard, #805/#811/#827) or build_template() ever sees the value: this method sets self._bit_length from the resolved length eagerly, as a cache, and shifts by it immediately, so the crash happens on this line rather than on the later, already-guarded ones. See GitHub issue #828. This guard runs regardless of whether bit_length was supplied, so a field constructed with a fixed bit_length and a callable length that resolves negative raises the identical message as one with no bit_length at all, rather than falling through to a template='...-1s' ProtocolError from length later – see GitHub issue #831. A resolved length of exactly 0 is a legitimate empty field (e.g. len=4 above resolving to 0) and is left alone.

Notes

Rebuilding the template here is what applies a callable length, and build_template() recomputes self._need_process as it goes, so the flag and the template always describe the same width. They did not always: see GitHub issue #591.

build_template(length, signed)[source]

Build template for field.

Parameters:
  • length (int) – Field size (in bytes)

  • signed (bool) – Whether the field is signed

Return type:

str

Returns:

Template for field.

Notes

self._need_process is assigned here rather than only ever raised, so that it always describes the length this template was built for. It used to be set True in the fall-through branch and never put back, which made it a latch: a callable length is a placeholder of -1 at construction, -1 takes the fall-through branch, and the flag then survived the rebuild in __call__() that resolved the real width. pre_process() consequently handed bytes to a template that had become >Q – or >I, >H, >B – and struct.pack() refused it. See GitHub issue #591.

Assigning it is what tells a placeholder apart from a width that genuinely needs byte packing, without having to remember that a placeholder was ever in play: the answer for -1 is True, the answer for 8 is False, and whichever width is in force now is the one that decides. A callable resolving to, say, 3 still takes the fall-through branch and still gets True, because for 3 that is the correct answer rather than a leftover one.

pre_process(value, packet)[source]

Process field value before construction (packing).

Parameters:
  • value (int) – Field value.

  • packet (dict[str, Any]) – Packet data.

Return type:

int | bytes

Returns:

Processed field value.

Notes

Masking against self._bit_mask truncates the value to the field’s bit length, but it also turns a negative value into its unsigned two’s-complement pattern, which neither struct.pack() nor int.to_bytes() accepts for a signed field. A signed field therefore maps the pattern back into its signed range afterwards, so that e.g. a PCAP-NG section length of -1 (section length not specified) can be written out.

A field packed without having been resolved – so with _length still negative – has its width derived from the value instead, and that rebuild can land on a width struct() has a native integer code for. The flag is therefore consulted after the rebuild rather than before it, since deciding first and rebuilding second is how the template and the value being returned came to disagree in the first place. C.f. #591.

That width is a ceiling of the bit length over eight, and it is written as one. It used to read math.ceil(value.bit_length() // 8), which is not a ceiling at all: math.ceil() of an int is that int, so the // had already floored the quotient and the outer call did nothing. Every value whose bit length is not an exact multiple of eight was therefore sized one octet short – 256 at one octet, 65536 at two, and 1 itself at zero – which int.to_bytes() and struct.pack() both refuse. See GitHub issue #599.

post_process(value, packet)[source]

Process field value after parsing (unpacked).

Parameters:
Return type:

int

Returns:

Processed field value.

pcapkit.corekit.fields.numbers._rebuild_unregistered_member(namespace, value, name, attrs)[source]

Rebuild the member EnumField._unregistered_member() returned.

This is what pickle and copy reconstruct through, in place of the value lookup Enum.__reduce_ex__ would otherwise have reduced the member to. It is a module-level function rather than a method so that every pickle protocol can name it: protocols below 4 cannot reference a callable nested inside a class.

Parameters:
  • namespace (Type[IntEnum] | Type[IntEnum]) – The registry class to rebuild the member as an instance of.

  • value (Any) – The member’s _value_.

  • name (str) – The member’s _name_.

  • attrs (dict[str, Any]) – The extra attributes the member carried.

Return type:

Any

Returns:

A member equal to the original and, like it, absent from every one of namespace’s lookup tables – rebuilding goes back through EnumField._unregistered_member() and never through namespace.__new__, so it cannot register anything either.

pcapkit.corekit.fields.numbers._reduce_unregistered_member(namespace, value, name, attrs, protocol)[source]

The __reduce_ex__ EnumField._unregistered_member() installs.

Bound to its first four arguments with functools.partial(), so that the reducer holds the ingredients of the member rather than the member itself.

Parameters:
  • namespace (Type[IntEnum] | Type[IntEnum]) – The registry class the member is an instance of.

  • value (Any) – The member’s _value_.

  • name (str) – The member’s _name_.

  • attrs (dict[str, Any]) – The extra attributes the member carries.

  • protocol (int) – The pickle protocol version, ignored – the reduction is the same for all of them, and copy passes 4 here.

Return type:

tuple[Callable[..., Any], tuple[Any, ...]]

Returns:

A two-tuple of _rebuild_unregistered_member() and its arguments.

Type Variables

pcapkit.corekit.fields.numbers._T: int