Skip to content

Commit 8b50c1c

Browse files
authored
fix(vendor): drop the bracketed [UNSPECIFIED] from ESP Integrity comments (#2294) (#2371)
Integrity.rfcs tested the whole cell against UNKNOWN, but IANA spells the "no document" cell "[UNSPECIFIED]", brackets included, so it passed through and pcapkit.const.esp.integrity read "#: AUTH_DES_MAC [UNSPECIFIED] (DEPRECATED [:rfc:`8247`])", and the same for AUTH_KPDK_MD5. Strip one pair of surrounding brackets before the UNKNOWN test, in Integrity.rfcs and in Cipher.rfcs, which shares the check. The const file is regenerated by running the crawler against the live registry; cipher.py regenerates unchanged. Test: test_esp_unspecified_reference_2294_unit checks both crawlers' rfcs on bracketed and bare placeholders and renders the recorded registry into the committed integrity.py.
1 parent 88ec502 commit 8b50c1c

4 files changed

Lines changed: 147 additions & 6 deletions

File tree

‎pcapkit/const/esp/integrity.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ class Integrity(EnumRegistry, IntEnum):
4141
#: Alias of :attr:`Integrity.AUTH_HMAC_SHA1_96`.
4242
HMAC_SHA1_96 = 2
4343

44-
#: AUTH_DES_MAC [UNSPECIFIED] (DEPRECATED [:rfc:`8247`])
44+
#: AUTH_DES_MAC (DEPRECATED [:rfc:`8247`])
4545
AUTH_DES_MAC = 3
4646

4747
#: Alias of :attr:`Integrity.AUTH_DES_MAC`.
4848
DES_MAC = 3
4949

50-
#: AUTH_KPDK_MD5 [UNSPECIFIED] (DEPRECATED [:rfc:`8247`])
50+
#: AUTH_KPDK_MD5 (DEPRECATED [:rfc:`8247`])
5151
AUTH_KPDK_MD5 = 4
5252

5353
#: Alias of :attr:`Integrity.AUTH_KPDK_MD5`.

‎pcapkit/vendor/esp/cipher.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,18 @@ def rfcs(text: 'str') -> 'str':
4040
Returns:
4141
The cell with its ``[RFCxxxx]`` citations turned into
4242
:rst:role:`rfc` roles, or an empty string when the cell names no
43-
document at all. IANA's ``[RFC xxxx]; [RFC yyyy]`` spelling of a
43+
document at all, bracketed (``[UNSPECIFIED]``) or not
44+
(:issue:`2294`). IANA's ``[RFC xxxx]; [RFC yyyy]`` spelling of a
4445
cell is rendered as its ``[RFCxxxx][RFCyyyy]`` one is, as
4546
:meth:`~pcapkit.vendor.default.Vendor.split_references` does for
4647
the other crawlers (:issue:`1664`).
4748
4849
"""
4950
text = re.sub(r'\][\s;]*\[', '][', re.sub(r'\s+', ' ', text)).strip()
50-
if text.upper() in UNKNOWN:
51+
# IANA brackets a placeholder as it brackets a citation, so one
52+
# surrounding pair is dropped before the placeholder test (#2294).
53+
bare = text[1:-1].strip() if text.startswith('[') and text.endswith(']') else text
54+
if bare.upper() in UNKNOWN:
5155
return ''
5256
return re.sub(r'\[RFC\s*(\d+)\]',
5357
lambda match: f'[:rfc:`{match.group(1)}`]', text).replace('_', ' ')

‎pcapkit/vendor/esp/integrity.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,18 @@ def rfcs(text: 'str') -> 'str':
4040
Returns:
4141
The cell with its ``[RFCxxxx]`` citations turned into
4242
:rst:role:`rfc` roles, or an empty string when the cell names no
43-
document at all. IANA's ``[RFC xxxx]; [RFC yyyy]`` spelling of a
43+
document at all, bracketed (``[UNSPECIFIED]``) or not
44+
(:issue:`2294`). IANA's ``[RFC xxxx]; [RFC yyyy]`` spelling of a
4445
cell is rendered as its ``[RFCxxxx][RFCyyyy]`` one is, as
4546
:meth:`~pcapkit.vendor.default.Vendor.split_references` does for
4647
the other crawlers (:issue:`1664`).
4748
4849
"""
4950
text = re.sub(r'\][\s;]*\[', '][', re.sub(r'\s+', ' ', text)).strip()
50-
if text.upper() in UNKNOWN:
51+
# IANA brackets a placeholder as it brackets a citation, so one
52+
# surrounding pair is dropped before the placeholder test (#2294).
53+
bare = text[1:-1].strip() if text.startswith('[') and text.endswith(']') else text
54+
if bare.upper() in UNKNOWN:
5155
return ''
5256
return re.sub(r'\[RFC\s*(\d+)\]',
5357
lambda match: f'[:rfc:`{match.group(1)}`]', text).replace('_', ' ')
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# -*- coding: utf-8 -*-
2+
"""Regression tests for GitHub issue #2294.
3+
4+
:meth:`Integrity.rfcs <pcapkit.vendor.esp.integrity.Integrity.rfcs>` returns
5+
an empty string for a cell that names no document, whether IANA brackets the
6+
placeholder (``[UNSPECIFIED]``) or not, by dropping one surrounding pair of
7+
brackets before testing the cell against ``UNKNOWN``. Guards against testing
8+
the cell as it stands: a bracketed placeholder matches no ``UNKNOWN``
9+
spelling, and :mod:`pcapkit.const.esp.integrity` then renders as
10+
``#: AUTH_DES_MAC [UNSPECIFIED] (DEPRECATED [:rfc:`8247`])``.
11+
:meth:`Cipher.rfcs <pcapkit.vendor.esp.cipher.Cipher.rfcs>` shares the check,
12+
though its registry spells the cell without brackets.
13+
14+
"""
15+
from __future__ import annotations
16+
17+
import importlib.util
18+
import pathlib
19+
import unittest
20+
from typing import TYPE_CHECKING
21+
22+
from tests._support import reimport_once_per_class
23+
24+
if TYPE_CHECKING:
25+
from typing import Any
26+
27+
#: Test tier (#1958): renders a constant module from recorded data and compares it
28+
#: with the committed file, which the vendor scripts regenerate on one interpreter.
29+
__tier__ = 'LATEST'
30+
31+
#: Repository root, i.e. the grandparent of the directory holding this file.
32+
ROOT = pathlib.Path(__file__).resolve().parents[2]
33+
34+
#: Every distribution importing :mod:`pcapkit.vendor` needs -- see
35+
#: :mod:`tests.vendor.test_ipx_packet_unit` for why all three are checked.
36+
VENDOR_DEPS = ('requests', 'bs4', 'html5lib')
37+
38+
#: Whether the crawlers are importable at all.
39+
HAS_VENDOR_DEPS = all(importlib.util.find_spec(name) is not None for name in VENDOR_DEPS)
40+
41+
#: ``ikev2-parameters/ikev2-parameters-7.csv`` (IKEv2 integrity algorithm
42+
#: transform IDs), live registry, ``Last-Modified`` 2026-10-13 20:07:18Z.
43+
INTEGRITY_CSV = (
44+
'Number,Name,Status,Reference\r\n'
45+
'0,NONE,,[RFC 7296]\r\n'
46+
'1,AUTH_HMAC_MD5_96,DEPRECATED [RFC 8247],[RFC 2403]; [RFC 7296]\r\n'
47+
'2,AUTH_HMAC_SHA1_96,,[RFC 2404]; [RFC 7296]\r\n'
48+
'3,AUTH_DES_MAC,DEPRECATED [RFC 8247],[UNSPECIFIED]\r\n'
49+
'4,AUTH_KPDK_MD5,DEPRECATED [RFC 8247],[UNSPECIFIED]\r\n'
50+
'5,AUTH_AES_XCBC_96,,[RFC 3566]; [RFC 7296]\r\n'
51+
'6,AUTH_HMAC_MD5_128,DEPRECATED [RFC 9395],[RFC 4595]\r\n'
52+
'7,AUTH_HMAC_SHA1_160,DEPRECATED [RFC 9395],[RFC 4595]\r\n'
53+
'8,AUTH_AES_CMAC_96,,[RFC 4494]\r\n'
54+
'9,AUTH_AES_128_GMAC,,[RFC 4543]\r\n'
55+
'10,AUTH_AES_192_GMAC,,[RFC 4543]\r\n'
56+
'11,AUTH_AES_256_GMAC,,[RFC 4543]\r\n'
57+
'12,AUTH_HMAC_SHA2_256_128,,[RFC 4868]\r\n'
58+
'13,AUTH_HMAC_SHA2_384_192,,[RFC 4868]\r\n'
59+
'14,AUTH_HMAC_SHA2_512_256,,[RFC 4868]\r\n'
60+
'15-1023,Unassigned,,\r\n'
61+
'1024-65535,Reserved for Private Use,,[RFC 7296]\r\n'
62+
)
63+
64+
65+
def _normalize(context: 'str') -> 'str':
66+
"""Apply the whitespace normalisation :meth:`Vendor.__init__` writes files through."""
67+
lines = [] # type: list[str]
68+
for line in context.splitlines():
69+
if line:
70+
if line.strip():
71+
lines.append(line.rstrip())
72+
else:
73+
lines.append(line)
74+
return '\n'.join(lines) + '\n'
75+
76+
77+
@unittest.skipUnless(HAS_VENDOR_DEPS, f'vendor extra not installed ({", ".join(VENDOR_DEPS)})')
78+
class UnspecifiedReferenceTests(unittest.TestCase):
79+
"""A bracketed ``[UNSPECIFIED]`` cell renders as no citation at all."""
80+
81+
def setUp(self) -> None:
82+
reimport_once_per_class(self)
83+
84+
import pcapkit.vendor.default as default
85+
86+
resolved = pathlib.Path(default.__file__).resolve()
87+
if ROOT not in resolved.parents:
88+
self.skipTest(f'{default.__name__} was imported from {resolved}, which is '
89+
f'outside {ROOT}; install this checkout with `pip install -e .`')
90+
91+
@staticmethod
92+
def _crawlers() -> 'tuple[Any, ...]':
93+
from pcapkit.vendor.esp.cipher import Cipher
94+
from pcapkit.vendor.esp.integrity import Integrity
95+
96+
return Cipher, Integrity
97+
98+
def test_rfcs_drops_a_bracketed_placeholder(self) -> None:
99+
for crawler in self._crawlers():
100+
for cell in ('[UNSPECIFIED]', '[ UNSPECIFIED ]', '[unspecified]', '[-]', '[]',
101+
'UNSPECIFIED', '-', ''):
102+
with self.subTest(crawler=crawler.__name__, cell=cell):
103+
self.assertEqual(crawler.rfcs(cell), '')
104+
105+
def test_rfcs_keeps_a_bracketed_citation(self) -> None:
106+
for crawler in self._crawlers():
107+
with self.subTest(crawler=crawler.__name__):
108+
self.assertEqual(crawler.rfcs('[RFC 4868]'), '[:rfc:`4868`]')
109+
self.assertEqual(crawler.rfcs('[RFC 2403]; [RFC 7296]'),
110+
'[:rfc:`2403`][:rfc:`7296`]')
111+
self.assertEqual(crawler.rfcs('DEPRECATED [RFC 8247]'),
112+
'DEPRECATED [:rfc:`8247`]')
113+
114+
def test_integrity_matches_the_committed_file(self) -> None:
115+
from pcapkit.vendor.esp.integrity import Integrity
116+
117+
vendor = Integrity.__new__(Integrity)
118+
vendor.NAME = Integrity.__name__
119+
vendor.DOCS = Integrity.__doc__
120+
lines = vendor.request(INTEGRITY_CSV)
121+
vendor.record = vendor.count(lines)
122+
rendered = _normalize(vendor.context(lines))
123+
124+
path = ROOT / 'pcapkit' / 'const' / 'esp' / 'integrity.py'
125+
committed = path.read_text(encoding='utf-8')
126+
self.assertNotIn('UNSPECIFIED', committed)
127+
self.assertIn('#: AUTH_DES_MAC (DEPRECATED [:rfc:`8247`])', committed)
128+
self.assertIn('#: AUTH_KPDK_MD5 (DEPRECATED [:rfc:`8247`])', committed)
129+
self.assertEqual(rendered, committed)
130+
131+
132+
if __name__ == '__main__':
133+
unittest.main()

0 commit comments

Comments
 (0)