Skip to content

Commit e64f991

Browse files
committed
refactor(schema): share the IPv6-option schema helpers (#1519)
- Move the nine HOPOPT / IPv6-Opts option helpers into a new `pcapkit.protocols.schema.internet.ipv6_option`, parameterised by the error prefix and, for the selectors, by the option schemas to return. `hopopt.py` and `ipv6_opts.py` bind them in same-named functions, and use `rpl_opt_sub_tlv_len` and `pad_opt_data_len` unchanged. - Correct the TaggerID values in the `smf_i_dpd_tid_selector` docstring (IPv4 is 2 and IPv6 is 3, not 1 and 2). - Keep the PCAP and PCAP-NG `byteorder_callback` separate: only the PCAP-NG one falls back to an enclosing `__packet__`. Note why in the frame docstring and pin the difference in a test. - Let the address-typed switch discovery in `tests/corekit/test_fields_misc.py` read the shared helper a selector delegates to. - Document the shared module on a new docs page, and point the HOPOPT and IPv6-Opts pages at it. Byte-identical on every sample capture; new tests pin each helper and both error prefixes.
1 parent ffca646 commit e64f991

11 files changed

Lines changed: 701 additions & 390 deletions

File tree

‎docs/source/pcapkit/protocols/internet/hopopt.rst‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,15 @@ Type Stubs
220220
Auxiliary Functions
221221
~~~~~~~~~~~~~~~~~~~
222222

223+
The option helpers are shared with
224+
:mod:`~pcapkit.protocols.schema.internet.ipv6_opts` and documented in
225+
:mod:`pcapkit.protocols.schema.internet.ipv6_option`. The functions below bind
226+
them to this module's error prefix and option schemas;
227+
:func:`~pcapkit.protocols.schema.internet.ipv6_option.rpl_opt_sub_tlv_len` and
228+
:func:`~pcapkit.protocols.schema.internet.ipv6_option.pad_opt_data_len` are
229+
used unchanged.
230+
223231
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.mpl_opt_seed_id_len
224-
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.pad_opt_data_len
225232
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.calipso_pad_len
226233
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.mpl_opt_pad_len
227234

‎docs/source/pcapkit/protocols/internet/index.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internet layer, with detailed implementation and methods.
2020
ipv6_opts
2121
ipv6_route
2222
hopopt
23+
ipv6_option
2324
ipsec
2425
ah
2526
esp
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
IPv6 Options - Shared Option Helpers
2+
====================================
3+
4+
.. module:: pcapkit.protocols.schema.internet.ipv6_option
5+
6+
:mod:`pcapkit.protocols.schema.internet.ipv6_option` contains the field helpers
7+
shared by the option schemas of
8+
:mod:`~pcapkit.protocols.schema.internet.hopopt` (HOPOPT) and
9+
:mod:`~pcapkit.protocols.schema.internet.ipv6_opts` (IPv6-Opts), since both
10+
headers carry the same IPv6 options [*]_. A helper that reports an error takes
11+
the header's name as ``prefix``, and a selector takes the header's option
12+
schemas. Each header module binds them in a function of the same name, which
13+
is what its fields use.
14+
15+
Auxiliary Functions
16+
-------------------
17+
18+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.rpl_opt_sub_tlv_len
19+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.mpl_opt_seed_id_len
20+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.pad_opt_data_len
21+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.calipso_pad_len
22+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.mpl_opt_pad_len
23+
24+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.smf_dpd_data_selector
25+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.smf_i_dpd_tid_selector
26+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.smf_i_dpd_id_len
27+
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_option.quick_start_data_selector
28+
29+
.. rubric:: Footnotes
30+
31+
.. [*] https://www.rfc-editor.org/rfc/rfc8200#section-4.2

‎docs/source/pcapkit/protocols/internet/ipv6_opts.rst‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,15 @@ Type Stubs
220220
Auxiliary Functions
221221
~~~~~~~~~~~~~~~~~~~
222222

223+
The option helpers are shared with
224+
:mod:`~pcapkit.protocols.schema.internet.hopopt` and documented in
225+
:mod:`pcapkit.protocols.schema.internet.ipv6_option`. The functions below bind
226+
them to this module's error prefix and option schemas;
227+
:func:`~pcapkit.protocols.schema.internet.ipv6_option.rpl_opt_sub_tlv_len` and
228+
:func:`~pcapkit.protocols.schema.internet.ipv6_option.pad_opt_data_len` are
229+
used unchanged.
230+
223231
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.mpl_opt_seed_id_len
224-
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.pad_opt_data_len
225232
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.calipso_pad_len
226233
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.mpl_opt_pad_len
227234

‎pcapkit/protocols/schema/internet/hopopt.py‎

Lines changed: 33 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@
1010
from pcapkit.const.ipv6.router_alert import RouterAlert as Enum_RouterAlert
1111
from pcapkit.const.ipv6.seed_id import SeedID as Enum_SeedID
1212
from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode as Enum_SMFDPDMode
13-
from pcapkit.const.ipv6.tagger_id import TaggerID as Enum_TaggerID
1413
from pcapkit.const.reg.transtype import TransType as Enum_TransType
1514
from pcapkit.corekit.fields.collections import OptionField
1615
from pcapkit.corekit.fields.field import NO_VALUE
17-
from pcapkit.corekit.fields.ipaddress import IPv4AddressField, IPv6AddressField
18-
from pcapkit.corekit.fields.misc import (ConditionalField, ForwardMatchField, NoValueField,
19-
PayloadField, SchemaField, SwitchField)
16+
from pcapkit.corekit.fields.ipaddress import IPv6AddressField
17+
from pcapkit.corekit.fields.misc import (ConditionalField, ForwardMatchField, PayloadField,
18+
SwitchField)
2019
from pcapkit.corekit.fields.numbers import (EnumField, NumberField, UInt8Field, UInt16Field,
2120
UInt32Field)
2221
from pcapkit.corekit.fields.strings import BitField, BytesField, PaddingField
22+
from pcapkit.protocols.schema.internet import ipv6_option
23+
from pcapkit.protocols.schema.internet.ipv6_option import pad_opt_data_len, rpl_opt_sub_tlv_len
2324
from pcapkit.protocols.schema.schema import EnumSchema, Schema, schema_final
24-
from pcapkit.utilities.exceptions import FieldValueError
2525
from pcapkit.utilities.logging import SPHINX_TYPE_CHECKING
2626

2727
__all__ = [
@@ -129,244 +129,83 @@ class QSNonce(TypedDict):
129129
reserved: int
130130

131131

132-
def rpl_opt_sub_tlv_len(pkt: 'dict[str, Any]') -> 'int':
133-
"""Return RPL option sub-TLV length.
134-
135-
Args:
136-
pkt: RPL option unpacked schema.
137-
138-
Returns:
139-
RPL option sub-TLV length, i.e. ``Opt Data Len`` less the four octets
140-
of the fixed fields. An ``Opt Data Len`` below ``4`` gives ``0``, and
141-
the reader then rejects the option.
142-
143-
"""
144-
return max(pkt['len'] - 4, 0)
132+
# NOTE: The option helpers are shared with IPv6-Opts and live in
133+
# :mod:`pcapkit.protocols.schema.internet.ipv6_option`. The functions below bind
134+
# what differs between the two headers: the ``HOPOPT`` error prefix, and the
135+
# option schemas of this module.
145136

146137

147138
def mpl_opt_seed_id_len(pkt: 'dict[str, Any]') -> 'int':
148139
"""Return MPL Seed-ID length.
149140
150-
Args:
151-
pkt: MPL option unpacked schema.
152-
153-
Returns:
154-
MPL Seed-ID length.
141+
See :func:`pcapkit.protocols.schema.internet.ipv6_option.mpl_opt_seed_id_len`;
142+
errors are reported as ``HOPOPT``.
155143
156144
"""
157-
s_type = pkt['flags']['type']
158-
if s_type == 0:
159-
return 0
160-
if s_type == 1:
161-
return 2
162-
if s_type == 2:
163-
return 8
164-
if s_type == 3:
165-
return 16
166-
raise FieldValueError(f'HOPOPT: invalid MPL Seed-ID type: {s_type}')
145+
return ipv6_option.mpl_opt_seed_id_len(pkt, prefix='HOPOPT')
167146

168147

169148
def smf_i_dpd_id_len(pkt: 'dict[str, Any]') -> 'int':
170149
"""Return SMF I-DPD identifier length.
171150
172-
Args:
173-
pkt: SMF identification-based DPD option unpacked schema.
174-
175-
Returns:
176-
SMF I-DPD identifier length.
177-
178-
Raises:
179-
FieldValueError: If ``Opt Data Len`` on the wire is too short to hold
180-
the TaggerID it declares, which would otherwise underflow the
181-
identifier length below zero.
151+
See :func:`pcapkit.protocols.schema.internet.ipv6_option.smf_i_dpd_id_len`;
152+
errors are reported as ``HOPOPT``.
182153
183154
"""
184-
length = pkt['len'] - (1 if pkt['info']['type'] == 0 else (pkt['info']['len'] + 2))
185-
if length < 0:
186-
raise FieldValueError(f'HOPOPT: invalid SMF I-DPD option length: {pkt["len"]}')
187-
return length
155+
return ipv6_option.smf_i_dpd_id_len(pkt, prefix='HOPOPT')
188156

189157

190158
def smf_dpd_data_selector(pkt: 'dict[str, Any]') -> 'Field':
191159
"""Selector function for :attr:`_SMFDPDOption.data` field.
192160
193-
Args:
194-
pkt: Packet data.
195-
196-
Returns:
197-
* If ``mode`` is ``0``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField`
198-
wrapped :class:`~pcapkit.protocols.schema.internet.hopopt.SMFIdentificationBasedDPDOption`
199-
instance.
200-
* If ``mode`` is ``1``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField`
201-
wrapped :class:`~pcapkit.protocols.schema.internet.hopopt.SMFHashBasedDPDOption`
202-
instance.
203-
204-
Note:
205-
The field is sized ``Opt Data Len + 2`` rather than ``Opt Data Len``.
206-
``Opt Data Len`` counts only what follows the option header
207-
[:rfc:`8200#section-4.2`], while both schemas this may return inherit
208-
:attr:`Option.type` and :attr:`Option.len` and so parse those two octets
209-
themselves. Sizing the field at ``Opt Data Len`` would hand them an area two
210-
octets short of the option they read, which
211-
:class:`~pcapkit.corekit.fields.collections.OptionField` would then
212-
mis-count against the option area.
161+
See :func:`pcapkit.protocols.schema.internet.ipv6_option.smf_dpd_data_selector`,
162+
choosing among the schemas registered with :class:`SMFDPDOption`; errors
163+
are reported as ``HOPOPT``.
213164
214165
"""
215-
# NOTE: With ``Opt Data Len`` 0 the option has no data octet, so the mode
216-
# bit forward-matched above belongs to whatever follows the option.
217-
if pkt['test']['len'] == 0:
218-
raise FieldValueError('HOPOPT: invalid SMF DPD option length: 0')
219-
220-
mode = Enum_SMFDPDMode.get(pkt['test']['mode'])
221-
schema = SMFDPDOption.registry[mode]
222-
if schema is None:
223-
raise FieldValueError(f'HOPOPT: invalid SMF DPD mode: {mode}')
224-
return SchemaField(length=pkt['test']['len'] + 2, schema=schema)
166+
return ipv6_option.smf_dpd_data_selector(pkt, prefix='HOPOPT', base=SMFDPDOption)
225167

226168

227169
def smf_i_dpd_tid_selector(pkt: 'dict[str, Any]') -> 'Field':
228170
"""Selector function for :attr:`SMFIdentificationBasedDPDOption.tid` field.
229171
230-
Args:
231-
pkt: Packet data.
232-
233-
Returns:
234-
* If ``tid_type`` is ``0``, returns a :class:`~pcapkit.corekit.fields.misc.NoValueField`
235-
instance.
236-
* If ``tid_type`` is ``1``, returns a :class:`~pcapkit.corekit.fields.ipaddress.IPv4AddressField`
237-
instance.
238-
* If ``tid_type`` is ``2``, returns a :class:`~pcapkit.corekit.fields.ipaddress.IPv6AddressField`
239-
instance.
240-
* Otherwise, returns a :class:`~pcapkit.corekit.fields.strings.BytesField` instance.
172+
See :func:`pcapkit.protocols.schema.internet.ipv6_option.smf_i_dpd_tid_selector`;
173+
errors are reported as ``HOPOPT``.
241174
242175
"""
243-
tid_type = Enum_TaggerID.get(pkt['info']['type'])
244-
tid_len = pkt['info']['len']
245-
246-
# update type
247-
pkt['info']['type'] = tid_type
248-
249-
if tid_type == Enum_TaggerID.NULL:
250-
if tid_len != 0:
251-
raise FieldValueError(f'HOPOPT: invalid TaggerID length: {tid_len}')
252-
return NoValueField()
253-
if tid_type == Enum_TaggerID.IPv4:
254-
if tid_len != 3:
255-
raise FieldValueError(f'HOPOPT: invalid TaggerID length: {tid_len}')
256-
return IPv4AddressField()
257-
if tid_type == Enum_TaggerID.IPv6:
258-
if tid_len != 15:
259-
raise FieldValueError(f'HOPOPT: invalid TaggerID length: {tid_len}')
260-
return IPv6AddressField()
261-
return BytesField(length=tid_len + 1)
176+
return ipv6_option.smf_i_dpd_tid_selector(pkt, prefix='HOPOPT')
262177

263178

264179
def quick_start_data_selector(pkt: 'dict[str, Any]') -> 'Field':
265180
"""Selector function for :attr:`_QuickStartOption.data` field.
266181
267-
Args:
268-
pkt: Packet data.
269-
270-
Returns:
271-
* If ``func`` is ``0``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField`
272-
wrapped :class:`~pcapkit.protocols.schema.internet.hopopt.QuickStartRequestOption`
273-
instance.
274-
* If ``func`` is ``8``, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField`
275-
wrapped :class:`~pcapkit.protocols.schema.internet.hopopt.QuickStartReportOption`
276-
instance.
277-
* Otherwise, returns a :class:`~pcapkit.corekit.fields.misc.SchemaField`
278-
wrapped :class:`~pcapkit.protocols.schema.internet.hopopt.UnassignedOption`
279-
instance, sized by the option's own ``Opt Data Len``.
280-
281-
"""
282-
func = Enum_QSFunction.get(pkt['flags']['func'])
283-
pkt['flags']['func'] = func
284-
285-
schema = QuickStartOption.registry[func]
286-
if schema is None:
287-
# An unassigned function has no defined layout, so the option is kept
288-
# as raw data, sized by its own ``Opt Data Len``.
289-
return SchemaField(length=pkt['flags']['len'] + 2, schema=UnassignedOption)
290-
291-
# NOTE: The suboption schemas carry the option's own ``type`` and ``len``
292-
# octets, so this is the width of the whole option: the two header octets
293-
# plus the six data octets :rfc:`4782#section-3.2` gives both functions.
294-
return SchemaField(length=8, schema=schema)
295-
296-
297-
def pad_opt_data_len(pkt: 'dict[str, Any]') -> 'int':
298-
"""Return the length of the padding data of a padding option.
299-
300-
Args:
301-
pkt: Padding option unpacked schema.
302-
303-
Returns:
304-
Number of padding octets carried after the option header, i.e. the
305-
value of the ``Opt Data Len`` field of a ``PadN`` option, and zero for
306-
a ``Pad1`` option, which carries no such field.
307-
308-
Note:
309-
A ``Pad1`` option is a single octet with neither an ``Opt Data Len``
310-
field nor any option data (c.f. :rfc:`8200#section-4.2`), which is why
311-
:attr:`Option.len` is declared as a
312-
:class:`~pcapkit.corekit.fields.misc.ConditionalField` and is skipped
313-
for it. A skipped conditional field is *recorded* in the packet data as
314-
:data:`~pcapkit.corekit.fields.field.NO_VALUE`, rather than being left
315-
out of it, so the test below has to be on the **value** and not on the
316-
presence of the key: ``pkt.get('len', 0)`` on its own hands that
317-
:obj:`~pcapkit.corekit.fields.field.NoValueType` straight to
318-
:class:`~pcapkit.corekit.fields.strings.PaddingField`, where it becomes
319-
an unusable :mod:`struct` template and surfaces much later as an opaque
320-
:exc:`struct.error`, which is how a ``Pad1`` option would fail to parse.
182+
See :func:`pcapkit.protocols.schema.internet.ipv6_option.quick_start_data_selector`,
183+
choosing among the schemas registered with :class:`QuickStartOption`, and
184+
:class:`UnassignedOption` for an unassigned QS function.
321185
322186
"""
323-
length = pkt.get('len', 0)
324-
if not isinstance(length, int): # ``NO_VALUE`` (skipped) or :obj:`None` (unset)
325-
return 0
326-
return length
187+
return ipv6_option.quick_start_data_selector(pkt, base=QuickStartOption,
188+
unassigned=UnassignedOption)
327189

328190

329191
def calipso_pad_len(pkt: 'dict[str, Any]') -> 'int':
330192
"""Return CALIPSO option padding length.
331193
332-
Args:
333-
pkt: CALIPSO option unpacked schema.
334-
335-
Returns:
336-
CALIPSO option padding length.
337-
338-
Raises:
339-
FieldValueError: If ``Opt Data Len`` on the wire is too short to hold
340-
the fixed header and the compartment bitmap declared by
341-
``cmpt_len``, which would otherwise underflow the padding length
342-
below zero.
194+
See :func:`pcapkit.protocols.schema.internet.ipv6_option.calipso_pad_len`;
195+
errors are reported as ``HOPOPT``.
343196
344197
"""
345-
length = pkt['len'] - 8 - pkt['cmpt_len'] * 4
346-
if length < 0:
347-
raise FieldValueError(f'HOPOPT: invalid CALIPSO option length: {pkt["len"]}')
348-
return length
198+
return ipv6_option.calipso_pad_len(pkt, prefix='HOPOPT')
349199

350200

351201
def mpl_opt_pad_len(pkt: 'dict[str, Any]') -> 'int':
352202
"""Return MPL option padding length.
353203
354-
Args:
355-
pkt: MPL option unpacked schema.
356-
357-
Returns:
358-
MPL option padding length.
359-
360-
Raises:
361-
FieldValueError: If ``Opt Data Len`` on the wire is too short to hold
362-
the fixed header and the Seed-ID declared by ``flags.type``, which
363-
would otherwise underflow the padding length below zero.
204+
See :func:`pcapkit.protocols.schema.internet.ipv6_option.mpl_opt_pad_len`;
205+
errors are reported as ``HOPOPT``.
364206
365207
"""
366-
length = pkt['len'] - 2 - (0 if pkt['flags']['type'] == 0 else mpl_opt_seed_id_len(pkt))
367-
if length < 0:
368-
raise FieldValueError(f'HOPOPT: invalid MPL option length: {pkt["len"]}')
369-
return length
208+
return ipv6_option.mpl_opt_pad_len(pkt, prefix='HOPOPT')
370209

371210

372211
class Option(EnumSchema[Enum_Option]):

0 commit comments

Comments
 (0)