Skip to content

Commit e427d96

Browse files
committed
fix(vendor): add the SSLv2 crawler, DATA-attribute shape
pcapkit.const.sslv2's two modules were hand-curated with no pcapkit.vendor.sslv2 crawler, violating the const-vendor-pairing rule (#2495: "ALL const MUST have its own vendor crawler... use the DATA attribute... no exception"). - Add pcapkit.vendor.sslv2 (handshake_type, version), mirroring the ARPHRD precedent (#2497): each Vendor subclass's request() returns a DATA dict transcribed from the expired SSLv2 draft (draft-hickman-netscape-ssl-00) instead of fetching a URL. - Wire SSLv2_HandshakeType and SSLv2_Version into pcapkit.vendor.__all__. - Regenerate both const modules from the crawlers. Only drift: the docstring (moved to the crawler) and Version.SSLv2's literal (0x0002 -> 2, the crawler's decimal rendering). A second run is a byte-for-byte no-op. - Bump EXPECTED_CRAWLERS_WITH_CONSTANT_MISS_LINES 35->37 for the two new crawlers. - Update docs/source/pcapkit/const/sslv2.rst and the now-stale "hand-curated, no crawler" comments in two tests. Verified: tests/vendor/, tests/const/, the sslv2-specific tests, and tests/project/test_public_api.py all green.
1 parent c47621a commit e427d96

10 files changed

Lines changed: 264 additions & 45 deletions

File tree

‎docs/source/pcapkit/const/sslv2.rst‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ SSLv2 Handshake Message Type
2323
.. module:: pcapkit.const.sslv2.handshake_type
2424

2525
This module contains the constant enumeration for **SSLv2 Handshake Message
26-
Type**, hand-curated rather than generated by a
27-
:class:`~pcapkit.vendor.default.Vendor` crawler: the expired SSLv2 draft is
28-
not a live registry a crawler could re-fetch.
26+
Type**, which is automatically generated from
27+
:class:`pcapkit.vendor.sslv2.handshake_type.HandshakeType`. Its source is the
28+
expired SSLv2 draft linked above, not a live registry, so the crawler reads a
29+
``DATA`` attribute instead of fetching a URL.
2930

3031
.. autoclass:: pcapkit.const.sslv2.handshake_type.HandshakeType
3132
:members:
@@ -38,7 +39,8 @@ SSLv2 Protocol Version
3839
.. module:: pcapkit.const.sslv2.version
3940

4041
This module contains the constant enumeration for **SSLv2 Protocol
41-
Version**, hand-curated for the same reason
42+
Version**, which is automatically generated from
43+
:class:`pcapkit.vendor.sslv2.version.Version`, for the same reason
4244
:class:`~pcapkit.const.sslv2.handshake_type.HandshakeType` is.
4345

4446
.. autoclass:: pcapkit.const.sslv2.version.Version

‎pcapkit/const/sslv2/handshake_type.py‎

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,8 @@
55
66
.. module:: pcapkit.const.sslv2.handshake_type
77
8-
This module contains the constant enumeration for **SSLv2 Handshake Message
9-
Type**, hand-curated rather than generated by a
10-
:class:`~pcapkit.vendor.default.Vendor` crawler: the ``MSG-*`` codes below
11-
are the complete, closed set defined by the expired Netscape SSLv2
12-
draft [*]_, which is not an IANA (or any other) live registry a crawler
13-
could re-fetch -- there is nothing upstream of this file to go stale against.
14-
15-
.. [*] https://datatracker.ietf.org/doc/html/draft-hickman-netscape-ssl-00
8+
This module contains the constant enumeration for **SSLv2 Handshake Message Type**,
9+
which is automatically generated from :class:`pcapkit.vendor.sslv2.handshake_type.HandshakeType`.
1610
1711
"""
1812

@@ -85,10 +79,6 @@ def _validate_value(cls, value: 'Any') -> 'None':
8579
def _missing_(cls, value: 'int') -> 'HandshakeType':
8680
"""Lookup function used when value is not found.
8781
88-
A wire byte the draft does not define, ``9`` through ``255``,
89-
resolves to an unregistered ``Unassigned`` member rather than
90-
raising, as other registries in :mod:`pcapkit.const` do.
91-
9282
Args:
9383
value: Value to get enum item.
9484

