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
52 changes: 42 additions & 10 deletions docs/source/contributing/conventions/const-vendor-pairing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,47 @@ module docstring and comments, alongside the ``DATA`` literal it documents; the
generated ``const`` module carries only the plain, templated docstring every
other generated module does.

Known Violation
~~~~~~~~~~~~~~~
``LINK``-and-Scrape Pairing: TLS Content Types and HandshakeType
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:class:`~pcapkit.const.tls.content_type.ContentType` and
:class:`~pcapkit.const.tls.handshake_type.HandshakeType` (:pr:`2441`) both cite a live
IANA registry and claim to name every value IANA assigns, with no
:mod:`pcapkit.vendor` crawler behind either one -- no ``pcapkit/vendor/tls/``
directory exists, and neither class appears in :data:`pcapkit.vendor.__all__`. This
was flagged in that PR's own review and merged anyway on the stated reasoning that the
crawler would follow later. Tracked as :issue:`2495`. Unlike ARPHRD, TLS's source
*is* a live, web-published IANA table, so its eventual crawler takes the normal
``LINK``-and-scrape shape, not the ``DATA`` attribute.
:class:`~pcapkit.const.tls.handshake_type.HandshakeType` (:pr:`2441`) now have
matching crawlers -- :mod:`pcapkit.vendor.tls.content_type` and
:mod:`pcapkit.vendor.tls.handshake_type`, exported as ``TLS_ContentType`` and
``TLS_HandshakeType`` -- closing the gap :issue:`2495` tracked. Both take the
normal ``LINK``-and-scrape shape against IANA's ``tls-parameters-5.csv`` and
``tls-parameters-7.csv``, as this doc said the eventual crawler would; unlike
ARPHRD, TLS's source is a live, web-published table, so neither reads a
``DATA`` literal.

Running either crawler is a true no-op against the committed ``const``
modules: regenerating both reproduces the tree byte-for-byte, the same
guarantee ARPHRD and the ``sslv2`` restack carry. Getting there meant
regenerating the ``const`` modules committed alongside :pr:`2441`, which
predated the crawler and had drifted from it in three ways, caught in
cross-review of :pr:`2503`:

* the committed ``ContentType`` resolved values 32-63 to ``Unassigned`` via a
flat ``_missing_`` fallback, while IANA's registry -- and the crawler --
call that band ``Reserved`` (:rfc:`9147`); the committed docstring already
said "Reserved", the code did not. Regenerating fixes the resolved name for
that band.
* the committed ``ContentType`` carried an ``invalid = 0`` sentinel with no
IANA counterpart and no reference anywhere in the codebase (IANA leaves 0
inside its 0-19 ``Unassigned`` band, unnamed); it is dropped, and value 0
now resolves to ``Unassigned`` like the rest of that band.
:class:`~pcapkit.protocols.application.ssl.SSL` never checked this member
by name -- its wire-type allowlist already excluded value 0 by construction
-- so the sentinel's removal is behaviourally a no-op there too.
* the committed ``HandshakeType`` cited RFC 5246, 6066 and 4680 for several
members where IANA's registry now cites :rfc:`9846`, and left IANA's two
single-row ``Unassigned`` entries (values 7 and 255) to ``_missing_``
rather than naming them; the crawler renders both as explicit members
(``unassigned_7``, ``unassigned_255``), matching the registry.

The protocol-specific prose the hand-curated modules carried -- the
record-layer header citations, the Unassigned/Reserved band rationale, the
``_RESERVED``-suffix handling -- moved into the crawlers' own module
docstrings and comments, the way :mod:`pcapkit.vendor.linux.arphrd` already
does for ARPHRD; the generated ``const`` modules carry only the plain,
templated docstring every other generated module does.
61 changes: 33 additions & 28 deletions pcapkit/const/tls/content_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,7 @@
.. module:: pcapkit.const.tls.content_type

This module contains the constant enumeration for **TLS/DTLS Content Types**,
as assigned by IANA in the ``TLS ContentType`` registry [*]_. The record-layer
header -- :rfc:`9846#section-5.1` for TLS, :rfc:`9147#section-4`'s
``DTLSPlaintext`` for DTLS -- opens with this type in both.

