diff --git a/docs/source/pcapkit/const/pcapng.rst b/docs/source/pcapkit/const/pcapng.rst index c044996f22..7291f614c2 100644 --- a/docs/source/pcapkit/const/pcapng.rst +++ b/docs/source/pcapkit/const/pcapng.rst @@ -24,6 +24,8 @@ enumerations include: - Secrets Types [*]_ * - :class:`PCAPNG_FilterType ` - Filter Types [*]_ + * - :class:`PCAPNG_TLSKeyLabel ` + - TLS Key Log Labels [*]_ Block Types =========== @@ -116,6 +118,19 @@ which is automatically generated from :class:`pcapkit.vendor.pcapng.filter_type. :undoc-members: :show-inheritance: +TLS Key Log Labels +=================== + +.. module:: pcapkit.const.pcapng.tls_key_label + +This module contains the constant enumeration for **TLS Key Log Labels**, +which is automatically generated from :class:`pcapkit.vendor.pcapng.tls_key_label.TLSKeyLabel`. + +.. autoclass:: pcapkit.const.pcapng.tls_key_label.TLSKeyLabel + :members: + :undoc-members: + :show-inheritance: + .. raw:: html
@@ -127,3 +142,4 @@ which is automatically generated from :class:`pcapkit.vendor.pcapng.filter_type. .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-name-resolution-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-decryption-secrets-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-interface-description-block +.. [*] https://www.iana.org/assignments/tls-parameters/tls-sslkeylogfile-labels.csv, c.f., :rfc:`9850#section-4.2` diff --git a/docs/source/pcapkit/protocols/misc/pcapng.rst b/docs/source/pcapkit/protocols/misc/pcapng.rst index 07f1f69268..6b306d5360 100644 --- a/docs/source/pcapkit/protocols/misc/pcapng.rst +++ b/docs/source/pcapkit/protocols/misc/pcapng.rst @@ -197,10 +197,12 @@ Auxiliary Data :undoc-members: :show-inheritance: -.. autoclass:: pcapkit.protocols.misc.pcapng.TLSKeyLabel - :members: - :undoc-members: - :show-inheritance: +:class:`TLSKeyLabel ` moved to +:mod:`pcapkit.const.pcapng.tls_key_label` (GitHub issue #886), generated the +same way as its :mod:`pcapkit.const.pcapng` siblings since :rfc:`9850#section-4.2` +makes it an IANA registry rather than a hand-picked helper enum. This module +still re-exports it under its own name, so +``from pcapkit.protocols.misc.pcapng import TLSKeyLabel`` keeps working. .. autoclass:: pcapkit.protocols.misc.pcapng.WireGuardKeyLabel :members: diff --git a/docs/source/pcapkit/vendor/pcapng.rst b/docs/source/pcapkit/vendor/pcapng.rst index 110016f13a..6a6911367e 100644 --- a/docs/source/pcapkit/vendor/pcapng.rst +++ b/docs/source/pcapkit/vendor/pcapng.rst @@ -24,6 +24,8 @@ vendor crawlers include: - Secrets Types [*]_ * - :class:`PCAPNG_FilterType ` - Filter Types [*]_ + * - :class:`PCAPNG_TLSKeyLabel ` + - TLS Key Log Labels [*]_ .. note:: @@ -124,6 +126,18 @@ which is automatically generating :class:`pcapkit.const.pcapng.filter_type.Filte :members: FLAG :show-inheritance: +TLS Key Log Labels +=================== + +.. module:: pcapkit.vendor.pcapng.tls_key_label + +This module contains the vendor crawler for **TLS Key Log Labels**, +which is automatically generating :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`. + +.. autoclass:: pcapkit.vendor.pcapng.tls_key_label.TLSKeyLabel + :members: LINK + :show-inheritance: + .. rubric:: Footnotes .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-standardized-block-type-cod @@ -133,3 +147,4 @@ which is automatically generating :class:`pcapkit.const.pcapng.filter_type.Filte .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-name-resolution-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-decryption-secrets-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-interface-description-block +.. [*] https://www.iana.org/assignments/tls-parameters/tls-sslkeylogfile-labels.csv, c.f., :rfc:`9850#section-4.2` diff --git a/pcapkit/const/__init__.py b/pcapkit/const/__init__.py index 2361df9bab..ec0cb91049 100644 --- a/pcapkit/const/__init__.py +++ b/pcapkit/const/__init__.py @@ -83,5 +83,5 @@ # PCAPNG 'PCAPNG_BlockType', 'PCAPNG_OptionType', 'PCAPNG_HashAlgorithm', 'PCAPNG_VerdictType', 'PCAPNG_RecordType', 'PCAPNG_SecretsType', - 'PCAPNG_FilterType', + 'PCAPNG_FilterType', 'PCAPNG_TLSKeyLabel', ] diff --git a/pcapkit/const/pcapng/__init__.py b/pcapkit/const/pcapng/__init__.py index 3184eb874d..ec1ab275e5 100644 --- a/pcapkit/const/pcapng/__init__.py +++ b/pcapkit/const/pcapng/__init__.py @@ -25,6 +25,8 @@ - Secrets Types [*]_ * - :class:`PCAPNG_FilterType ` - Filter Types [*]_ + * - :class:`PCAPNG_TLSKeyLabel ` + - TLS Key Log Labels [*]_ .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-standardized-block-type-cod .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-options @@ -33,6 +35,7 @@ .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-name-resolution-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-decryption-secrets-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-interface-description-block +.. [*] https://www.iana.org/assignments/tls-parameters/tls-sslkeylogfile-labels.csv, c.f., :rfc:`9850#section-4.2` """ @@ -42,10 +45,11 @@ from pcapkit.const.pcapng.option_type import OptionType as PCAPNG_OptionType from pcapkit.const.pcapng.record_type import RecordType as PCAPNG_RecordType from pcapkit.const.pcapng.secrets_type import SecretsType as PCAPNG_SecretsType +from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as PCAPNG_TLSKeyLabel from pcapkit.const.pcapng.verdict_type import VerdictType as PCAPNG_VerdictType __all__ = [ 'PCAPNG_BlockType', 'PCAPNG_OptionType', 'PCAPNG_HashAlgorithm', 'PCAPNG_VerdictType', 'PCAPNG_RecordType', 'PCAPNG_SecretsType', - 'PCAPNG_FilterType', + 'PCAPNG_FilterType', 'PCAPNG_TLSKeyLabel', ] diff --git a/pcapkit/const/pcapng/tls_key_label.py b/pcapkit/const/pcapng/tls_key_label.py new file mode 100644 index 0000000000..f4826111ab --- /dev/null +++ b/pcapkit/const/pcapng/tls_key_label.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long,consider-using-f-string +"""TLS Key Log Labels +======================== + +.. module:: pcapkit.const.pcapng.tls_key_label + +This module contains the constant enumeration for **TLS Key Log Labels**, +which is automatically generated from :class:`pcapkit.vendor.pcapng.tls_key_label.TLSKeyLabel`. + +""" + +from aenum import StrEnum + +from pcapkit.corekit.enum import EnumRegistry + +__all__ = ['TLSKeyLabel'] + + +class TLSKeyLabel(EnumRegistry, StrEnum): + """[TLSKeyLabel] TLS Key Log Labels""" + + #: NSS-historical: not a registered label of :rfc:`9850#section-4.2`'s "TLS + #: SSLKEYLOGFILE Labels" registry. Defined by the Mozilla NSS + #: ``SSLKEYLOGFILE`` convention and removed in NSS 3.34; kept here only so + #: that key logs predating :rfc:`9850` still read. + RSA = 'RSA' + + #: Master secret in TLS 1.2 and earlier, c.f., :rfc:`9850#section-4.2`. + CLIENT_RANDOM = 'CLIENT_RANDOM' + + #: Secret for client early data records, c.f., :rfc:`9850#section-4.2`. + CLIENT_EARLY_TRAFFIC_SECRET = 'CLIENT_EARLY_TRAFFIC_SECRET' # nosec B105 + + #: Early exporter secret, c.f., :rfc:`9850#section-4.2`. + EARLY_EXPORTER_SECRET = 'EARLY_EXPORTER_SECRET' # nosec B105 + + #: Secret protecting client handshake, c.f., :rfc:`9850#section-4.2`. + CLIENT_HANDSHAKE_TRAFFIC_SECRET = 'CLIENT_HANDSHAKE_TRAFFIC_SECRET' # nosec B105 + + #: Secret protecting server handshake, c.f., :rfc:`9850#section-4.2`. + SERVER_HANDSHAKE_TRAFFIC_SECRET = 'SERVER_HANDSHAKE_TRAFFIC_SECRET' # nosec B105 + + #: Secret protecting client records post handshake, c.f., + #: :rfc:`9850#section-4.2`. + CLIENT_TRAFFIC_SECRET_0 = 'CLIENT_TRAFFIC_SECRET_0' # nosec B105 + + #: Secret protecting server records post handshake, c.f., + #: :rfc:`9850#section-4.2`. + SERVER_TRAFFIC_SECRET_0 = 'SERVER_TRAFFIC_SECRET_0' # nosec B105 + + #: Exporter secret after handshake, c.f., :rfc:`9850#section-4.2`. + EXPORTER_SECRET = 'EXPORTER_SECRET' # nosec B105 + + #: HPKE KEM shared secret used in the ECH, c.f., :rfc:`9850#section-4.2`. + ECH_SECRET = 'ECH_SECRET' # nosec B105 + + #: ECHConfig used for construction of the ECH, c.f., :rfc:`9850#section-4.2`. + ECH_CONFIG = 'ECH_CONFIG' diff --git a/pcapkit/foundation/reassembly/data/data.py b/pcapkit/foundation/reassembly/data/data.py index 48d9a0f82d..9733ca960d 100644 --- a/pcapkit/foundation/reassembly/data/data.py +++ b/pcapkit/foundation/reassembly/data/data.py @@ -38,13 +38,23 @@ class Completion(StrEnum): against a member instead. It derives from :class:`~pcapkit.utilities.compat.StrEnum`, as - :class:`~pcapkit.protocols.application.httpv1.Type` and - :class:`~pcapkit.protocols.misc.pcapng.TLSKeyLabel` do, which buys two things - a plain :class:`enum.Enum` does not: the value survives + :class:`~pcapkit.protocols.application.httpv1.Type` does, which buys two + things a plain :class:`enum.Enum` does not: the value survives :func:`json.dumps` -- a plain enumeration raises :exc:`TypeError` there, and :meth:`Datagram.to_dict ` hands this field straight out -- and ``datagram.completed == 'timeout'`` works, so the new state can be tested for without importing this class. + :class:`~pcapkit.protocols.misc.pcapng.TLSKeyLabel` used to be a third + precedent for the same :class:`~pcapkit.utilities.compat.StrEnum` base, but + GitHub issue #886 moved its canonical definition to + :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`, generated like its + :mod:`pcapkit.const.pcapng` siblings: it now derives from :class:`aenum`'s + own ``StrEnum`` (via :class:`~pcapkit.corekit.enum.EnumRegistry`) rather than + from :mod:`pcapkit.utilities.compat`'s version-branched one. Both + properties above still hold for it either way -- ``aenum.StrEnum`` is a + :class:`str` subclass same as the stdlib one -- but ``isinstance``/ + ``issubclass`` against :class:`pcapkit.utilities.compat.StrEnum` no longer + does, which is why it is called out here rather than left silently stale. Warning: Being a :class:`str` whose :attr:`PARTIAL` and :attr:`TIMEOUT` members are diff --git a/pcapkit/protocols/misc/pcapng.py b/pcapkit/protocols/misc/pcapng.py index 70c7200f06..4d0a0fb171 100644 --- a/pcapkit/protocols/misc/pcapng.py +++ b/pcapkit/protocols/misc/pcapng.py @@ -35,6 +35,7 @@ from pcapkit.const.pcapng.option_type import OptionType as Enum_OptionType from pcapkit.const.pcapng.record_type import RecordType as Enum_RecordType from pcapkit.const.pcapng.secrets_type import SecretsType as Enum_SecretsType +from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as Enum_TLSKeyLabel from pcapkit.const.pcapng.verdict_type import VerdictType as Enum_VerdictType from pcapkit.const.reg.linktype import LinkType as Enum_LinkType from pcapkit.corekit.module import ModuleDescriptor @@ -270,26 +271,27 @@ class PacketReception(enum.IntEnum): PROMISCUOUS = 0b100 -class TLSKeyLabel(StrEnum): - """TLS key log label, c.f., :rfc:`9850`.""" - - #: NSS-historical: not a registered label of :rfc:`9850#section-4.2`'s "TLS - #: SSLKEYLOGFILE Labels" registry. Defined by the Mozilla NSS - #: ``SSLKEYLOGFILE`` convention and removed in NSS 3.34; kept here only so - #: that key logs predating :rfc:`9850` still read. - RSA = 'RSA' - CLIENT_RANDOM = 'CLIENT_RANDOM' - CLIENT_EARLY_TRAFFIC_SECRET = 'CLIENT_EARLY_TRAFFIC_SECRET' # nosec B105 - CLIENT_HANDSHAKE_TRAFFIC_SECRET = 'CLIENT_HANDSHAKE_TRAFFIC_SECRET' # nosec B105 - SERVER_HANDSHAKE_TRAFFIC_SECRET = 'SERVER_HANDSHAKE_TRAFFIC_SECRET' # nosec B105 - CLIENT_TRAFFIC_SECRET_0 = 'CLIENT_TRAFFIC_SECRET_0' # nosec B105 - SERVER_TRAFFIC_SECRET_0 = 'SERVER_TRAFFIC_SECRET_0' # nosec B105 - EARLY_EXPORTER_SECRET = 'EARLY_EXPORTER_SECRET' # nosec B105 - EXPORTER_SECRET = 'EXPORTER_SECRET' # nosec B105 - #: HPKE KEM shared secret used in the ECH, c.f., :rfc:`9850#section-4.2`. - ECH_SECRET = 'ECH_SECRET' # nosec B105 - #: ECHConfig used for construction of the ECH, c.f., :rfc:`9850#section-4.2`. - ECH_CONFIG = 'ECH_CONFIG' +# NOTE: TLSKeyLabel used to be hand-written here. GitHub issue #886: RFC 9850 +# :rfc:`9850#section-4.2` makes its values an IANA registry ("TLS +# SSLKEYLOGFILE Labels", Specification Required) rather than a closed, +# hand-picked helper enum, so it moved to +# :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`, generated the same +# way as its :mod:`pcapkit.const.pcapng` siblings -- imported above as +# ``Enum_TLSKeyLabel``, matching the ``Enum_*`` alias every one of its seven +# :mod:`pcapkit.const.pcapng` siblings already carries in this file (the +# owner's review on #890: this import was the only one of the eight lacking +# it, because the class used to be *defined* here rather than imported, so +# nothing applied the convention until this move made it an import). The +# assignment below re-exports the same object under the module's own, +# unaliased name, so ``from pcapkit.protocols.misc.pcapng import +# TLSKeyLabel`` keeps working and still resolves to the identical class -- +# not a copy -- that every internal ``Enum_TLSKeyLabel`` reference below uses. +# :class:`WireGuardKeyLabel` below stays hand-written -- it is verified closed +# (draft-ietf-opsawg-pcapng-06 section 4.7's "is one of" four names) and not a +# registry, so filing it under :mod:`pcapkit.const` would misrepresent it as +# one; the ``Enum_*`` alias convention is about imports of const enums, and +# this is a local definition, so it does not apply here. +TLSKeyLabel = Enum_TLSKeyLabel class WireGuardKeyLabel(StrEnum): @@ -5753,7 +5755,7 @@ def _make_secrets_unknown(self, type: 'Enum_SecretsType', secrets: 'Optional[Dat ) def _make_secrets_tls(self, type: 'Enum_SecretsType', secrets: 'Optional[Data_TLSKeyLog]' = None, *, - entries: 'Optional[dict[TLSKeyLabel, OrderedMultiDict[bytes, bytes]]]' = None, + entries: 'Optional[dict[Enum_TLSKeyLabel, OrderedMultiDict[bytes, bytes]]]' = None, **kwargs: 'Any') -> 'Schema_TLSKeyLog': """Make PCAP-NG TLS Key Log secrets. diff --git a/pcapkit/vendor/__init__.py b/pcapkit/vendor/__init__.py index bedd669b2c..8eae956d09 100644 --- a/pcapkit/vendor/__init__.py +++ b/pcapkit/vendor/__init__.py @@ -109,5 +109,5 @@ # PCAPNG 'PCAPNG_BlockType', 'PCAPNG_OptionType', 'PCAPNG_HashAlgorithm', 'PCAPNG_VerdictType', 'PCAPNG_RecordType', 'PCAPNG_SecretsType', - 'PCAPNG_FilterType', + 'PCAPNG_FilterType', 'PCAPNG_TLSKeyLabel', ] diff --git a/pcapkit/vendor/pcapng/__init__.py b/pcapkit/vendor/pcapng/__init__.py index 6d628e00bd..020a12132c 100644 --- a/pcapkit/vendor/pcapng/__init__.py +++ b/pcapkit/vendor/pcapng/__init__.py @@ -25,6 +25,8 @@ - Secrets Types [*]_ * - :class:`PCAPNG_FilterType ` - Filter Types [*]_ + * - :class:`PCAPNG_TLSKeyLabel ` + - TLS Key Log Labels [*]_ .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-standardized-block-type-cod .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-options @@ -33,6 +35,7 @@ .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-name-resolution-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-decryption-secrets-block .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-interface-description-block +.. [*] https://www.iana.org/assignments/tls-parameters/tls-sslkeylogfile-labels.csv, c.f., :rfc:`9850#section-4.2` """ @@ -42,10 +45,11 @@ from pcapkit.vendor.pcapng.option_type import OptionType as PCAPNG_OptionType from pcapkit.vendor.pcapng.record_type import RecordType as PCAPNG_RecordType from pcapkit.vendor.pcapng.secrets_type import SecretsType as PCAPNG_SecretsType +from pcapkit.vendor.pcapng.tls_key_label import TLSKeyLabel as PCAPNG_TLSKeyLabel from pcapkit.vendor.pcapng.verdict_type import VerdictType as PCAPNG_VerdictType __all__ = [ 'PCAPNG_BlockType', 'PCAPNG_OptionType', 'PCAPNG_HashAlgorithm', 'PCAPNG_VerdictType', 'PCAPNG_RecordType', 'PCAPNG_SecretsType', - 'PCAPNG_FilterType', + 'PCAPNG_FilterType', 'PCAPNG_TLSKeyLabel', ] diff --git a/pcapkit/vendor/pcapng/tls_key_label.py b/pcapkit/vendor/pcapng/tls_key_label.py new file mode 100644 index 0000000000..3f550dac2a --- /dev/null +++ b/pcapkit/vendor/pcapng/tls_key_label.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +"""TLS Key Log Labels +======================== + +.. module:: pcapkit.vendor.pcapng.tls_key_label + +This module contains the vendor crawler for **TLS Key Log Labels**, +which is automatically generating :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`. + +""" + +import collections +import csv +import re +import sys +from typing import TYPE_CHECKING + +from pcapkit.vendor.default import Vendor + +if TYPE_CHECKING: + from collections import Counter + from typing import Callable + +__all__ = ['TLSKeyLabel'] + +#: Hand-carried comment for the ``RSA`` member, copied verbatim from the text +#: GitHub issue #883 gave it in +#: :class:`pcapkit.protocols.misc.pcapng.TLSKeyLabel` -- ``RSA`` has no row in +#: :attr:`TLSKeyLabel.LINK`'s registry to generate a comment from (``grep -c +#: RSA`` on the fetched CSV is 0), so it is carried across rather than +#: derived, per GitHub issue #886's first constraint. The embedded +#: ``\n #: `` continuations match every other multi-line member comment +#: this crawler (and its siblings) emit, so the rendered class body indents +#: correctly once :meth:`TLSKeyLabel.context` joins it in. +RSA_COMMENT = ('NSS-historical: not a registered label of :rfc:`9850#section-4.2`\'s "TLS\n' + ' #: SSLKEYLOGFILE Labels" registry. Defined by the Mozilla NSS\n' + ' #: ``SSLKEYLOGFILE`` convention and removed in NSS 3.34; kept here only so\n' + ' #: that key logs predating :rfc:`9850` still read.') + +LINE = lambda NAME, DOCS, ENUM, MODL: f'''\ +# -*- coding: utf-8 -*- +# pylint: disable=line-too-long,consider-using-f-string +"""{(name := DOCS.split(' [', maxsplit=1)[0])} +{'=' * (len(name) + 6)} + +.. module:: {MODL.replace('vendor', 'const')} + +This module contains the constant enumeration for **{name}**, +which is automatically generated from :class:`{MODL}.{NAME}`. + +""" + +from aenum import StrEnum + +from pcapkit.corekit.enum import EnumRegistry + +__all__ = ['{NAME}'] + + +class {NAME}(EnumRegistry, StrEnum): + """[{NAME}] {DOCS}""" + + {ENUM} +''' # type: Callable[[str, str, str, str], str] + + +class TLSKeyLabel(Vendor): + """TLS Key Log Labels""" + + #: Link to registry. + LINK = 'https://www.iana.org/assignments/tls-parameters/tls-sslkeylogfile-labels.csv' + + def count(self, data: 'list[str]') -> 'Counter[str]': + """Count field records. + + Args: + data: CSV data. + + Returns: + Field recordings. + + """ + reader = csv.reader(data) + next(reader) # header + return collections.Counter(map(lambda item: self.safe_name(item[0]), reader)) + + def process(self, data: 'list[str]') -> 'tuple[list[str], list[str]]': + """Process CSV data. + + Args: + data: CSV data. + + Returns: + Enumeration fields and missing fields. ``RSA`` is prepended by + hand -- see :data:`RSA_COMMENT` -- since it has no row in the + registry itself. The second element is always empty: this + registry does not mint an ``_missing_`` (see + :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`'s own + docstring for why), so there is nothing to render there. + + """ + enum = [ + f"#: {RSA_COMMENT}\n RSA = 'RSA'", + ] # type: list[str] + + reader = csv.reader(data) + next(reader) # header + for item in reader: + label, desc, ref = item[0].strip(), item[1].strip(), item[2].strip() + + # NOTE: fails loudly rather than guessing. A silent fallback to + # '9850' here would mis-cite a future row referenced by anything + # other than a bare ``[RFCnnnn]``, and blindly appending + # ``#section-4.2`` below to whatever number *did* match would + # mis-cite a row citing a different RFC entirely -- section 4.2 is + # specifically RFC 9850's own "TLS SSLKEYLOGFILE Labels" registry + # section, not a fact about any other RFC's structure. All ten + # rows fetched as of 2026-09-28 are ``[RFC9850]``, so this always + # takes the ``rfc == '9850'`` branch today; a future registry + # change either still fits it or raises here for a human to look + # at, rather than rendering a citation nobody checked. + match = re.match(r'\[RFC(\d+)\]\Z', ref) + if match is None: + raise ValueError( + f'{label!r} cites {ref!r}, which this crawler does not know how to ' + f'render as an RFC citation -- extend the parsing rather than letting ' + f'it guess') + rfc = match.group(1) + section = '#section-4.2' if rfc == '9850' else '' + + renm = self.safe_name(label) + sufs = self.wrap_comment(f'{desc}, c.f., :rfc:`{rfc}{section}`.') + + pref = f"{renm} = '{label}'" + # NOTE: mirrors the ``# nosec B105`` bandit suppression every + # such member already carries in + # :class:`pcapkit.protocols.misc.pcapng.TLSKeyLabel` as of #883 -- + # bandit's B105 (hardcoded password string) flags a string + # constant assigned to a name containing ``SECRET``, which eight + # of these ten labels' names do. + if 'SECRET' in label: + pref += ' # nosec B105' + + enum.append(f'#: {sufs}\n {pref}') + return enum, [] + + def context(self, data: 'list[str]') -> 'str': + """Generate constant context. + + Args: + data: CSV data. + + Returns: + Constant context. + + """ + enum, _ = self.process(data) + ENUM = '\n\n '.join(map(lambda s: s.rstrip(), enum)).strip() + return LINE(self.NAME, self.DOCS, ENUM, self.__module__) + + +if __name__ == '__main__': + sys.exit(TLSKeyLabel()) # type: ignore[arg-type] diff --git a/tests/const/test_const_enum_builtin_parity.py b/tests/const/test_const_enum_builtin_parity.py index f58012fd34..b7a374469a 100644 --- a/tests/const/test_const_enum_builtin_parity.py +++ b/tests/const/test_const_enum_builtin_parity.py @@ -366,16 +366,23 @@ def test_the_sweep_size_is_pinned(self) -> None: # dropping its ``IntFlag`` base, one for one, so the sum each of those # counts on stays the same even though the two addends moved. # - # Five of the nine string registries are the application layer one, which + # Five of the ten string registries are the application layer one, which # GitHub issue #732 split into a package: the memberless # pcapkit.const.reg.apptype.apptype.AppType base plus one registry per # transport protocol. It is discovered exactly like a member-bearing # registry, since this sweep is structural and never looks at members. + # The tenth is GitHub issue #886's + # pcapkit.const.pcapng.tls_key_label.TLSKeyLabel, moved here from a + # hand-written class under pcapkit.protocols.misc.pcapng -- it defines + # no ``_missing_`` of its own, so it is not among the three ``value. + # upper()`` outliers #647 fixed above, and it never reached + # tests.const.test_const_enum_get's narrower (IntEnum, IntFlag)-only + # sweep, so that module's own pinned count does not move. self.assertEqual(len(ints), 112) self.assertEqual(len(flags), 6) - self.assertEqual(len(strs), 9) - self.assertEqual(len(self.enums), 127) - self.assertEqual(len({obj.__module__ for obj in self.enums}), 121) + self.assertEqual(len(strs), 10) + self.assertEqual(len(self.enums), 128) + self.assertEqual(len({obj.__module__ for obj in self.enums}), 122) def test_every_registry_rejects_a_negative_value(self) -> None: """The registry-wide form of GitHub issue #647. diff --git a/tests/const/test_const_registry_protocol.py b/tests/const/test_const_registry_protocol.py index ab3c3c0eba..ebca561105 100644 --- a/tests/const/test_const_registry_protocol.py +++ b/tests/const/test_const_registry_protocol.py @@ -721,12 +721,32 @@ class _Str(EnumRegistry, StrEnum): #: :class:`~pcapkit.corekit.enum.EnumRegistry` at all, by the owner's own #: ruling against extending it (GitHub PR #836) -- there is no registry #: protocol here to inherit or shadow. +#: +#: ``pcapkit/const/pcapng/tls_key_label.py`` (GitHub issue #886) is excluded +#: for a different reason than the ten above, worth spelling out since it is +#: not "carries a bespoke get()/register()" -- it does not; its own +#: ``get``/``get_all``/``register``/``register_alias`` are exactly the +#: base's generic implementation, same as every one of the 105 +#: :data:`GENERATED_SAMPLE`-shaped registries below. What excludes it is +#: narrower: this whole census, and its ``class \w+(EnumRegistry, IntEnum):`` +#: regex a few lines down, is scoped to the :class:`~aenum.IntEnum` batch +#: tier 3 of #775 converted. ``TLSKeyLabel`` is :class:`~aenum.StrEnum`-valued +#: -- RFC 9850's labels are strings, matched by value -- and it was generated +#: after that batch closed, so it was never a candidate for it either way. +#: Excluding it here is not a judgement about its own inherited-protocol +#: cleanliness; :mod:`tests.const.test_const_enum_get`, +#: :mod:`tests.const.test_const_enum_lookup` and +#: :mod:`tests.const.test_const_enum_builtin_parity`'s dynamic +#: ``pkgutil.walk_packages`` sweeps already discover and cover it generically +#: -- it is simply outside what this file's historical, IntEnum-only census +#: counts. EXCLUDED_STILL_BESPOKE = frozenset({ 'pcapkit/const/ftp/command.py', 'pcapkit/const/ftp/return_code.py', 'pcapkit/const/http/method.py', 'pcapkit/const/http/status_code.py', 'pcapkit/const/pcapng/option_type.py', + 'pcapkit/const/pcapng/tls_key_label.py', 'pcapkit/const/reg/apptype/apptype.py', 'pcapkit/const/reg/apptype/dccp.py', 'pcapkit/const/reg/apptype/sctp.py', @@ -762,9 +782,9 @@ def test_generated_sample_carries_no_copy_of_the_protocol(self) -> None: self.assertIn("def _missing_(cls, value: 'int')", source) def test_every_generated_const_module_is_accounted_for(self) -> None: - """The full census, measured on this batch rather than assumed: 121 + """The full census, measured on this batch rather than assumed: 122 modules under :mod:`pcapkit.const`, splitting exactly three ways -- - the 6 :data:`CONVERTED` bespoke ones tier 2 already handled, the 10 + the 6 :data:`CONVERTED` bespoke ones tier 2 already handled, the 11 :data:`EXCLUDED_STILL_BESPOKE` deliberately left alone, and the remaining 105 this tier converts. Measured by me on the prior head (commit ``05468a06b``, this batch's own base): of those 121, 6 carried @@ -773,6 +793,13 @@ def test_every_generated_const_module_is_accounted_for(self) -> None: the 6 bespoke StrEnum/AppType files, since a hand-copied docstring is not proof of a shared template) -- 105 is what is left once the 6 converted and the 10 excluded are both taken out. + + 121 became 122 with GitHub issue #886's + ``pcapkit/const/pcapng/tls_key_label.py``, and the 10 excluded became + 11 to hold it -- see the comment directly above + :data:`EXCLUDED_STILL_BESPOKE` for why it lands there rather than + among the 105. 105 itself does not move: the new file is absorbed by + the excluded side of the split, not the generated side. """ const_root = REPO_ROOT / 'pcapkit' / 'const' all_files = sorted( @@ -784,7 +811,7 @@ def test_every_generated_const_module_is_accounted_for(self) -> None: generated = [path for path in all_files if path not in converted_relpaths and path not in EXCLUDED_STILL_BESPOKE] - self.assertEqual(len(all_files), 121) + self.assertEqual(len(all_files), 122) self.assertEqual(len(generated), 105) for relpath in generated: diff --git a/tests/protocols/misc/test_pcapng_unit.py b/tests/protocols/misc/test_pcapng_unit.py index fb2fed2839..8847130e69 100644 --- a/tests/protocols/misc/test_pcapng_unit.py +++ b/tests/protocols/misc/test_pcapng_unit.py @@ -1259,10 +1259,20 @@ def test_pcapng_record_and_secrets_readers_cover_dispatch(self) -> None: self.assertEqual(PCAPNG.__dict__['__secrets__'][SecretsType.TLS_Key_Log], 'tls') def test_tls_key_label_tracks_rfc_9850_plus_documented_rsa_exception(self) -> None: + # GitHub issue #886: the canonical definition moved from a hand-written + # class in this module to a generated one under + # :mod:`pcapkit.const.pcapng.tls_key_label`, with a re-export left here + # so ``from pcapkit.protocols.misc.pcapng import TLSKeyLabel`` keeps + # working. Both names have to resolve to the *same* object, not merely + # to two classes that happen to agree. + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as CanonicalTLSKeyLabel from pcapkit.protocols.misc.pcapng import TLSKeyLabel - # RFC 9850 :rfc:`9850#section-4.2`'s "TLS SSLKEYLOGFILE Labels" registry, - # plus the NSS-historical ``RSA`` label this module documents and retains. + self.assertIs(TLSKeyLabel, CanonicalTLSKeyLabel) + + # RFC 9850 :rfc:`9850#section-4.2`'s "TLS SSLKEYLOGFILE Labels" registry + # (10 rows as of 2026-09-28), plus the NSS-historical ``RSA`` label this + # module documents and retains -- it has no row of its own. expected = { 'RSA', 'CLIENT_RANDOM', @@ -1277,11 +1287,30 @@ def test_tls_key_label_tracks_rfc_9850_plus_documented_rsa_exception(self) -> No 'ECH_CONFIG', } self.assertEqual(set(TLSKeyLabel.__members__), expected) + self.assertEqual(len(TLSKeyLabel.__members__), 11) + + # Every member's value pinned exactly -- this is a value-matched + # registry (``TLSKeyLabel(label.upper())`` at + # pcapkit/protocols/schema/misc/pcapng.py:2108), so the value, not + # merely the name, is what callers depend on. + for name in expected: + with self.subTest(name=name): + member = TLSKeyLabel[name] + self.assertEqual(member.value, name) + self.assertIs(TLSKeyLabel(name), member) self.assertIs(TLSKeyLabel('ECH_SECRET'), TLSKeyLabel.ECH_SECRET) self.assertIs(TLSKeyLabel('ECH_CONFIG'), TLSKeyLabel.ECH_CONFIG) self.assertIs(TLSKeyLabel('RSA'), TLSKeyLabel.RSA) + # Matching stays by value, not name -- lower-case never resolves, and + # an unrecognised label still raises rather than minting, unchanged + # from before this class was generated. + with self.assertRaises(ValueError): + TLSKeyLabel('client_random') + with self.assertRaises(ValueError): + TLSKeyLabel('NOT_A_REGISTERED_LABEL') + def test_pcapng_schema_helpers_and_post_process_branches(self) -> None: from pcapkit.const.pcapng.block_type import BlockType from pcapkit.const.pcapng.option_type import OptionType