‎pcapkit/const/sslv2/version.py‎

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,8 @@
55
66
.. module:: pcapkit.const.sslv2.version
77
8-
This module contains the constant enumeration for **SSLv2 Protocol
9-
Version**, hand-curated rather than generated by a
10-
:class:`~pcapkit.vendor.default.Vendor` crawler, for the same reason
11-
:class:`pcapkit.const.sslv2.handshake_type.HandshakeType` is: the value below
12-
comes from the expired Netscape SSLv2 draft [*]_, not a live registry.
13-
14-
SSLv2 carries no record-layer version field; ``CLIENT-VERSION`` and
15-
``SERVER-VERSION`` are handshake-body fields. See :meth:`Version._missing_`
16-
for values other than ``SSLv2``.
17-
18-
.. [*] https://datatracker.ietf.org/doc/html/draft-hickman-netscape-ssl-00
8+
This module contains the constant enumeration for **SSLv2 Protocol Version**,
9+
which is automatically generated from :class:`pcapkit.vendor.sslv2.version.Version`.
1910
2011
"""
2112

@@ -36,7 +27,7 @@ class Version(EnumRegistry, IntEnum):
3627
"""[Version] SSLv2 Protocol Version"""
3728

3829
#: ``SSL-2.0``
39-
SSLv2 = 0x0002
30+
SSLv2 = 2
4031

4132
@classmethod
4233
def _validate_value(cls, value: 'Any') -> 'None':
@@ -64,12 +55,6 @@ def _validate_value(cls, value: 'Any') -> 'None':
6455
def _missing_(cls, value: 'int') -> 'Version':
6556
"""Lookup function used when value is not found.
6657
67-
A ``CLIENT-VERSION``/``SERVER-VERSION`` other than ``0x0002`` is not
68-
necessarily malformed: per :rfc:`6101#appendix-E`, an SSLv3-capable
69-
client sends an SSLv2-framed ``CLIENT-HELLO`` whose version reads
70-
``768`` (``0x0300``). So any unrecognised value resolves to an
71-
unregistered member rather than raising.
72-
7358
Args:
7459
value: Value to get enum item.
7560

‎pcapkit/vendor/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def __getattr__(name: 'str') -> 'typing.NoReturn':
119119
from pcapkit.vendor.ngap import *
120120
from pcapkit.vendor.ospf import *
121121
from pcapkit.vendor.sctp import *
122+
from pcapkit.vendor.sslv2 import *
122123
from pcapkit.vendor.tcp import *
123124
from pcapkit.vendor.vlan import *
124125

@@ -178,6 +179,8 @@ def __getattr__(name: 'str') -> 'typing.NoReturn':
178179
# SCTP
179180
'SCTP_Chunk', 'SCTP_Parameter', 'SCTP_CauseCode',
180181
'SCTP_PayloadProtocolIdentifier',
182+
# SSLv2
183+
'SSLv2_HandshakeType', 'SSLv2_Version',
181184
# TCP
182185
'TCP_Checksum', 'TCP_Option', 'TCP_MPTCPOption', 'TCP_Flags',
183186
# VLAN