Every value IANA assigns is named below. Everything else in the 0-255 range it
lists as Unassigned (0-19 and 28-31, plus 64-255, which :rfc:`9443` reserves
for multiplexing with other protocols on the same port) or as Reserved (32-63,
:rfc:`9147`), and :meth:`ContentType._missing_` resolves those to an
``Unassigned`` member without growing the registry.

.. [*] https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-5
which is automatically generated from :class:`pcapkit.vendor.tls.content_type.ContentType`.

"""

Expand All @@ -36,45 +26,47 @@
class ContentType(EnumRegistry, IntEnum):
"""[ContentType] TLS/DTLS Content Types"""

#: Invalid -- a sentinel :rfc:`9846#section-5.1` names so that a record
#: carrying it can be rejected, never a value sent on the wire; IANA keeps
#: 0 inside its Unassigned 0-19 band rather than assigning it [:rfc:`9846`]
invalid = 0

#: ChangeCipherSpec [:rfc:`9846`]
#: change_cipher_spec [:rfc:`9846`]
change_cipher_spec = 20

#: Alert [:rfc:`9846`]
#: alert [:rfc:`9846`]
alert = 21

#: Handshake [:rfc:`9846`]
#: handshake [:rfc:`9846`]
handshake = 22

#: Application Data [:rfc:`9846`]
#: application_data [:rfc:`9846`]
application_data = 23

#: Heartbeat [:rfc:`6520`]
#: heartbeat [:rfc:`6520`]
heartbeat = 24

#: ``tls12_cid`` [:rfc:`9146`]
#: tls12_cid [:rfc:`9146`]
tls12_cid = 25

#: ACK -- DTLS 1.3 only [:rfc:`9147`]
#: ACK [:rfc:`9147`]
ack = 26

#: ``return_routability_check`` -- DTLS connection-ID path validation
#: [:rfc:`9853`]
#: return_routability_check [:rfc:`9853`]
return_routability_check = 27

@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:`~pcapkit.corekit.enum.EnumRegistry.register` refuses exactly the
values :meth:`~pcapkit.corekit.enum.EnumLookup.get` refuses, in the
same words (:issue:`1816`).

Args:
value: Value about to be looked up or minted.

Raises:
EnumValueError: If ``value`` fails that check.
EnumValueError: If ``value`` fails that check. A
:exc:`ValueError`, so an ``except ValueError`` caller is
unaffected.

"""
if not (isinstance(value, int) and 0 <= value <= 255):
Expand All @@ -90,5 +82,18 @@ def _missing_(cls, value: 'int') -> 'ContentType':
"""
if not (isinstance(value, int) and 0 <= value <= 255):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
#: Unassigned
return cls._unregistered_member(value, 'Unassigned')
if 0 <= value <= 19:
#: Unassigned (Requires coordination; see [RFC 9443])
#: [:rfc:`5764`][:rfc:`9443`]
return cls._unregistered_member(value, 'Unassigned')
if 28 <= value <= 31:
#: Unassigned
return cls._unregistered_member(value, 'Unassigned')
if 32 <= value <= 63:
#: Reserved [:rfc:`9147`]
return cls._unregistered_member(value, 'Reserved')
if 64 <= value <= 255:
#: Unassigned (Requires coordination; see [RFC 9443])
#: [:rfc:`5764`][:rfc:`9443`]
return cls._unregistered_member(value, 'Unassigned')
return super()._missing_(value)
100 changes: 54 additions & 46 deletions pcapkit/const/tls/handshake_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,7 @@
.. module:: pcapkit.const.tls.handshake_type

This module contains the constant enumeration for **TLS/DTLS HandshakeType**,
as assigned by IANA in the ``TLS HandshakeType`` registry [*]_, including the
DTLS-only connection-ID messages :rfc:`9147#section-5.2` adds to that same
registry. Every value IANA assigns is named below; the ranges it lists as
Unassigned fall through to :meth:`_missing_` like any other unassigned value.

Each member's name is the message name the protocol uses on the wire. IANA
suffixes ``_RESERVED`` onto the ones TLS 1.3 retired -- it keeps the code point
reserved rather than reassigning it -- which is a statement about the registry
rather than a different message name, so the suffix is not carried into the
member name and each such member instead records IANA's own comment on why the
assignment is retired.

.. [*] https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-7
which is automatically generated from :class:`pcapkit.vendor.tls.handshake_type.HandshakeType`.

"""

