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
15 changes: 14 additions & 1 deletion pcapkit/protocols/internet/internet.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,26 @@ def register(cls, code: 'Enum_TransType', protocol: 'ModuleDescriptor[Protocol]
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
:class:`~pcapkit.protocols.protocol.Protocol` subclass.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If this transport-layer
protocol number is already registered, naming the displaced
entry and its replacement so a caller can tell *what* was lost.
Fires on presence alone -- see :meth:`ProtocolBase.register
<pcapkit.protocols.protocol.ProtocolBase.register>` for why that
differs from ``register_protocol``.

"""
if isinstance(protocol, ModuleDescriptor):
protocol = protocol.klass
if not issubclass(protocol, Protocol):
raise RegistryError(f'protocol must be a Protocol subclass, not {protocol!r}')
if code in cls.__proto__:
warn(f'protocol {code} already registered, overwriting', RegistryWarning)
warn(f'protocol {code} already registered, overwriting '
f'{cls.__proto__[code]!r} with {protocol!r}', RegistryWarning)
cls.__proto__[code] = protocol

##########################################################################
Expand Down
15 changes: 14 additions & 1 deletion pcapkit/protocols/link/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,26 @@ def register(cls, code: 'Enum_EtherType', protocol: 'ModuleDescriptor[Protocol]
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
:class:`~pcapkit.protocols.protocol.Protocol` subclass.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If this EtherType is
already registered, naming the displaced entry and its
replacement so a caller can tell *what* was lost. Fires on
presence alone -- see :meth:`ProtocolBase.register
<pcapkit.protocols.protocol.ProtocolBase.register>` for why that
differs from ``register_protocol``.

"""
if isinstance(protocol, ModuleDescriptor):
protocol = protocol.klass
if not issubclass(protocol, Protocol):
raise RegistryError(f'protocol must be a Protocol subclass, not {protocol!r}')
if code in cls.__proto__:
warn(f'protocol {code} already registered, overwriting', RegistryWarning)
warn(f'protocol {code} already registered, overwriting '
f'{cls.__proto__[code]!r} with {protocol!r}', RegistryWarning)
cls.__proto__[code] = protocol

##########################################################################
Expand Down
15 changes: 14 additions & 1 deletion pcapkit/protocols/misc/pcap/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,26 @@ def register(cls, code: 'Enum_LinkType', protocol: 'ModuleDescriptor[Protocol] |
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
:class:`~pcapkit.protocols.protocol.Protocol` subclass.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If this link type is
already registered against PCAP frames, naming the displaced
entry and its replacement so a caller can tell *what* was lost.
Fires on presence alone -- see :meth:`ProtocolBase.register
<pcapkit.protocols.protocol.ProtocolBase.register>` for why that
differs from ``register_protocol``.

"""
if isinstance(protocol, ModuleDescriptor):
protocol = protocol.klass
if not issubclass(protocol, Protocol):
raise RegistryError(f'protocol must be a Protocol subclass, not {protocol!r}')
if code in cls.__proto__:
warn(f'protocol {code} already registered, overwriting', RegistryWarning)
warn(f'protocol {code} already registered, overwriting '
f'{cls.__proto__[code]!r} with {protocol!r}', RegistryWarning)
cls.__proto__[code] = protocol

def index(self, name: 'str | Protocol | Type[Protocol]') -> 'int':
Expand Down
15 changes: 14 additions & 1 deletion pcapkit/protocols/misc/pcapng.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,13 +866,26 @@ def register(cls, code: 'Enum_LinkType', protocol: 'ModuleDescriptor[Protocol] |
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
:class:`~pcapkit.protocols.protocol.Protocol` subclass.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If this link type is
already registered against PCAP-NG blocks, naming the displaced
entry and its replacement so a caller can tell *what* was lost.
Note this registry is separate from the PCAP one, so
:func:`~pcapkit.foundation.registry.protocols.register_linktype`
writing to both cannot make either warn about the other.

"""
if isinstance(protocol, ModuleDescriptor):
protocol = protocol.klass
if not issubclass(protocol, Protocol):
raise RegistryError(f'protocol must be a Protocol subclass, not {protocol!r}')
if code in cls.__proto__:
warn(f'protocol {code} already registered, overwriting', RegistryWarning)
warn(f'protocol {code} already registered, overwriting '
f'{cls.__proto__[code]!r} with {protocol!r}', RegistryWarning)
cls.__proto__[code] = protocol

@classmethod
Expand Down
32 changes: 31 additions & 1 deletion pcapkit/protocols/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,13 +767,43 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor | Type[ProtocolBase]'
protocol: module descriptor or a
:class:`~pcapkit.protocols.protocol.Protocol` subclass

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If ``code`` is already
registered. The warning names the displaced entry and its
replacement, so a caller can tell *what* was lost rather than
only that something was.

Note:
The guard fires on the mere presence of ``code``, including when the
incumbent and the replacement denote the same protocol. That is
deliberate, and differs from :func:`register_protocol
<pcapkit.foundation.registry.protocols.register_protocol>`, which
additionally requires the incumbent to be a *different* class. Two
things separate them. This
registry is keyed on a ``code`` the caller supplies, independently of
the value, so registering one class under two codes yields two keys
and never reaches the same key twice -- the spurious-warning case
that motivated the narrower guard cannot arise here, while a repeat
call for one code is a caller mistake worth reporting even when the
value is unchanged. And the incumbent may still be an unresolved
:class:`~pcapkit.corekit.module.ModuleDescriptor` while the
replacement is the very class it names, so "a different class" is not
decidable here without resolving the descriptor -- forcing the import
that the descriptor exists to defer, purely to decide whether to
warn.

"""
if isinstance(protocol, ModuleDescriptor):
protocol = protocol.klass
if not issubclass(protocol, ProtocolBase):
raise RegistryError(f'protocol must be a Protocol subclass, not {protocol!r}')
if code in cls.__proto__:
warn(f'protocol {code} already registered, overwriting', RegistryWarning)
warn(f'protocol {code} already registered, overwriting '
f'{cls.__proto__[code]!r} with {protocol!r}', RegistryWarning)
cls.__proto__[code] = protocol

@classmethod
Expand Down
72 changes: 66 additions & 6 deletions pcapkit/protocols/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
from pcapkit.utilities.compat import Mapping
from pcapkit.utilities.decorators import prepare
from pcapkit.utilities.exceptions import NoDefaultValue, ProtocolUnbound, SchemaError, stacklevel
from pcapkit.utilities.warnings import SchemaWarning, UnknownFieldWarning, warn
from pcapkit.utilities.warnings import (RegistryWarning, SchemaWarning, UnknownFieldWarning,
warn)

if TYPE_CHECKING:
from collections import OrderedDict
Expand Down Expand Up @@ -1074,6 +1075,16 @@ def __init_subclass__(cls, /, code: 'Optional[_ET | Iterable[_ET]]' = None, *arg
:attr:`registry` mapping with the given ``code``. If ``code`` is
not given, the subclass will not be registered.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If any of ``code`` is
already registered, naming the displaced schema and its
replacement. This is the same guard :meth:`register` applies,
and it is here as well because a class declaration is the
*other* way into :attr:`__enum__` -- ``class MyOption(Option,
code=...)`` writes the registry without any call to
:meth:`register`, so guarding only the method would leave the
declaration path silently displacing a built-in schema.

Notes:
If :attr:`__enum__` is not yet defined at function call,
it will automatically be defined as a :class:`_EnumRegistry`
Expand Down Expand Up @@ -1104,11 +1115,18 @@ def __init_subclass__(cls, /, code: 'Optional[_ET | Iterable[_ET]]' = None, *arg
cls.__enum__ = _EnumRegistry(getattr(manual, 'default_factory', None), manual)

if code is not None:
if isinstance(code, collections.abc.Iterable):
for _code in code:
cls.__enum__[_code] = (cls) # type: ignore[index]
else:
cls.__enum__[code] = (cls) # type: ignore[index]
# One loop over both shapes, so the overwrite guard below is written
# once rather than once per branch. ``register`` cannot be delegated
# to here: :class:`pcapkit.protocols.schema.misc.pcapng.Option`
# overrides it with an incompatible signature, so ``cls.register``
# does not mean the same thing for every subclass.
codes = code if isinstance(code, collections.abc.Iterable) else (code,)
for _code in codes:
if _code in cls.__enum__:
incumbent = cls.__enum__[_code] # type: ignore[index]
warn(f'schema {_code} already registered, overwriting '
f'{incumbent!r} with {cls!r}', RegistryWarning)
cls.__enum__[_code] = (cls) # type: ignore[index]
super().__init_subclass__()

@classmethod
Expand All @@ -1119,5 +1137,47 @@ def register(cls, code: '_ET', schema: 'Type[Self]') -> 'None':
code: Enumetaion code.
schema: Enumetaion schema.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If ``code`` is already
registered, naming the displaced schema and its replacement.

Note:
Every public registrar in
:mod:`pcapkit.foundation.registry.protocols` that accepts a
``schema`` registers two halves of one binding -- a parser class
through e.g. :meth:`IPv4.register_option
<pcapkit.protocols.internet.ipv4.IPv4.register_option>`, and a
schema class through this method. The parser half has warned on an
overwrite for as long as it has existed; this half assigned bare, so
one ``register_ipv4_option`` call replacing a built-in reported the
parser it displaced and said nothing about the schema. The guard
here closes that asymmetry.

It fires on the mere presence of ``code``, as the code-keyed parser
registries do and unlike :func:`register_protocol
<pcapkit.foundation.registry.protocols.register_protocol>`, whose key
is derived from the value it stores. ``code`` here is supplied by the
caller and is independent of ``schema``, so a repeat is a caller
mistake worth reporting even when the value is unchanged.

Presence is a faithful "was this really registered" test only because
:class:`_EnumRegistry` returns a miss without recording it. A plain
:class:`collections.defaultdict` would have inserted
:attr:`__default__` the first time any unregistered ``code`` was
looked up, so parsing a single packet carrying an unknown code would
have made the next legitimate registration for that code warn about
an entry no caller ever asked for -- the defect fixed for this layer
in #555, and for the parser-layer ``__proto__`` family in #421 and
#425/#428. That fix is what makes this guard safe to add.

:class:`pcapkit.protocols.schema.misc.pcapng.Option` overrides this
method with a namespaced registry of its own and does not delegate
here, so it is guarded separately.

"""
if code in cls.__enum__:
incumbent = cls.__enum__[code] # type: ignore[index]
warn(f'schema {code} already registered, overwriting '
f'{incumbent!r} with {schema!r}', RegistryWarning)

cls.__enum__[code] = schema # type: ignore[index]
16 changes: 14 additions & 2 deletions pcapkit/protocols/transport/sctp.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,14 +609,26 @@ def register(cls, code: 'Enum_PayloadProtocolIdentifier | int', protocol: 'Modul
its :attr:`self.__proto__ <SCTP.__proto__>` registry is keyed by
PPID rather than by port number.

Raises:
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
:class:`~pcapkit.protocols.protocol.ProtocolBase` subclass.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If this PPID is already
registered, naming the displaced entry and its replacement so a
caller can tell *what* was lost. Fires on presence alone, as the
port-keyed :meth:`Transport.register
<pcapkit.protocols.transport.transport.Transport.register>` it
overrides does.

"""
if isinstance(protocol, ModuleDescriptor):
protocol = protocol.klass
if not issubclass(protocol, ProtocolBase):
raise RegistryError(f'protocol must be a Protocol subclass, not {protocol!r}')
if code in cls.__proto__:
warn(f'payload protocol identifier {code} already registered, overwriting',
RegistryWarning)
warn(f'payload protocol identifier {code} already registered, overwriting '
f'{cls.__proto__[code]!r} with {protocol!r}', RegistryWarning)
cls.__proto__[code] = protocol

@classmethod
Expand Down
23 changes: 22 additions & 1 deletion pcapkit/protocols/transport/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[Protocol] | Type[Prot
protocol map should be associated directly with specific
transport layer protocol type.

Raises:
pcapkit.utilities.exceptions.UnsupportedCall: If called on
:class:`Transport` itself.
pcapkit.utilities.exceptions.RegistryError: If ``protocol`` is not a
:class:`~pcapkit.protocols.protocol.Protocol` subclass.

Warns:
pcapkit.utilities.warnings.RegistryWarning: If this port is already
registered, naming the displaced entry and its replacement so a
caller can tell *what* was lost. Fires on presence alone -- see
:meth:`ProtocolBase.register
<pcapkit.protocols.protocol.ProtocolBase.register>` for why that
differs from ``register_protocol``.

Note:
``cls.__proto__`` belongs to the concrete protocol, not to
:class:`Transport`, so ``register_apptype`` reaching this method
twice for one call -- once as ``TCP``, once as ``UDP`` -- inspects
two different registries and cannot warn spuriously.

"""
if cls is Transport:
raise UnsupportedCall(f'{cls.__name__} is an abstract class')
Expand All @@ -94,7 +114,8 @@ def register(cls, code: 'int', protocol: 'ModuleDescriptor[Protocol] | Type[Prot
if not issubclass(protocol, Protocol):
raise RegistryError(f'protocol must be a Protocol subclass, not {protocol!r}')
if code in cls.__proto__:
warn(f'port {code} already registered, overwriting', RegistryWarning)
warn(f'port {code} already registered, overwriting '
f'{cls.__proto__[code]!r} with {protocol!r}', RegistryWarning)
cls.__proto__[code] = protocol

@classmethod
Expand Down
Loading
Loading