‎pcapkit/vendor/sslv2/__init__.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# -*- coding: utf-8 -*-
2+
# pylint: disable=unused-import
3+
""":mod:`pcapkit.const.sslv2` Vendor Crawlers
4+
================================================
5+
6+
.. module:: pcapkit.vendor.sslv2
7+
8+
This module contains all vendor crawlers of
9+
:class:`~pcapkit.protocols.application.sslv2.SSLv2` implementations.
10+
Available crawlers include:
11+
12+
.. list-table::
13+
14+
* - :class:`SSLv2_HandshakeType <pcapkit.vendor.sslv2.handshake_type.HandshakeType>`
15+
- SSLv2 Handshake Message Type
16+
* - :class:`SSLv2_Version <pcapkit.vendor.sslv2.version.Version>`
17+
- SSLv2 Protocol Version
18+
19+
"""
20+
21+
from pcapkit.vendor.sslv2.handshake_type import HandshakeType as SSLv2_HandshakeType
22+
from pcapkit.vendor.sslv2.version import Version as SSLv2_Version
23+
24+
__all__ = ['SSLv2_HandshakeType', 'SSLv2_Version']
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# -*- coding: utf-8 -*-
2+
"""SSLv2 Handshake Message Type
3+
==================================
4+
5+
.. module:: pcapkit.vendor.sslv2.handshake_type
6+
7+
This module contains the vendor crawler for **SSLv2 Handshake Message Type**,
8+
which is automatically generating :class:`pcapkit.const.sslv2.handshake_type.HandshakeType`.
9+
10+
"""
11+
12+
import collections
13+
import sys
14+
from typing import TYPE_CHECKING
15+
16+
from pcapkit.vendor.default import Vendor
17+
18+
if TYPE_CHECKING:
19+
from collections import Counter
20+
21+
__all__ = ['HandshakeType']
22+
23+
###############################################################################
24+
# NOTE: this crawler does not crawl. Its source is the expired Netscape SSLv2
25+
# draft, draft-hickman-netscape-ssl-00, which is not an IANA (or any other)
26+
# live registry a crawler could re-fetch -- there is nothing upstream of this
27+
# module to go stale against. Per the const-vendor-pairing convention, a
28+
# hand-maintained registry still gets a crawler, reading
29+
# :data:`~pcapkit.vendor.sslv2.handshake_type.DATA` instead of fetching a URL
30+
# -- the same shape as :mod:`pcapkit.vendor.linux.arphrd`,
31+
# :mod:`pcapkit.vendor.ipx.packet` and :mod:`pcapkit.vendor.ipx.socket`.
32+
###############################################################################
33+
34+
#: SSLv2 handshake message type registry, transcribed from
35+
#: draft-hickman-netscape-ssl-00 for the complete, closed set of ``MSG-*``
36+
#: codes the draft defines.
37+
#:
38+
#: Maps a handshake message type to the enumeration name it takes and the
39+
#: ``MSG-*`` constant the comment above it renders. Names go through
40+
#: :meth:`~Vendor.rename`, so the generated member is the
41+
#: :meth:`~Vendor.safe_name` of the first element.
42+
DATA = {
43+
0: ('ERROR', '``MSG-ERROR``'),
44+
1: ('CLIENT_HELLO', '``MSG-CLIENT-HELLO``'),
45+
2: ('CLIENT_MASTER_KEY', '``MSG-CLIENT-MASTER-KEY``'),
46+
3: ('CLIENT_FINISHED', '``MSG-CLIENT-FINISHED``'),
47+
4: ('SERVER_HELLO', '``MSG-SERVER-HELLO``'),
48+
5: ('SERVER_VERIFY', '``MSG-SERVER-VERIFY``'),
49+
6: ('SERVER_FINISHED', '``MSG-SERVER-FINISHED``'),
50+
7: ('REQUEST_CERTIFICATE', '``MSG-REQUEST-CERTIFICATE``'),
51+
8: ('CLIENT_CERTIFICATE', '``MSG-CLIENT-CERTIFICATE``'),
52+
} # type: dict[int, tuple[str, str]]
53+
54+
55+
class HandshakeType(Vendor):
56+
"""SSLv2 Handshake Message Type"""
57+
58+
#: Value limit checker.
59+
FLAG = 'isinstance(value, int) and 0x00 <= value <= 0xFF'
60+
61+
def request(self) -> 'dict[int, tuple[str, str]]': # type: ignore[override] # pylint: disable=arguments-differ
62+
"""Fetch registry data.
63+
64+
Returns:
65+
Registry data (:data:`~pcapkit.vendor.sslv2.handshake_type.DATA`).
66+
67+
"""
68+
return DATA
69+
70+
def count(self, data: 'dict[int, tuple[str, str]]') -> 'Counter[str]': # type: ignore[override]
71+
"""Count field records.
72+
73+
Args:
74+
data: Registry data.
75+
76+
Returns:
77+
Field recordings.
78+
79+
"""
80+
return collections.Counter(self.safe_name(name) for name, _ in data.values())
81+
82+
def process(self, data: 'dict[int, tuple[str, str]]') -> 'tuple[list[str], list[str]]': # type: ignore[override]
83+
"""Process registry data.
84+
85+
Args:
86+
data: Registry data.
87+
88+
Returns:
89+
Enumeration fields and missing fields.
90+
91+
"""
92+
enum = [] # type: list[str]
93+
miss = [
94+
"return cls._unregistered_member(value, 'Unassigned')",
95+
]
96+
97+
for code, (name, desc) in data.items():
98+
pval = str(code)
99+
renm = self.rename(name, pval)
100+
101+
enum.append(f'#: {self.wrap_comment(desc)}\n {renm} = {pval}')
102+
return enum, miss
103+
104+
105+
if __name__ == '__main__':
106+
sys.exit(HandshakeType()) # type: ignore[arg-type]