Expand All @@ -38,95 +26,110 @@
class HandshakeType(EnumRegistry, IntEnum):
"""[HandshakeType] TLS/DTLS HandshakeType"""

#: ``hello_request`` -- TLS 1.2 and earlier only [:rfc:`5246`]
#: hello_request [:rfc:`9846`] -- Used in TLS versions prior to 1.3.
hello_request = 0

#: ``client_hello`` [:rfc:`9846`]
#: client_hello [:rfc:`9846`]
client_hello = 1

#: ``server_hello`` [:rfc:`9846`]
#: server_hello [:rfc:`9846`]
server_hello = 2

#: ``hello_verify_request`` -- DTLS 1.2 and earlier only; DTLS 1.3 moved
#: its return-routability role to the ``cookie`` extension
#: (:rfc:`9147#section-5.1`) [:rfc:`6347`]
#: hello_verify_request [:rfc:`6347`][:rfc:`9846`] -- Assigned for interim
#: draft, but the functionality was moved to a different message.
hello_verify_request = 3

#: ``new_session_ticket`` [:rfc:`9846`]
#: new_session_ticket [:rfc:`4507`][:rfc:`9846`][:rfc:`8447`][RFC Errata 7250]
new_session_ticket = 4

#: ``end_of_early_data`` [:rfc:`9846`]
#: end_of_early_data [:rfc:`9846`]
end_of_early_data = 5

#: ``hello_retry_request`` -- historical; the functionality moved to an
#: extension, and TLS 1.3 carries it on a ``server_hello`` instead
#: (:rfc:`9846#section-4.2.4`) [:rfc:`9846`]
#: hello_retry_request [:rfc:`9846`] -- Assigned for interim draft, but the
#: functionality was moved to an extension.
hello_retry_request = 6

#: ``encrypted_extensions`` [:rfc:`9846`]
#: Unassigned
unassigned_7 = 7

#: encrypted_extensions [:rfc:`9846`]
encrypted_extensions = 8

#: ``request_connection_id`` -- DTLS 1.3 only [:rfc:`9147`]
#: request_connection_id [:rfc:`9147`]
request_connection_id = 9

#: ``new_connection_id`` -- DTLS 1.3 only [:rfc:`9147`]
#: new_connection_id [:rfc:`9147`]
new_connection_id = 10

#: ``certificate`` [:rfc:`9846`]
#: certificate [:rfc:`9846`]
certificate = 11

#: ``server_key_exchange`` -- TLS 1.2 and earlier only [:rfc:`5246`]
#: server_key_exchange [:rfc:`9846`] -- Used in TLS versions prior to 1.3.
server_key_exchange = 12

#: ``certificate_request`` [:rfc:`9846`]
#: certificate_request [:rfc:`9846`]
certificate_request = 13

#: ``server_hello_done`` -- TLS 1.2 and earlier only [:rfc:`5246`]
#: server_hello_done [:rfc:`9846`] -- Used in TLS versions prior to 1.3.
server_hello_done = 14

#: ``certificate_verify`` [:rfc:`9846`]
#: certificate_verify [:rfc:`9846`]
certificate_verify = 15

#: ``client_key_exchange`` -- TLS 1.2 and earlier only [:rfc:`5246`]
#: client_key_exchange [:rfc:`9846`] -- Used in TLS versions prior to 1.3.
client_key_exchange = 16

#: ``client_certificate_request`` -- TLS 1.2 and earlier only, per IANA's
#: own comment on this assignment [:rfc:`9261`]
#: client_certificate_request [:rfc:`9261`] -- Used in TLS versions prior to
#: 1.3.
client_certificate_request = 17

#: ``finished`` [:rfc:`9846`]
#: finished [:rfc:`9846`]
finished = 20

#: ``certificate_url`` -- TLS 1.2 and earlier only [:rfc:`6066`]
#: certificate_url [:rfc:`6066`][:rfc:`9846`] -- Used in TLS versions prior to
#: 1.3.
certificate_url = 21

#: ``certificate_status`` -- TLS 1.2 and earlier only [:rfc:`6066`]
#: certificate_status [:rfc:`6066`][:rfc:`9846`] -- Used in TLS versions prior
#: to 1.3.
certificate_status = 22

