Source code for pcapkit.const.pcapng.option_type

# -*- coding: utf-8 -*-
# mypy: disable-error-code=assignment
# pylint: disable=line-too-long,consider-using-f-string
"""Option Types
==================

.. module:: pcapkit.const.pcapng.option_type

This module contains the constant enumeration for **Option Types**,
which is automatically generated from :class:`pcapkit.vendor.pcapng.option_type.OptionType`.

"""
from collections import defaultdict
from typing import TYPE_CHECKING

from aenum import StrEnum, extend_enum

from pcapkit.corekit.enum import EnumRegistry
from pcapkit.utilities.exceptions import EnumValueError

__all__ = ['OptionType']

if TYPE_CHECKING:
    from typing import Any, DefaultDict, Optional, Type


[docs] class OptionType(EnumRegistry, StrEnum): """[OptionType] Option Types .. versionchanged:: 1.1.0 A member converts with :class:`int` to its option code, and compares, orders and hashes as that code. """ if TYPE_CHECKING: #: Short name of the option type. opt_name: 'str' #: Numeric value of the option type. opt_value: 'int' #: Mapping of members based on namespace. __members_ns__: 'DefaultDict[str, dict[int, OptionType]]' = defaultdict(dict) def __new__(cls, value: 'int', name: 'str' = 'opt_unknown') -> 'Type[OptionType]': temp = '%s [%d]' % (name, value) obj = str.__new__(cls, temp) obj._value_ = temp obj.opt_name = name obj.opt_value = value namespace = name.split('_', maxsplit=1)[0] cls.__members_ns__[namespace][value] = obj return obj def __repr__(self) -> 'str': return f'<{self.__class__.__name__}.{self.opt_name}: {self.opt_value}>' def __str__(self) -> 'str': return '%s [%d]' % (self.opt_name, self.opt_value) def __int__(self) -> 'int': return self.opt_value def __lt__(self, other: 'OptionType') -> 'bool': return self.opt_value < other def __gt__(self, other: 'OptionType') -> 'bool': return self.opt_value > other def __le__(self, other: 'OptionType') -> 'bool': return self.opt_value <= other def __ge__(self, other: 'OptionType') -> 'bool': return self.opt_value >= other def __eq__(self, other: 'Any') -> 'bool': return self.opt_value == other def __ne__(self, other: 'Any') -> 'bool': return self.opt_value != other def __hash__(self) -> 'int': return hash(self.opt_value) #: opt_endofopt opt_endofopt: 'OptionType' = 0, 'opt_endofopt' #: opt_comment opt_comment: 'OptionType' = 1, 'opt_comment' #: opt_custom opt_custom_2988: 'OptionType' = 2988, 'opt_custom' #: opt_custom opt_custom_2989: 'OptionType' = 2989, 'opt_custom' #: opt_custom opt_custom_19372: 'OptionType' = 19372, 'opt_custom' #: opt_custom opt_custom_19373: 'OptionType' = 19373, 'opt_custom' #: if_name if_name: 'OptionType' = 2, 'if_name' #: if_description if_description: 'OptionType' = 3, 'if_description' #: if_IPv4addr if_IPv4addr: 'OptionType' = 4, 'if_IPv4addr' #: if_IPv6addr if_IPv6addr: 'OptionType' = 5, 'if_IPv6addr' #: if_MACaddr if_MACaddr: 'OptionType' = 6, 'if_MACaddr' #: if_EUIaddr if_EUIaddr: 'OptionType' = 7, 'if_EUIaddr' #: if_speed if_speed: 'OptionType' = 8, 'if_speed' #: if_tsresol if_tsresol: 'OptionType' = 9, 'if_tsresol' #: if_tzone if_tzone: 'OptionType' = 10, 'if_tzone' #: if_filter if_filter: 'OptionType' = 11, 'if_filter' #: if_os if_os: 'OptionType' = 12, 'if_os' #: if_fcslen if_fcslen: 'OptionType' = 13, 'if_fcslen' #: if_tsoffset if_tsoffset: 'OptionType' = 14, 'if_tsoffset' #: if_hardware if_hardware: 'OptionType' = 15, 'if_hardware' #: if_txspeed if_txspeed: 'OptionType' = 16, 'if_txspeed' #: if_rxspeed if_rxspeed: 'OptionType' = 17, 'if_rxspeed' #: epb_flags epb_flags: 'OptionType' = 2, 'epb_flags' #: epb_hash epb_hash: 'OptionType' = 3, 'epb_hash' #: epb_dropcount epb_dropcount: 'OptionType' = 4, 'epb_dropcount' #: epb_packetid epb_packetid: 'OptionType' = 5, 'epb_packetid' #: epb_queue epb_queue: 'OptionType' = 6, 'epb_queue' #: epb_verdict epb_verdict: 'OptionType' = 7, 'epb_verdict' #: ns_dnsname ns_dnsname: 'OptionType' = 2, 'ns_dnsname' #: ns_dnsIP4addr ns_dnsIP4addr: 'OptionType' = 3, 'ns_dnsIP4addr' #: ns_dnsIP6addr ns_dnsIP6addr: 'OptionType' = 4, 'ns_dnsIP6addr' #: isb_starttime isb_starttime: 'OptionType' = 2, 'isb_starttime' #: isb_endtime isb_endtime: 'OptionType' = 3, 'isb_endtime' #: isb_ifrecv isb_ifrecv: 'OptionType' = 4, 'isb_ifrecv' #: isb_ifdrop isb_ifdrop: 'OptionType' = 5, 'isb_ifdrop' #: isb_filteraccept isb_filteraccept: 'OptionType' = 6, 'isb_filteraccept' #: isb_osdrop isb_osdrop: 'OptionType' = 7, 'isb_osdrop' #: isb_usrdeliv isb_usrdeliv: 'OptionType' = 8, 'isb_usrdeliv' #: pack_flags pack_flags: 'OptionType' = 2, 'pack_flags' #: pack_hash pack_hash: 'OptionType' = 3, 'pack_hash'
[docs] @classmethod def _unregistered_member(cls, value: 'int', name: 'str') -> 'OptionType': """Build a member absent from this registry's own lookup tables. Cannot reuse the base's generic construction -- :meth:`~pcapkit.corekit.enum.EnumRegistry._unregistered_member` calls :class:`str`'s ``__new__`` directly on the raw ``value``, but OptionType stores a *formatted* string (``'{name} [{value}]'``) as its own :attr:`~aenum.Enum._value_`, not ``value`` itself, so that approach would build a member whose value looks nothing like this registry's real members. Deliberately does **not** add to :attr:`__members_ns__` either -- that dict is this registry's own second lookup table, alongside :attr:`~aenum.Enum._value2member_map_`, and growing it would defeat the whole point of an *unregistered* member exactly as growing ``_value2member_map_`` would. Args: value: Value to get enum item. name: Bare label for the unregistered member, per the ranged mint/unmint criterion. """ temp = '%s [%d]' % (name, value) obj = str.__new__(cls, temp) obj._name_ = name # pylint: disable=attribute-defined-outside-init obj._value_ = temp obj.opt_name = name obj.opt_value = value return obj
[docs] @classmethod def register(cls, value: 'int', name: 'str') -> 'OptionType': """Register a new option code ``value`` under ``name``. Overrides :meth:`~pcapkit.corekit.enum.EnumRegistry.register` because of this registry's own :meth:`__new__`: * :meth:`__new__` takes the option name as its second argument and files the member under the namespace that name's prefix gives (``if_rtaudit`` goes under ``if``), so ``name`` is passed through to it as well as used as the member's attribute name. * :attr:`~aenum.Enum._value_` is the formatted ``'{name} [{value}]'`` string, so the base's duplicate check against :attr:`~aenum.Enum._value2member_map_` can never match an :class:`int` code. The check runs against :attr:`__members_ns__` instead, the table :meth:`get` resolves codes through: both ``name``'s own namespace and ``opt``, whose codes are common to every block and which :meth:`get` overlays the namespace on. Args: value: Option code of the new member. name: Name of the new member, led by its namespace, e.g. ``if_rtaudit``. Returns: The newly registered member. Raises: ValueError: If ``value`` is already assigned in ``name``'s namespace or in ``opt``, or if ``name`` is already taken. EnumValueError: If ``value`` is outside the range :meth:`_missing_` accepts, from :meth:`_validate_value`. A :exc:`ValueError`, carrying the message the constructor raises for the same code. """ namespace = name.split('_', maxsplit=1)[0] for space in ('opt', namespace): existing = cls.__members_ns__.get(space, {}).get(value) if existing is not None: raise ValueError(f'{value!r} is already registered on {cls.__name__} as ' f'{existing.name!r} in namespace {space!r}') cls._validate_value(value) try: return extend_enum(cls, name, value, name) except TypeError as error: raise ValueError(str(error)) from error
[docs] @classmethod def register_alias(cls, value: 'int', name: 'str') -> 'OptionType': """Add ``name`` as a further name for the member at option code ``value``. Overrides :meth:`~pcapkit.corekit.enum.EnumRegistry.register_alias` for the same reason as :meth:`register`: the base looks the :class:`int` code up in :attr:`~aenum.Enum._value2member_map_`, whose keys are the formatted ``'{name} [{value}]'`` strings, so it could never find one. The member is resolved as :meth:`get` resolves ``value`` with ``namespace`` set to ``name``'s prefix: that namespace overlaid on ``opt``. So ``register_alias(2, 'if_label')`` aliases ``if_name``, while ``register_alias(1, 'if_note')`` aliases ``opt_comment``, since ``if`` assigns no code 1 of its own. The alias adds a name only; the member keeps its own name and namespace. Args: value: Option code of the existing member to alias. name: Alias to add for it, led by the namespace to resolve ``value`` in. Returns: The existing member, now reachable under ``name`` as well. Raises: ValueError: If neither ``name``'s namespace nor ``opt`` assigns ``value``, or if ``name`` is already taken. """ namespace = name.split('_', maxsplit=1)[0] member = cls.__members_ns__.get(namespace, {}).get(value) if member is None: member = cls.__members_ns__.get('opt', {}).get(value) if member is None: raise ValueError(f'{value!r} is not a registered {cls.__name__} in namespace ' f'{namespace!r} or opt; use {cls.__name__}.register() to mint one') # NOTE: :func:`~aenum.extend_enum` builds a throwaway member through # :meth:`__new__` to learn its value, and :meth:`__new__` files that # throwaway in :attr:`__members_ns__` over ``member``. The alias # resolves to ``member``, so ``member`` is put back. member_ns = member.opt_name.split('_', maxsplit=1)[0] try: return extend_enum(cls, name, value, member.opt_name) except TypeError as error: raise ValueError(str(error)) from error finally: cls.__members_ns__[member_ns][value] = member
@staticmethod def get(key: 'int | str', default: 'int' = -1, *, namespace: 'str' = 'opt') -> 'OptionType': """Backport support for original codes. Args: key: Key to get enum item. default: Default value if not found. namespace: Namespace of the enum item. :meta private: """ if isinstance(key, int): temp_ns = OptionType.__members_ns__.get('opt', {}).copy() temp_ns.update(OptionType.__members_ns__.get(namespace, {})) if key in temp_ns: return temp_ns[key] # NOTE: GitHub issue #860. An extend_enum(...) here would mint a # permanent member as a side effect of a mere lookup -- exactly # the ``get``/``_missing_`` minting the owner's ruling forbids, # here on the wire-facing path # (:meth:`pcapkit.protocols.misc.pcapng.PCAPNG._make_pcapng_options` # calls this with raw option-code bytes off the wire). # ``_unregistered_member`` never touches ``__members_ns__`` # either, so a genuinely unknown option code grows neither # lookup table. return OptionType._unregistered_member(key, '%s_unknown' % namespace) if key in OptionType.__members__: return getattr(OptionType, key) # NOTE: a member's value is its formatted ``'{name} [{value}]'`` # string, so ``get(member.value)`` resolves it as ``cls(value)`` would. if key in OptionType._value2member_map_: # type: ignore[misc] return OptionType._value2member_map_[key] # type: ignore[misc,return-value] # NOTE: same ruling, the str-keyed path: rather than mint ``key`` # itself as the member's name with ``default`` as its value -- neither # ``get`` nor ``_missing_`` has enough information to register one # properly, as there is no namespace, description or anything else # IANA would attach to it -- this returns an unregistered # pseudo-member with the same attributes such a mint would set. return OptionType._unregistered_member(default, key)
[docs] @classmethod def _validate_value(cls, value: 'Any') -> 'None': """Reject ``value`` unless it passes the range check :meth:`_missing_` makes. Overrides :meth:`~pcapkit.corekit.enum.EnumLookup._validate_value` with that check and the message it raises, so :meth:`register` refuses exactly the option codes the constructor refuses, in the same words (:issue:`1816`). :meth:`get` never calls it: that override answers every :class:`int` code, out of range or not, with an unregistered member. Args: value: Option code about to be minted. Raises: EnumValueError: If ``value`` fails that check. A :exc:`ValueError`, so an ``except ValueError`` caller is unaffected. """ if not (isinstance(value, int) and 0 <= value <= 0xFFFF): raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs] @classmethod def _missing_(cls, value: 'int') -> 'OptionType': """Lookup function used when value is not found. Args: value: Value to get enum item. """ if not (isinstance(value, int) and 0 <= value <= 0xFFFF): raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if value in cls.__members_ns__.get('opt', {}): return cls.__members_ns__['opt'][value] return cls._unregistered_member(value, 'opt_unknown')