‎pcapkit/vendor/sslv2/version.py‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# -*- coding: utf-8 -*-
2+
"""SSLv2 Protocol Version
3+
============================
4+
5+
.. module:: pcapkit.vendor.sslv2.version
6+
7+
This module contains the vendor crawler for **SSLv2 Protocol Version**, which
8+
is automatically generating :class:`pcapkit.const.sslv2.version.Version`.
9+
10+
"""
11+
12+
import collections
13+
import sys
14+
from typing import TYPE_CHECKING
15+
16+
from pcapkit.vendor.default import Vendor
17+
18+
if TYPE_CHECKING:
19+
from collections import Counter
20+
21+
__all__ = ['Version']
22+
23+
###############################################################################
24+
# NOTE: this crawler does not crawl. Its source is the expired Netscape SSLv2
25+
# draft, draft-hickman-netscape-ssl-00, which is not an IANA (or any other)
26+
# live registry a crawler could re-fetch -- there is nothing upstream of this
27+
# module to go stale against. Per the const-vendor-pairing convention, a
28+
# hand-maintained registry still gets a crawler, reading
29+
# :data:`~pcapkit.vendor.sslv2.version.DATA` instead of fetching a URL -- the
30+
# same shape as :mod:`pcapkit.vendor.linux.arphrd`,
31+
# :mod:`pcapkit.vendor.ipx.packet` and :mod:`pcapkit.vendor.ipx.socket`.
32+
#
33+
# SSLv2 carries no record-layer version field; ``CLIENT-VERSION`` and
34+
# ``SERVER-VERSION`` are handshake-body fields, which is why the registry
35+
# below names a single value rather than a numbered range: any value other
36+
# than ``SSL-2.0`` is not necessarily malformed -- per :rfc:`6101#appendix-E`,
37+
# an SSLv3-capable client sends an SSLv2-framed ``CLIENT-HELLO`` whose version
38+
# reads ``768`` (``0x0300``) -- so :meth:`~pcapkit.const.sslv2.version.Version._missing_`
39+
# resolves any other in-range value to an unregistered member instead of
40+
# raising.
41+
###############################################################################
42+
43+
#: SSLv2 protocol version registry, transcribed from
44+
#: draft-hickman-netscape-ssl-00 for the single value the draft defines.
45+
#:
46+
#: Maps the version value to the enumeration name it takes and the
47+
#: ``SSL-*`` constant the comment above it renders. Names go through
48+
#: :meth:`~Vendor.rename`, so the generated member is the
49+
#: :meth:`~Vendor.safe_name` of the first element.
50+
DATA = {
51+
2: ('SSLv2', '``SSL-2.0``'),
52+
} # type: dict[int, tuple[str, str]]
53+
54+
55+
class Version(Vendor):
56+
"""SSLv2 Protocol Version"""
57+
58+
#: Value limit checker.
59+
FLAG = 'isinstance(value, int) and 0x0000 <= value <= 0xFFFF'
60+
61+
def request(self) -> 'dict[int, tuple[str, str]]': # type: ignore[override] # pylint: disable=arguments-differ
62+
"""Fetch registry data.
63+
64+
Returns:
65+
Registry data (:data:`~pcapkit.vendor.sslv2.version.DATA`).
66+
67+
"""
68+
return DATA
69+
70+
def count(self, data: 'dict[int, tuple[str, str]]') -> 'Counter[str]': # type: ignore[override]
71+
"""Count field records.
72+
73+
Args:
74+
data: Registry data.
75+
76+
Returns:
77+
Field recordings.
78+
79+
"""
80+
return collections.Counter(self.safe_name(name) for name, _ in data.values())
81+
82+
def process(self, data: 'dict[int, tuple[str, str]]') -> 'tuple[list[str], list[str]]': # type: ignore[override]
83+
"""Process registry data.
84+
85+
Args:
86+
data: Registry data.
87+
88+
Returns:
89+
Enumeration fields and missing fields.
90+
91+
"""
92+
enum = [] # type: list[str]
93+
miss = [
94+
"return cls._unregistered_member(value, 'Unassigned')",
95+
]
96+
97+
for code, (name, desc) in data.items():
98+
pval = str(code)
99+
renm = self.rename(name, pval)
100+
101+
enum.append(f'#: {self.wrap_comment(desc)}\n {renm} = {pval}')
102+
return enum, miss
103+
104+
105+
if __name__ == '__main__':
106+
sys.exit(Version()) # type: ignore[arg-type]