#: ``supplemental_data`` -- TLS 1.2 and earlier only [:rfc:`4680`]
#: supplemental_data [:rfc:`4680`][:rfc:`9846`] -- Used in TLS versions prior
#: to 1.3.
supplemental_data = 23

#: ``key_update`` [:rfc:`9846`]
#: key_update [:rfc:`9846`]
key_update = 24

#: ``compressed_certificate`` [:rfc:`8879`]
#: compressed_certificate [:rfc:`8879`]
compressed_certificate = 25

#: ``ekt_key`` [:rfc:`8870`]
#: ekt_key [:rfc:`8870`]
ekt_key = 26

#: ``message_hash`` [:rfc:`9846`]
#: message_hash [:rfc:`9846`]
message_hash = 254

#: Unassigned
unassigned_255 = 255

@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:`~pcapkit.corekit.enum.EnumRegistry.register` refuses exactly the
values :meth:`~pcapkit.corekit.enum.EnumLookup.get` refuses, in the
same words (:issue:`1816`).

Args:
value: Value about to be looked up or minted.

Raises:
EnumValueError: If ``value`` fails that check.
EnumValueError: If ``value`` fails that check. A
:exc:`ValueError`, so an ``except ValueError`` caller is
unaffected.

"""
if not (isinstance(value, int) and 0 <= value <= 255):
Expand All @@ -142,5 +145,10 @@ def _missing_(cls, value: 'int') -> 'HandshakeType':
"""
if not (isinstance(value, int) and 0 <= value <= 255):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
#: Unassigned
return cls._unregistered_member(value, 'Unassigned')
if 18 <= value <= 19:
#: Unassigned
return cls._unregistered_member(value, 'Unassigned')
if 27 <= value <= 253:
#: Unassigned
return cls._unregistered_member(value, 'Unassigned')
return super()._missing_(value)
12 changes: 7 additions & 5 deletions pcapkit/protocols/application/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@
_DTLS_VERSION_MAJOR = 0xFE

#: The content types a genuine record may carry. An allowlist rather than
#: every :class:`~pcapkit.const.tls.content_type.ContentType` member: the
#: registry names ``invalid(0)``, a sentinel :rfc:`9846#section-5.1` rejects,
#: and mints a member for any unassigned value it is asked for.
#: every :class:`~pcapkit.const.tls.content_type.ContentType` member: 0 is
#: never assigned, a value :rfc:`9846#section-5.1` rejects outright, and the
#: enum mints an ``Unassigned``/``Reserved`` member for any value IANA has
#: not named.
#:
#: ``tls12_cid`` (25, :rfc:`9146#section-4`) is left out: its connection ID
#: precedes ``length`` and has a negotiated length the record does not carry,
Expand Down Expand Up @@ -235,8 +236,9 @@ def _validate_tls_record(self, schema: 'Any', *, available: 'int', dtls: 'bool')
"""
# NOTE: ``make()`` leaves ``content_type`` a bare :obj:`int`, not the enum member a
# parse yields, so resolve it through the registry to cover both.
# NOTE: checked against ``_WIRE_CONTENT_TYPES``, not by name: ``invalid(0)`` is
# named yet never a record's content type.
# NOTE: checked against ``_WIRE_CONTENT_TYPES``, not by name: an
# unassigned or reserved value still resolves to a named member via
# ``_missing_``, yet is never a record's genuine content type.
content_type = Enum_ContentType.get(int(schema.content_type))
if content_type not in _WIRE_CONTENT_TYPES:
raise ProtocolError(
Expand Down
3 changes: 3 additions & 0 deletions pcapkit/vendor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def __getattr__(name: 'str') -> 'typing.NoReturn':
from pcapkit.vendor.ospf import *
from pcapkit.vendor.sctp import *
from pcapkit.vendor.tcp import *
from pcapkit.vendor.tls import *
from pcapkit.vendor.vlan import *

# Crawlers only. This list is also the default target list of
Expand Down Expand Up @@ -180,6 +181,8 @@ def __getattr__(name: 'str') -> 'typing.NoReturn':
'SCTP_PayloadProtocolIdentifier',
# TCP
'TCP_Checksum', 'TCP_Option', 'TCP_MPTCPOption', 'TCP_Flags',
# TLS
'TLS_ContentType', 'TLS_HandshakeType',
# VLAN
'VLAN_PriorityLevel',
# PCAPNG
Expand Down
Loading
Loading