From d9d852096b59c9be62a68fb7fd73feea200159cb Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sat, 19 Sep 2026 00:52:56 -0400 Subject: [PATCH] refactor: extract duplicated wire-unit arithmetic in mh/ipv6_route/hopopt/ipv6_opts (#495) Per-class helpers for read-side length arithmetic that had been copy-pasted across many call sites -- the same pattern that caused #487 and #398. - ipv6_route.py: add ipv6_route_header_length() beside the existing ipv6_route_data_length(), replacing the 4 `header.length * 8 + 8` sites in _read_data_type_*. Finishes the read-side half of #487/#489. - mh.py: add MH._mh_message_length(), replacing the 25 identical `(header.length + 1) * 8` sites in _read_msg_*. Mirrors the write side already unified at make() (`(len(data_val) + 6) // 8 - 1`). - mh.py, hopopt.py, ipv6_opts.py: add one per-class option-length helper each (_mh_option_length, _hopopt_option_length, _ipv6_opts_option_length), replacing the 92/17/17 = 126 `.length + 2` / `.len + 2` sites in _read_opt_*. This is the exact +2/-2 mismatch #398 fixed six times independently. Deliberately left alone: the hopopt.py/ipv6_opts.py shared base class (cross-cutting, its own issue) and the TCP/IP reassembly pair, which must NOT be merged since RFC 791 and RFC 9293 mandate opposite overlap resolution. Pure refactor, no behaviour change: full protocols test suite (466 passed, 1260 subtests) and test_option_roundtrip_unit.py (6 passed, 358 subtests) are identical before and after, and construct-then-parse byte comparisons for MH/IPv6-Route/HOPOPT/IPv6-Opts match exactly pre- and post-change. --- pcapkit/protocols/internet/hopopt.py | 65 +++- pcapkit/protocols/internet/ipv6_opts.py | 65 +++- pcapkit/protocols/internet/ipv6_route.py | 9 +- pcapkit/protocols/internet/mh.py | 292 +++++++++++------- .../protocols/schema/internet/ipv6_route.py | 27 ++ 5 files changed, 303 insertions(+), 155 deletions(-) diff --git a/pcapkit/protocols/internet/hopopt.py b/pcapkit/protocols/internet/hopopt.py index eb794c191a..5a3c0d1bb8 100644 --- a/pcapkit/protocols/internet/hopopt.py +++ b/pcapkit/protocols/internet/hopopt.py @@ -449,6 +449,37 @@ def _read_opt_type(self, kind: 'int') -> 'tuple[int, bool]': bin_ = bin(kind)[2:].zfill(8) return int(bin_[:2], base=2), bool(int(bin_[2], base=2)) + @staticmethod + def _hopopt_option_length(schema_len: 'int') -> 'int': + """Compute a HOPOPT option's whole-option length from its on-the-wire ``Opt Data Len``. + + Per :rfc:`8200#section-4.3`, an option's ``Opt Data Len`` field + (what each ``Schema_*Option.len`` here holds) counts *"the length of + the Option Data field of this option, in octets"* -- i.e. it + excludes the Option Type and Opt Data Len fields themselves, so the + whole option, which is what every ``_read_opt_*`` below reports back + as the parsed option's own ``.length``, is two octets more. This is + the exact ``+2``/``-2`` mismatch #398 fixed independently in six + places (see ``Data_PadOption.length`` vs. ``Schema_PadOption.length`` + below, at the surviving explanation of that fix); collecting the + read-side half of it into one helper is so a future fix to this + arithmetic only has to happen once. Do NOT drop the ``+ 2``: that is + precisely the mismatch #398 fixed. + + Note that only the *stored-length* read-side call sites are + collected here -- most ``_make_opt_*`` methods recompute the wire + ``Opt Data Len`` from ``len(value)`` rather than reading a parsed + ``.length`` back, so they have nothing to unify against this helper. + + Args: + schema_len: raw ``Opt Data Len`` field value, as read off the wire. + + Returns: + Whole-option length, in octets, including the Type and Opt Data Len fields. + + """ + return schema_len + 2 + def _read_hopopt_options(self, length: 'int') -> 'Option': """Read HOPOPT options. @@ -509,7 +540,7 @@ def _read_opt_none(self, schema: 'Schema_UnassignedOption', *, options: 'Option' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), data=schema.data, ) return opt @@ -569,7 +600,7 @@ def _read_opt_pad(self, schema: 'Schema_PadOption', *, options: 'Option') -> 'Da if code == Enum_Option.Pad1: _size = 1 else: - _size = schema.len + 2 + _size = self._hopopt_option_length(schema.len) opt = Data_PadOption( type=schema.type, @@ -610,7 +641,7 @@ def _read_opt_tun(self, schema: 'Schema_TunnelEncapsulationLimitOption', *, opti type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), limit=schema.limit, ) return opt @@ -644,7 +675,7 @@ def _read_opt_ra(self, schema: 'Schema_RouterAlertOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), value=schema.alert, ) return opt @@ -686,7 +717,7 @@ def _read_opt_calipso(self, schema: 'Schema_CALIPSOOption', *, options: 'Option' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), domain=schema.domain, cmpt_len=schema.cmpt_len * 4, level=schema.level, @@ -753,7 +784,7 @@ def _read_opt_smf_dpd(self, schema: 'Schema_SMFDPDOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), dpd_type=mode, tid_type=tid_type, tid_len=tid_len, @@ -768,7 +799,7 @@ def _read_opt_smf_dpd(self, schema: 'Schema_SMFDPDOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), dpd_type=mode, hav=schema.hav, ) @@ -811,7 +842,7 @@ def _read_opt_pdm(self, schema: 'Schema_PDMOption', *, options: 'Option') -> 'Da type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), scaledtlr=schema.scaledtlr, scaledtls=schema.scaledtls, psntp=schema.psntp, @@ -875,7 +906,7 @@ def _read_opt_qs(self, schema: 'Schema_QuickStartOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema_req.len + 2, + length=self._hopopt_option_length(schema_req.len), func=func, rate=40000 * (2 ** rate) / 1000 if rate > 0 else 0, ttl=datetime.timedelta(seconds=schema_req.ttl), @@ -889,7 +920,7 @@ def _read_opt_qs(self, schema: 'Schema_QuickStartOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema_rep.len + 2, + length=self._hopopt_option_length(schema_rep.len), func=func, rate=40000 * (2 ** rate) / 1000 if rate > 0 else 0, nonce=schema_rep.nonce['nonce'], @@ -933,7 +964,7 @@ def _read_opt_rpl(self, schema: 'Schema_RPLOption', *, options: 'Option') -> 'Da type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), flags=Data_RPLFlags( down=bool(schema.flags['down']), rank_err=bool(schema.flags['rank_err']), @@ -994,7 +1025,7 @@ def _read_opt_mpl(self, schema: 'Schema_MPLOption', *, options: 'Option') -> 'Da type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), seed_type=kind, flags=Data_MPLFlags( max=bool(schema.flags['max']), @@ -1032,7 +1063,7 @@ def _read_opt_ilnp(self, schema: 'Schema_ILNPOption', *, options: 'Option') -> ' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), nonce=schema.nonce, ) return opt @@ -1064,7 +1095,7 @@ def _read_opt_lio(self, schema: 'Schema_LineIdentificationOption', *, options: ' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), line_id_len=schema.id_len, line_id=schema.id, ) @@ -1101,7 +1132,7 @@ def _read_opt_jumbo(self, schema: 'Schema_JumboPayloadOption', *, options: 'Opti type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), jumbo_len=schema.jumbo_len, ) return opt @@ -1145,7 +1176,7 @@ def _read_opt_home(self, schema: 'Schema_HomeAddressOption', *, options: 'Option type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), address=schema.addr, ) return opt @@ -1183,7 +1214,7 @@ def _read_opt_ip_dff(self, schema: 'Schema_IPDFFOption', *, options: 'Option') - type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._hopopt_option_length(schema.len), version=schema.flags['ver'], flags=Data_DFFFlags( dup=bool(schema.flags['dup']), diff --git a/pcapkit/protocols/internet/ipv6_opts.py b/pcapkit/protocols/internet/ipv6_opts.py index 6f5308a41f..27d5a156bb 100644 --- a/pcapkit/protocols/internet/ipv6_opts.py +++ b/pcapkit/protocols/internet/ipv6_opts.py @@ -460,6 +460,37 @@ def _read_opt_type(self, kind: 'int') -> 'tuple[int, bool]': bin_ = bin(kind)[2:].zfill(8) return int(bin_[:2], base=2), bool(int(bin_[2], base=2)) + @staticmethod + def _ipv6_opts_option_length(schema_len: 'int') -> 'int': + """Compute an IPv6-Opts option's whole-option length from its on-the-wire ``Opt Data Len``. + + Per :rfc:`8200#section-4.3`, an option's ``Opt Data Len`` field + (what each ``Schema_*Option.len`` here holds) counts *"the length of + the Option Data field of this option, in octets"* -- i.e. it + excludes the Option Type and Opt Data Len fields themselves, so the + whole option, which is what every ``_read_opt_*`` below reports back + as the parsed option's own ``.length``, is two octets more. This is + the exact ``+2``/``-2`` mismatch #398 fixed independently in six + places (see ``Data_PadOption.length`` vs. ``Schema_PadOption.length`` + below, at the surviving explanation of that fix); collecting the + read-side half of it into one helper is so a future fix to this + arithmetic only has to happen once. Do NOT drop the ``+ 2``: that is + precisely the mismatch #398 fixed. + + Note that only the *stored-length* read-side call sites are + collected here -- most ``_make_opt_*`` methods recompute the wire + ``Opt Data Len`` from ``len(value)`` rather than reading a parsed + ``.length`` back, so they have nothing to unify against this helper. + + Args: + schema_len: raw ``Opt Data Len`` field value, as read off the wire. + + Returns: + Whole-option length, in octets, including the Type and Opt Data Len fields. + + """ + return schema_len + 2 + def _read_ipv6_opts(self, length: 'int') -> 'Option': """Read IPv6-Opts options. @@ -520,7 +551,7 @@ def _read_opt_none(self, schema: 'Schema_UnassignedOption', *, options: 'Option' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), data=schema.data, ) return opt @@ -581,7 +612,7 @@ def _read_opt_pad(self, schema: 'Schema_PadOption', *, options: 'Option') -> 'Da if code == Enum_Option.Pad1: _size = 1 else: - _size = schema.len + 2 + _size = self._ipv6_opts_option_length(schema.len) opt = Data_PadOption( type=schema.type, @@ -622,7 +653,7 @@ def _read_opt_tun(self, schema: 'Schema_TunnelEncapsulationLimitOption', *, opti type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), limit=schema.limit, ) return opt @@ -656,7 +687,7 @@ def _read_opt_ra(self, schema: 'Schema_RouterAlertOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), value=schema.alert, ) return opt @@ -698,7 +729,7 @@ def _read_opt_calipso(self, schema: 'Schema_CALIPSOOption', *, options: 'Option' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), domain=schema.domain, cmpt_len=schema.cmpt_len * 4, level=schema.level, @@ -765,7 +796,7 @@ def _read_opt_smf_dpd(self, schema: 'Schema_SMFDPDOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), dpd_type=mode, tid_type=tid_type, tid_len=tid_len, @@ -780,7 +811,7 @@ def _read_opt_smf_dpd(self, schema: 'Schema_SMFDPDOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), dpd_type=mode, hav=schema.hav, ) @@ -823,7 +854,7 @@ def _read_opt_pdm(self, schema: 'Schema_PDMOption', *, options: 'Option') -> 'Da type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), scaledtlr=schema.scaledtlr, scaledtls=schema.scaledtls, psntp=schema.psntp, @@ -887,7 +918,7 @@ def _read_opt_qs(self, schema: 'Schema_QuickStartOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema_req.len + 2, + length=self._ipv6_opts_option_length(schema_req.len), func=func, rate=40000 * (2 ** rate) / 1000 if rate > 0 else 0, ttl=datetime.timedelta(seconds=schema_req.ttl), @@ -901,7 +932,7 @@ def _read_opt_qs(self, schema: 'Schema_QuickStartOption', *, options: 'Option') type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema_rep.len + 2, + length=self._ipv6_opts_option_length(schema_rep.len), func=func, rate=40000 * (2 ** rate) / 1000 if rate > 0 else 0, nonce=schema_rep.nonce['nonce'], @@ -945,7 +976,7 @@ def _read_opt_rpl(self, schema: 'Schema_RPLOption', *, options: 'Option') -> 'Da type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), flags=Data_RPLFlags( down=bool(schema.flags['down']), rank_err=bool(schema.flags['rank_err']), @@ -1006,7 +1037,7 @@ def _read_opt_mpl(self, schema: 'Schema_MPLOption', *, options: 'Option') -> 'Da type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), seed_type=kind, flags=Data_MPLFlags( max=bool(schema.flags['max']), @@ -1044,7 +1075,7 @@ def _read_opt_ilnp(self, schema: 'Schema_ILNPOption', *, options: 'Option') -> ' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), nonce=schema.nonce, ) return opt @@ -1076,7 +1107,7 @@ def _read_opt_lio(self, schema: 'Schema_LineIdentificationOption', *, options: ' type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), line_id_len=schema.id_len, line_id=schema.id, ) @@ -1113,7 +1144,7 @@ def _read_opt_jumbo(self, schema: 'Schema_JumboPayloadOption', *, options: 'Opti type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), jumbo_len=schema.jumbo_len, ) return opt @@ -1157,7 +1188,7 @@ def _read_opt_home(self, schema: 'Schema_HomeAddressOption', *, options: 'Option type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), address=schema.addr, ) return opt @@ -1195,7 +1226,7 @@ def _read_opt_ip_dff(self, schema: 'Schema_IPDFFOption', *, options: 'Option') - type=schema.type, action=Enum_OptionAction.get(schema.type >> 6), change=bool(schema.type & 0b00100000), - length=schema.len + 2, + length=self._ipv6_opts_option_length(schema.len), version=schema.flags['ver'], flags=Data_DFFFlags( dup=bool(schema.flags['dup']), diff --git a/pcapkit/protocols/internet/ipv6_route.py b/pcapkit/protocols/internet/ipv6_route.py index 9a3345314c..b590855e9e 100644 --- a/pcapkit/protocols/internet/ipv6_route.py +++ b/pcapkit/protocols/internet/ipv6_route.py @@ -42,6 +42,7 @@ from pcapkit.protocols.schema.internet.ipv6_route import Type2 as Schema_Type2 from pcapkit.protocols.schema.internet.ipv6_route import UnknownType as Schema_UnknownType from pcapkit.protocols.schema.internet.ipv6_route import ipv6_route_data_length +from pcapkit.protocols.schema.internet.ipv6_route import ipv6_route_header_length from pcapkit.protocols.schema.schema import Schema from pcapkit.utilities.exceptions import ProtocolError, UnsupportedCall from pcapkit.utilities.warnings import RegistryWarning, warn @@ -444,7 +445,7 @@ def _read_data_type_none(self, schema: 'Schema_UnknownType', *, header: 'Schema_ """ ipv6_route = Data_UnknownType( next=header.next, - length=header.length * 8 + 8, + length=ipv6_route_header_length(header.length), type=header.type, seg_left=header.seg_left, data=schema.data, @@ -511,7 +512,7 @@ def _read_data_type_src(self, schema: 'Schema_SourceRoute', *, header: 'Schema_I ipv6_route = Data_SourceRoute( next=header.next, - length=header.length * 8 + 8, + length=ipv6_route_header_length(header.length), type=header.type, seg_left=header.seg_left, ip=tuple(schema.ip), @@ -559,7 +560,7 @@ def _read_data_type_2(self, schema: 'Schema_Type2', *, header: 'Schema_IPv6_Rout ipv6_route = Data_Type2( next=header.next, - length=header.length * 8 + 8, + length=ipv6_route_header_length(header.length), type=header.type, seg_left=header.seg_left, ip=schema.ip, @@ -613,7 +614,7 @@ def _read_data_type_rpl(self, schema: 'Schema_RPL', *, header: 'Schema_IPv6_Rout ipv6_route = Data_RPL( next=header.next, - length=header.length * 8 + 8, + length=ipv6_route_header_length(header.length), type=header.type, seg_left=header.seg_left, cmpr_i=schema.cmpr_i, diff --git a/pcapkit/protocols/internet/mh.py b/pcapkit/protocols/internet/mh.py index dde59639fc..4d32670d89 100644 --- a/pcapkit/protocols/internet/mh.py +++ b/pcapkit/protocols/internet/mh.py @@ -1368,6 +1368,33 @@ def read(self, length: 'Optional[int]' = None, *, version: 'Literal[4, 6]' = 4, return mh return self._decode_next_layer(mh, schema.next, length - mh.length) + @staticmethod + def _mh_message_length(header_len: 'int') -> 'int': + """Compute the total on-the-wire MH header length for a given ``Header Len``. + + Per :rfc:`6275#section-6.1.1`, ``Header Len`` is *"the length of the + Mobility Header, in units of 8 octets, excluding the first 8 + octets"* -- i.e. the total header is ``8 + 8 * header_len`` octets, + or equivalently ``(header_len + 1) * 8``. Every ``_read_msg_*`` + below reports this value back as the parsed message's own + ``.length``, which :meth:`~pcapkit.protocols.internet.mh.MH.read` + then subtracts from the outer packet length to find the next + layer's length -- precisely the role ``Hdr Ext Len`` played in + #487, and the same read-side duplication :meth:`make`'s write-side + expression (``(len(data_val) + 6) // 8 - 1``, this formula's + inverse) had already been unified out of. Do NOT drop the ``+ 1``: + the units either side of it differ (octets vs. 8-octet units), and + dropping the offset silently reinterprets the field. + + Args: + header_len: raw ``Header Len`` field value, as read off the wire. + + Returns: + Total length, in octets, of the on-the-wire MH header. + + """ + return (header_len + 1) * 8 + def make(self, next: 'Enum_TransType | StdlibEnum | AenumEnum | str | int' = Enum_TransType.UDP, next_default: 'Optional[int]' = None, @@ -1565,7 +1592,7 @@ def _read_msg_unknown(self, schema: 'Schema_UnknownMessage', *, """ data = Data_UnknownMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, data=schema.data, @@ -1600,7 +1627,7 @@ def _read_msg_brr(self, schema: 'Schema_BindingRefreshRequestMessage', *, """ data = Data_BindingRefreshRequestMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, options=self._read_mh_options(schema.options) @@ -1639,7 +1666,7 @@ def _read_msg_hoti(self, schema: 'Schema_HomeTestInitMessage', *, """ data = Data_HomeTestInitMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, cookie=schema.cookie, @@ -1679,7 +1706,7 @@ def _read_msg_coti(self, schema: 'Schema_CareofTestInitMessage', *, """ data = Data_CareofTestInitMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, cookie=schema.cookie, @@ -1723,7 +1750,7 @@ def _read_msg_hot(self, schema: 'Schema_HomeTestMessage', *, """ data = Data_HomeTestMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, nonce_index=schema.nonce_index, @@ -1769,7 +1796,7 @@ def _read_msg_cot(self, schema: 'Schema_CareofTestMessage', *, """ data = Data_CareofTestMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, nonce_index=schema.nonce_index, @@ -1809,7 +1836,7 @@ def _read_msg_bu(self, schema: 'Schema_BindingUpdateMessage', *, """ data = Data_BindingUpdateMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -1852,7 +1879,7 @@ def _read_msg_ba(self, schema: 'Schema_BindingAcknowledgementMessage', *, """ data = Data_BindingAcknowledgementMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, status=schema.status, @@ -1898,7 +1925,7 @@ def _read_msg_be(self, schema: 'Schema_BindingErrorMessage', *, """ data = Data_BindingErrorMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, status=schema.status, @@ -1945,7 +1972,7 @@ def _read_msg_fbu(self, schema: 'Schema_FastBindingUpdateMessage', *, """ data = Data_FastBindingUpdateMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2002,7 +2029,7 @@ def _read_msg_fback(self, schema: 'Schema_FastBindingAcknowledgmentMessage', *, """ data = Data_FastBindingAcknowledgmentMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, status=FastBindingAcknowledgmentStatus(schema.status), @@ -2047,7 +2074,7 @@ def _read_msg_fna(self, schema: 'Schema_FastNeighborAdvertisementMessage', *, """ data = Data_FastNeighborAdvertisementMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, options=self._read_mh_options(schema.options), @@ -2090,7 +2117,7 @@ def _read_msg_emh(self, schema: 'Schema_ExperimentalMessage', *, """ data = Data_ExperimentalMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, data=schema.data, @@ -2133,7 +2160,7 @@ def _read_msg_hi(self, schema: 'Schema_HandoverInitiateMessage', *, """ data = Data_HandoverInitiateMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2183,7 +2210,7 @@ def _read_msg_hack(self, schema: 'Schema_HandoverAcknowledgeMessage', *, """ data = Data_HandoverAcknowledgeMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2243,7 +2270,7 @@ def _read_msg_has(self, schema: 'Schema_HomeAgentSwitchMessage', *, data = Data_HomeAgentSwitchMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, addresses=tuple(schema.addresses), @@ -2287,7 +2314,7 @@ def _read_msg_hb(self, schema: 'Schema_HeartbeatMessage', *, """ data = Data_HeartbeatMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, unsolicited=bool(schema.flags['U']), @@ -2354,7 +2381,7 @@ def _read_msg_brm(self, schema: 'Schema_BindingRevocationMessage', *, """ data = Data_BindingRevocationMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, br_type=schema.br_type, @@ -2403,7 +2430,7 @@ def _read_msg_lri(self, schema: 'Schema_LocalizedRoutingInitiationMessage', *, """ data = Data_LocalizedRoutingInitiationMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2449,7 +2476,7 @@ def _read_msg_lra(self, schema: 'Schema_LocalizedRoutingAcknowledgmentMessage', """ data = Data_LocalizedRoutingAcknowledgmentMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2494,7 +2521,7 @@ def _read_msg_upn(self, schema: 'Schema_UpdateNotificationMessage', *, """ data = Data_UpdateNotificationMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2536,7 +2563,7 @@ def _read_msg_upa(self, schema: 'Schema_UpdateNotificationAcknowledgementMessage """ data = Data_UpdateNotificationAcknowledgementMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2598,7 +2625,7 @@ def _read_msg_fbm(self, schema: 'Schema_FlowBindingMessage', *, """ data = Data_FlowBindingMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, fb_type=schema.fb_type, @@ -2642,7 +2669,7 @@ def _read_msg_sq(self, schema: 'Schema_SubscriptionQueryMessage', *, """ data = Data_SubscriptionQueryMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2679,7 +2706,7 @@ def _read_msg_sr(self, schema: 'Schema_SubscriptionResponseMessage', *, """ data = Data_SubscriptionResponseMessage( next=header.next, - length=(header.length + 1) * 8, + length=self._mh_message_length(header.length), type=header.type, chksum=header.chksum, seq=schema.seq, @@ -2688,6 +2715,37 @@ def _read_msg_sr(self, schema: 'Schema_SubscriptionResponseMessage', *, ) return data + @staticmethod + def _mh_option_length(schema_length: 'int') -> 'int': + """Compute an MH option's whole-option length from its on-the-wire ``Option Length``. + + Per :rfc:`6275#section-6.2`, an MH option's ``Option Length`` field + (what each ``Schema_*Option.length`` here holds) counts *"the length + of the option, in octets, excluding the Option Type and Option + Length fields"* -- so the whole option, which is what every + ``_read_opt_*`` below reports back as the parsed option's own + ``.length``, is two octets more. This is the exact ``+2``/``-2`` + mismatch #398 fixed independently in six places (see + ``Data_PadOption.length`` vs. ``Schema_PadOption.length`` below, at + the surviving explanation of that fix); collecting the read-side + half of it into one helper is so a future fix to this arithmetic + only has to happen once. Do NOT drop the ``+ 2``: that is precisely + the mismatch #398 fixed. + + Note that only the *stored-length* read-side call sites are + collected here -- most ``_make_opt_*`` methods recompute the wire + ``Option Length`` from ``len(value)`` rather than reading a parsed + ``.length`` back, so they have nothing to unify against this helper. + + Args: + schema_length: raw ``Option Length`` field value, as read off the wire. + + Returns: + Whole-option length, in octets, including the Type and Length fields. + + """ + return schema_length + 2 + def _read_mh_options(self, options_schema: 'list[Schema_Option]') -> 'Option': """Read MH options. @@ -2741,7 +2799,7 @@ def _read_opt_none(self, schema: 'Schema_UnassignedOption', *, """ data = Data_UnassignedOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), data=schema.data, ) return data @@ -2834,7 +2892,7 @@ def _read_opt_bra(self, schema: 'Schema_BindingRefreshAdviceOption', *, data = Data_BindingRefreshAdviceOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), interval=datetime.timedelta(seconds=schema.interval * 4), ) return data @@ -2874,7 +2932,7 @@ def _read_opt_aca(self, schema: 'Schema_AlternateCareofAddressOption', *, data = Data_AlternateCareofAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=schema.address, ) return data @@ -2908,7 +2966,7 @@ def _read_opt_ni(self, schema: 'Schema_NonceIndicesOption', *, data = Data_NonceIndicesOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), home=schema.home, careof=schema.careof, ) @@ -2947,7 +3005,7 @@ def _read_opt_bad(self, schema: 'Schema_AuthorizationDataOption', *, data = Data_AuthorizationDataOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), data=schema.data, ) return data @@ -2990,7 +3048,7 @@ def _read_opt_mnp(self, schema: 'Schema_MobileNetworkPrefixOption', *, data = Data_MobileNetworkPrefixOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), prefix=prefix, ) return data @@ -3024,7 +3082,7 @@ def _read_opt_lla(self, schema: 'Schema_LinkLayerAddressOption', *, data = Data_LinkLayerAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), code=schema.code, lla=schema.lla, ) @@ -3056,7 +3114,7 @@ def _read_opt_mn_id(self, schema: 'Schema_MNIDOption', *, """ data = Data_MNIDOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), subtype=schema.subtype, identifier=schema.identifier, ) @@ -3093,7 +3151,7 @@ def _read_opt_auth(self, schema: 'Schema_AuthOption', *, data = Data_AuthOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), subtype=schema.subtype, spi=schema.spi, data=schema.data, @@ -3131,7 +3189,7 @@ def _read_opt_mesg_id(self, schema: 'Schema_MesgIDOption', *, data = Data_MesgIDOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), timestamp=schema.timestamp, ntp_timestamp=NTPTimestamp(schema.seconds, schema.fraction), ) @@ -3164,7 +3222,7 @@ def _read_opt_cga_pr(self, schema: 'Schema_CGAParametersRequestOption', *, data = Data_CGAParametersRequestOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), ) return data @@ -3202,7 +3260,7 @@ def _read_opt_cga_param(self, schema: 'Schema_CGAParametersOption', *, data = Data_CGAParametersOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), parameters=tuple(Data_CGAParameter( modifier=param.modifier, prefix=param.prefix, @@ -3243,7 +3301,7 @@ def _read_opt_signature(self, schema: 'Schema_SignatureOption', *, """ data = Data_SignatureOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), signature=schema.signature, ) return data @@ -3278,7 +3336,7 @@ def _read_opt_phkt(self, schema: 'Schema_PermanentHomeKeygenTokenOption', *, """ data = Data_PermanentHomeKeygenTokenOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), token=schema.token, ) return data @@ -3310,7 +3368,7 @@ def _read_opt_ct_init(self, schema: 'Schema_CareofTestInitOption', *, data = Data_CareofTestInitOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), ) return data @@ -3345,7 +3403,7 @@ def _read_opt_ct(self, schema: 'Schema_CareofTestOption', *, data = Data_CareofTestOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), token=schema.token, ) return data @@ -3374,7 +3432,7 @@ def _read_opt_exp(self, schema: 'Schema_ExperimentalMobilityOption', *, """ data = Data_ExperimentalMobilityOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), data=schema.data, ) return data @@ -3484,7 +3542,7 @@ def _read_opt_ipv6_ap(self, schema: 'Schema_IPv6AddressPrefixOption', *, data = Data_IPv6AddressPrefixOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), code=IPv6AddressPrefixCode(schema.code), prefix_length=schema.prefix_length, address=schema.address, @@ -3531,7 +3589,7 @@ def _read_opt_dns(self, schema: 'Schema_DNSUpdateOption', *, data = Data_DNSUpdateOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), status=schema.status, remove=bool(schema.flags['R']), identity=schema.identity, @@ -3575,7 +3633,7 @@ def _read_opt_vendor(self, schema: 'Schema_VendorSpecificOption', *, data = Data_VendorSpecificOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), vendor=schema.vendor, subtype=schema.subtype, data=schema.data, @@ -3616,7 +3674,7 @@ def _read_opt_service(self, schema: 'Schema_ServiceSelectionOption', *, data = Data_ServiceSelectionOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), identifier=schema.identifier, ) return data @@ -3658,7 +3716,7 @@ def _read_opt_hnp(self, schema: 'Schema_HomeNetworkPrefixOption', *, data = Data_HomeNetworkPrefixOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), prefix_length=schema.prefix_length, prefix=schema.prefix, ) @@ -3691,7 +3749,7 @@ def _read_opt_hi(self, schema: 'Schema_HandoffIndicatorOption', *, data = Data_HandoffIndicatorOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), hi=schema.hi, ) return data @@ -3729,7 +3787,7 @@ def _read_opt_att(self, schema: 'Schema_AccessTechnologyTypeOption', *, data = Data_AccessTechnologyTypeOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), att=schema.att, ) return data @@ -3774,7 +3832,7 @@ def _read_opt_mn_lli(self, schema: 'Schema_MNLLIdentifierOption', *, data = Data_MNLLIdentifierOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), lli=schema.lli, ) return data @@ -3820,7 +3878,7 @@ def _read_opt_lla_addr(self, schema: 'Schema_LinkLocalAddressOption', *, data = Data_LinkLocalAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=schema.address, ) return data @@ -3868,7 +3926,7 @@ def _read_opt_timestamp(self, schema: 'Schema_TimestampOption', *, data = Data_TimestampOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), timestamp=datetime.datetime.fromtimestamp( seconds + fraction / 65536, tz=datetime.timezone.utc), pmip_timestamp=PMIPv6Timestamp(seconds, fraction), @@ -3910,7 +3968,7 @@ def _read_opt_restart(self, schema: 'Schema_RestartCounterOption', *, data = Data_RestartCounterOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), counter=schema.counter, ) return data @@ -3952,7 +4010,7 @@ def _read_opt_ipv4_hoa(self, schema: 'Schema_IPv4HomeAddressOption', *, data = Data_IPv4HomeAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), prefix_length=prefix_length, address=schema.address, request_prefix=bool(schema.flags['P']), @@ -3999,7 +4057,7 @@ def _read_opt_ipv4_ack(self, schema: 'Schema_IPv4AddressAcknowledgementOption', data = Data_IPv4AddressAcknowledgementOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), status=schema.status, prefix_length=schema.flags['prefix_length'], address=schema.address, @@ -4041,7 +4099,7 @@ def _read_opt_nat(self, schema: 'Schema_NATDetectionOption', *, data = Data_NATDetectionOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), force=bool(schema.flags['F']), refresh=datetime.timedelta(seconds=schema.refresh), ) @@ -4081,7 +4139,7 @@ def _read_opt_ipv4_coa(self, schema: 'Schema_IPv4CareofAddressOption', *, data = Data_IPv4CareofAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=schema.address, ) return data @@ -4121,7 +4179,7 @@ def _read_opt_gre(self, schema: 'Schema_GREKeyOption', *, data = Data_GREKeyOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), key=schema.key if schema.length == 6 else None, ) return data @@ -4171,7 +4229,7 @@ def _read_opt_bid(self, schema: 'Schema_BindingIdentifierOption', *, data = Data_BindingIdentifierOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), bid=schema.bid, status=schema.status, simultaneous=bool(schema.flags['H']), @@ -4215,7 +4273,7 @@ def _read_opt_ipv4_hoa_req(self, schema: 'Schema_IPv4HomeAddressRequestOption', data = Data_IPv4HomeAddressRequestOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), prefix_length=schema.flags['prefix_length'], address=schema.address, ) @@ -4256,7 +4314,7 @@ def _read_opt_ipv4_hoa_rep(self, schema: 'Schema_IPv4HomeAddressReplyOption', *, data = Data_IPv4HomeAddressReplyOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), status=schema.status, prefix_length=schema.flags['prefix_length'], address=schema.address, @@ -4293,7 +4351,7 @@ def _read_opt_ipv4_router(self, schema: 'Schema_IPv4DefaultRouterAddressOption', data = Data_IPv4DefaultRouterAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=schema.address, ) return data @@ -4330,7 +4388,7 @@ def _read_opt_ipv4_dhcp(self, schema: 'Schema_IPv4DHCPSupportModeOption', *, data = Data_IPv4DHCPSupportModeOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), mode=Enum_DHCPSupportMode(schema.flags['S']), ) return data @@ -4394,7 +4452,7 @@ def _read_opt_cr(self, schema: 'Schema_ContextRequestOption', *, data = Data_ContextRequestOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), requests=tuple(requests), ) return data @@ -4434,7 +4492,7 @@ def _read_opt_lmaa(self, schema: 'Schema_LMAAddressOption', *, data = Data_LMAAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), code=LMAAddressCode(schema.code), address=schema.address, ) @@ -4472,7 +4530,7 @@ def _read_opt_mn_lla_iid(self, schema: 'Schema_MNLLAIIDOption', *, data = Data_MNLLAIIDOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), iid=schema.iid, ) return data @@ -4510,7 +4568,7 @@ def _read_opt_transient(self, schema: 'Schema_TransientBindingOption', *, data = Data_TransientBindingOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), late=bool(schema.flags['L']), lifetime=datetime.timedelta(milliseconds=schema.lifetime * 100), ) @@ -4552,7 +4610,7 @@ def _read_opt_fs(self, schema: 'Schema_FlowSummaryOption', *, data = Data_FlowSummaryOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), fid=tuple(schema.fid), ) return data @@ -4624,7 +4682,7 @@ def _read_fid_suboptions( if code in (Enum_FlowIDSuboption.Pad, Enum_FlowIDSuboption.PadN): pad = cast('Schema_PadFlowIdentificationSuboption', schema) - size = 1 if code == Enum_FlowIDSuboption.Pad else pad.length + 2 + size = 1 if code == Enum_FlowIDSuboption.Pad else self._mh_option_length(pad.length) data = Data_PadFlowIdentificationSuboption( type=code, length=size, @@ -4633,14 +4691,14 @@ def _read_fid_suboptions( bid_ref = cast('Schema_BIDReferenceSuboption', schema) data = Data_BIDReferenceSuboption( type=code, - length=bid_ref.length + 2, + length=self._mh_option_length(bid_ref.length), bid=tuple(bid_ref.bid), ) elif code == Enum_FlowIDSuboption.Traffic_Selector: selector = cast('Schema_TrafficSelectorSuboption', schema) data = Data_TrafficSelectorSuboption( type=code, - length=selector.length + 2, + length=self._mh_option_length(selector.length), ts_format=selector.ts_format, selector=selector.selector, ) @@ -4648,21 +4706,21 @@ def _read_fid_suboptions( action = cast('Schema_FlowBindingActionSuboption', schema) data = Data_FlowBindingActionSuboption( type=code, - length=action.length + 2, + length=self._mh_option_length(action.length), action=action.action, ) elif code == Enum_FlowIDSuboption.Target_Care_of_Address: target = cast('Schema_TargetCareofAddressSuboption', schema) data = Data_TargetCareofAddressSuboption( type=code, - length=target.length + 2, + length=self._mh_option_length(target.length), address=target.address, ) else: unknown = cast('Schema_UnassignedFlowIdentificationSuboption', schema) data = Data_UnassignedFlowIdentificationSuboption( type=code, - length=unknown.length + 2, + length=self._mh_option_length(unknown.length), data=unknown.data, ) @@ -4708,7 +4766,7 @@ def _read_opt_fid(self, schema: 'Schema_FlowIdentificationOption', *, data = Data_FlowIdentificationOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), fid=schema.fid, fid_pri=schema.fid_pri, status=schema.status, @@ -4744,7 +4802,7 @@ def _read_opt_rc(self, schema: 'Schema_RedirectCapabilityOption', *, data = Data_RedirectCapabilityOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), ) return data @@ -4794,7 +4852,7 @@ def _read_opt_redirect(self, schema: 'Schema_RedirectOption', *, data = Data_RedirectOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), ipv6=schema.ipv6, ipv4=schema.ipv4, ) @@ -4840,7 +4898,7 @@ def _read_opt_load(self, schema: 'Schema_LoadInformationOption', *, data = Data_LoadInformationOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), priority=schema.priority, sessions_in_use=schema.sessions_in_use, max_sessions=schema.max_sessions, @@ -4878,7 +4936,7 @@ def _read_opt_alt_ipv4_coa(self, schema: 'Schema_AlternateIPv4CareofAddressOptio data = Data_AlternateIPv4CareofAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=schema.address, ) return data @@ -4913,7 +4971,7 @@ def _read_opt_mn_group(self, schema: 'Schema_MNGroupIdentifierOption', *, data = Data_MNGroupIdentifierOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), subtype=schema.subtype, group_id=schema.group_id, ) @@ -4959,7 +5017,7 @@ def _read_opt_mag_addr(self, schema: 'Schema_MAGIPv6AddressOption', *, data = Data_MAGIPv6AddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address_length=schema.address_length, address=schema.address, ) @@ -5009,7 +5067,7 @@ def _read_ani_suboptions( net = cast('Schema_ANINetworkIdentifierSuboption', schema) data = Data_ANINetworkIdentifierSuboption( type=code, - length=net.length + 2, + length=self._mh_option_length(net.length), utf8=bool(net.flags['E']), net_name=net.net_name, ap_name=net.ap_name, @@ -5020,7 +5078,7 @@ def _read_ani_suboptions( raw_lon = self._decode_signed(geo.location['longitude'], 24) data = Data_ANIGeoLocationSuboption( type=code, - length=geo.length + 2, + length=self._mh_option_length(geo.length), latitude=raw_lat / 2 ** 15, longitude=raw_lon / 2 ** 15, raw_latitude=raw_lat, @@ -5030,7 +5088,7 @@ def _read_ani_suboptions( operator = cast('Schema_ANIOperatorIdentifierSuboption', schema) data = Data_ANIOperatorIdentifierSuboption( type=code, - length=operator.length + 2, + length=self._mh_option_length(operator.length), op_id_type=operator.op_id_type, identifier=operator.identifier, ) @@ -5038,7 +5096,7 @@ def _read_ani_suboptions( civic = cast('Schema_ANICivicLocationSuboption', schema) data = Data_ANICivicLocationSuboption( type=code, - length=civic.length + 2, + length=self._mh_option_length(civic.length), format=civic.format, location=civic.location, ) @@ -5046,21 +5104,21 @@ def _read_ani_suboptions( group = cast('Schema_ANIMAGGroupIdentifierSuboption', schema) data = Data_ANIMAGGroupIdentifierSuboption( type=code, - length=group.length + 2, + length=self._mh_option_length(group.length), group_id=group.group_id, ) elif code == Enum_ANISuboption.ANI_Update_Timer: timer = cast('Schema_ANIUpdateTimerSuboption', schema) data = Data_ANIUpdateTimerSuboption( type=code, - length=timer.length + 2, + length=self._mh_option_length(timer.length), timer=datetime.timedelta(seconds=timer.timer * 4), ) else: unknown = cast('Schema_UnassignedANISuboption', schema) data = Data_UnassignedANISuboption( type=code, - length=unknown.length + 2, + length=self._mh_option_length(unknown.length), data=unknown.data, ) @@ -5128,7 +5186,7 @@ def _read_opt_ani(self, schema: 'Schema_AccessNetworkIdentifierOption', *, data = Data_AccessNetworkIdentifierOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), suboptions=self._read_ani_suboptions(schema.suboptions), ) return data @@ -5172,7 +5230,7 @@ def _read_opt_offload(self, schema: 'Schema_IPv4TrafficOffloadSelectorOption', * data = Data_IPv4TrafficOffloadSelectorOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), mode=bool(schema.flags['M']), selector=self._read_fid_suboptions(schema.selector), ) @@ -5226,7 +5284,7 @@ def _read_opt_mcast_sel(self, schema: 'Schema_DynamicIPMulticastSelectorOption', data = Data_DynamicIPMulticastSelectorOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), protocol=schema.protocol, mode=bool(schema.flags['M']), records=schema.records, @@ -5279,7 +5337,7 @@ def _read_opt_dmnp(self, schema: 'Schema_DelegatedMNPOption', *, data = Data_DelegatedMNPOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), ipv4=ipv4, prefix_length=schema.prefix_length, prefix=schema.prefix, @@ -5325,7 +5383,7 @@ def _read_opt_ams4(self, schema: 'Schema_ActiveMulticastSubscriptionIPv4Option', data = Data_ActiveMulticastSubscriptionIPv4Option( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), igmp_type=schema.igmp_type, context=schema.context, ) @@ -5369,7 +5427,7 @@ def _read_opt_ams6(self, schema: 'Schema_ActiveMulticastSubscriptionIPv6Option', data = Data_ActiveMulticastSubscriptionIPv6Option( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), mld_type=schema.mld_type, context=schema.context, ) @@ -5416,7 +5474,7 @@ def _read_qos_attributes( session = cast('Schema_PerSessionBitRateAttribute', schema) data = Data_PerSessionBitRateAttribute( type=code, - length=session.length + 2, + length=self._mh_option_length(session.length), service=bool(session.flags['S']), exclude=bool(session.flags['E']), rate=session.rate, @@ -5430,14 +5488,14 @@ def _read_qos_attributes( rate = cast('Schema_BitRateAttribute', schema) data = Data_BitRateAttribute( type=code, - length=rate.length + 2, + length=self._mh_option_length(rate.length), rate=rate.rate, ) elif code == Enum_QoSAttribute.Allocation_Retention_Priority: arp = cast('Schema_AllocationRetentionPriorityAttribute', schema) data = Data_AllocationRetentionPriorityAttribute( type=code, - length=arp.length + 2, + length=self._mh_option_length(arp.length), priority_level=arp.priority['PL'], preemption_capability=arp.priority['PC'], preemption_vulnerability=arp.priority['PV'], @@ -5446,7 +5504,7 @@ def _read_qos_attributes( selector = cast('Schema_QoSTrafficSelectorAttribute', schema) data = Data_QoSTrafficSelectorAttribute( type=code, - length=selector.length + 2, + length=self._mh_option_length(selector.length), ts_format=selector.ts_format, selector=selector.selector, ) @@ -5454,7 +5512,7 @@ def _read_qos_attributes( vendor = cast('Schema_QoSVendorSpecificAttribute', schema) data = Data_QoSVendorSpecificAttribute( type=code, - length=vendor.length + 2, + length=self._mh_option_length(vendor.length), vendor=vendor.vendor, subtype=vendor.subtype, data=vendor.data, @@ -5463,7 +5521,7 @@ def _read_qos_attributes( unknown = cast('Schema_UnassignedQoSAttribute', schema) data = Data_UnassignedQoSAttribute( type=code, - length=unknown.length + 2, + length=self._mh_option_length(unknown.length), data=unknown.data, ) @@ -5507,7 +5565,7 @@ def _read_opt_qos(self, schema: 'Schema_QualityOfServiceOption', *, data = Data_QualityOfServiceOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), sr_id=schema.sr_id, dscp=schema.tc >> 2, oc=schema.oc, @@ -5557,7 +5615,7 @@ def _read_opt_lma_up(self, schema: 'Schema_LMAUserPlaneAddressOption', *, data = Data_LMAUserPlaneAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=cast('Optional[IPv4Address | IPv6Address]', schema.address if schema.length != 2 else None), ) @@ -5692,7 +5750,7 @@ def _read_lcmp_suboptions( rereg = cast('Schema_BindingReregistrationControlSuboption', schema) data = Data_BindingReregistrationControlSuboption( type=code, - length=rereg.length + 2, + length=self._mh_option_length(rereg.length), start_time=datetime.timedelta(seconds=rereg.start_time * 4), initial_retransmission=datetime.timedelta( seconds=rereg.initial_retransmission), @@ -5702,7 +5760,7 @@ def _read_lcmp_suboptions( heartbeat = cast('Schema_HeartbeatControlSuboption', schema) data = Data_HeartbeatControlSuboption( type=code, - length=heartbeat.length + 2, + length=self._mh_option_length(heartbeat.length), interval=datetime.timedelta(seconds=heartbeat.interval), retransmission_delay=datetime.timedelta( seconds=heartbeat.retransmission_delay), @@ -5712,7 +5770,7 @@ def _read_lcmp_suboptions( unknown = cast('Schema_UnassignedLMAControlledMAGSuboption', schema) data = Data_UnassignedLMAControlledMAGSuboption( type=code, - length=unknown.length + 2, + length=self._mh_option_length(unknown.length), data=unknown.data, ) @@ -5756,7 +5814,7 @@ def _read_opt_lcmp(self, schema: 'Schema_LMAControlledMAGParametersOption', *, data = Data_LMAControlledMAGParametersOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), suboptions=self._read_lcmp_suboptions(schema.suboptions), ) return data @@ -5800,7 +5858,7 @@ def _read_opt_mag_mp(self, schema: 'Schema_MAGMultipathBindingOption', *, data = Data_MAGMultipathBindingOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), att=schema.att, label=schema.label, bid=schema.bid, @@ -5844,7 +5902,7 @@ def _read_opt_mag_id(self, schema: 'Schema_MAGIdentifierOption', *, data = Data_MAGIdentifierOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), subtype=schema.subtype, identifier=schema.identifier, ) @@ -5887,7 +5945,7 @@ def _read_opt_anchored(self, schema: 'Schema_AnchoredPrefixOption', *, data = Data_AnchoredPrefixOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), prefix_length=schema.prefix_length, prefix=schema.prefix, ) @@ -5930,7 +5988,7 @@ def _read_opt_local_prefix(self, schema: 'Schema_LocalPrefixOption', *, data = Data_LocalPrefixOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), prefix_length=schema.prefix_length, prefix=schema.prefix, ) @@ -5986,7 +6044,7 @@ def _read_opt_prev_maar(self, schema: 'Schema_PreviousMAAROption', *, data = Data_PreviousMAAROption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), prefix_length=schema.prefix_length, maar=schema.maar, prefix=schema.prefix, @@ -6033,7 +6091,7 @@ def _read_opt_serv_maar(self, schema: 'Schema_ServingMAAROption', *, data = Data_ServingMAAROption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=schema.address, ) return data @@ -6073,7 +6131,7 @@ def _read_opt_dlif_lla(self, schema: 'Schema_DLIFLinkLocalAddressOption', *, data = Data_DLIFLinkLocalAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), address=schema.address, ) return data @@ -6116,7 +6174,7 @@ def _read_opt_dlif_lladdr(self, schema: 'Schema_DLIFLinkLayerAddressOption', *, data = Data_DLIFLinkLayerAddressOption( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), lla=schema.lla, ) return data @@ -6178,7 +6236,7 @@ def _read_ext_none(self, schema: 'Schema_UnknownExtension', *, """ data = Data_UnknownExtension( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), data=schema.data, ) return data @@ -6225,7 +6283,7 @@ def _read_ext_multiprefix(self, schema: 'Schema_MultiPrefixExtension', *, """ data = Data_MultiPrefixExtension( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), flag=bool(schema.flags['P']), prefixes=tuple(schema.prefixes), ) @@ -6270,7 +6328,7 @@ def _read_ext_exp(self, schema: 'Schema_ExperimentalExtension', *, """ data = Data_ExperimentalExtension( type=schema.type, - length=schema.length + 2, + length=self._mh_option_length(schema.length), data=schema.data, ) return data diff --git a/pcapkit/protocols/schema/internet/ipv6_route.py b/pcapkit/protocols/schema/internet/ipv6_route.py index 6374ad3399..d12a73e0c2 100644 --- a/pcapkit/protocols/schema/internet/ipv6_route.py +++ b/pcapkit/protocols/schema/internet/ipv6_route.py @@ -62,6 +62,33 @@ def ipv6_route_data_length(hdr_ext_len: 'int') -> 'int': return 4 + hdr_ext_len * 8 +def ipv6_route_header_length(hdr_ext_len: 'int') -> 'int': + """Total length, in octets, of the on-the-wire IPv6-Route header for a given ``Hdr Ext Len``. + + This is the fixed 4 octets of ``next``/``length``/``type``/``seg_left`` + plus the type-specific data computed by :func:`ipv6_route_data_length`, + i.e. ``4 + (4 + 8 * hdr_ext_len)`` -- a *different* quantity from + :func:`ipv6_route_data_length` itself (``8 + 8 * hdr_ext_len`` here vs. + ``4 + 8 * hdr_ext_len`` there), not a duplicate of it. It is what each + ``_read_data_type_*`` in + :mod:`pcapkit.protocols.internet.ipv6_route` reports back as the parsed + route data's own ``.length``, which :meth:`~pcapkit.protocols.internet. + ipv6_route.IPv6_Route.read` then subtracts from the outer packet length + to find the next layer's length. #489 unified the write side + (:meth:`~pcapkit.protocols.internet.ipv6_route.IPv6_Route._make_hdr_ext_len`) + into one helper; this is the matching read-side helper for the total + header length, finishing that half of #487/#489. + + Args: + hdr_ext_len: raw ``Hdr Ext Len`` field value, as read off the wire. + + Returns: + Total length, in octets, of the on-the-wire IPv6-Route header. + + """ + return 4 + ipv6_route_data_length(hdr_ext_len) + + def ipv6_route_data_selector(pkt: 'dict[str, Any]') -> 'Field': """Selector function for :attr:`IPv6_Route.data` field.