‎tests/const/test_const_enum_builtin_parity.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,9 @@ def test_the_sweep_size_is_pinned(self) -> None:
377377
# tests.const.test_const_enum_get's narrower (IntEnum, IntFlag)-only
378378
# sweep. The int count includes GitHub issue #880's ngap.ProcedureCode
379379
# and ngap.ProtocolIE, which are on EnumRegistry, and GitHub issue
380-
# #2457's sslv2.HandshakeType and sslv2.Version, hand-curated rather than
381-
# generated but the same EnumRegistry-plus-IntEnum shape.
380+
# #2457's sslv2.HandshakeType and sslv2.Version, generated by a
381+
# DATA-attribute crawler rather than a web-scraped one, but the same
382+
# EnumRegistry-plus-IntEnum shape.
382383
self.assertEqual(len(ints), 119)
383384
self.assertEqual(len(flags), 6)
384385
self.assertEqual(len(strs), 10)

‎tests/const/test_const_sslv2_unit.py‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22
"""Unit tests for :mod:`pcapkit.const.sslv2.handshake_type` and
33
:mod:`pcapkit.const.sslv2.version` (:issue:`2457`).
44
5-
Both registries are hand-curated rather than generated by a
6-
:class:`~pcapkit.vendor.default.Vendor` crawler -- the expired SSLv2 draft is
7-
not a live registry -- but they still follow this package's lookup contract:
8-
a registered member resolves by value, an unregistered one resolves to a
9-
nameless ``Unassigned`` member without being minted into the registry
10-
(:issue:`860`), and a value outside the member type's own range raises.
5+
Both registries are generated by a :class:`~pcapkit.vendor.default.Vendor`
6+
crawler (:mod:`pcapkit.vendor.sslv2`) that reads a ``DATA`` attribute instead
7+
of fetching a URL, since their source -- the expired SSLv2 draft -- is not a
8+
live registry a crawler could re-fetch. They still follow this package's
9+
lookup contract: a registered member resolves by value, an unregistered one
10+
resolves to a nameless ``Unassigned`` member without being minted into the
11+
registry (:issue:`860`), and a value outside the member type's own range
12+
raises.
1113
1214
"""
1315
from __future__ import annotations
1416

1517
import unittest
1618

17-
#: Test tier (#1958): pure-Python lookups over a hand-curated, closed registry,
18-
#: which behave the same on every interpreter.
19+
#: Test tier (#1958): pure-Python lookups over a closed registry, which
20+
#: behave the same on every interpreter.
1921
__tier__ = 'LATEST'
2022

2123

0 commit comments

Comments
 (0)