# -*- 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')