Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ This is the resolution of #548, which reported `TransType.L2TP` (115) as registe
- **Fixed** -- the one assertion #604 left pinning the old padding side, which had been red on `mainline` since #621 merged. `TCPUDPUnitTests.test_a_truncated_option_still_parses_its_declared_length` expected a truncated TCP option's `data` as the synthesised zero octets *followed by* the real ones, which is what `rjust()` produced; #621 made the padding `ljust()` everywhere but could not retarget this file, since another change (#612) owned it at the time and editing it concurrently risked discarding work that has since landed. The real octets now come first for both parametrised widths, and the docstring above the assertion says tail-padding rather than left-padding. Test-only: no library code changes, and the sibling case in `tests/protocols/internet/test_ipv4_unit.py` was already retargeted in #621. Measured against `main` at `2221c2d8f`: two subtest failures before, none after (#604).
- **Fixed** -- `main` went red the moment #604's `ljust()` landed, because `TCPUDPUnitTests.test_a_truncated_option_still_parses_its_declared_length` still pinned the head-padded short read that fix removed. The `Reserved_79` option declaring `length=12` over 6 real octets now reports `aabbccddeeff00000000` where the test expected `00000000aabbccddeeff`, so both subtests -- `declared_length=12` and `=32` -- failed on that one assertion while the 17 other cases in the file stayed green: the parse itself never changed, only which end the synthesised zeros sit at. The expectation is inverted, and the docstring above it -- which said the short read was *left*-padded and described the value as four zero octets followed by the six real ones -- is corrected to match, since a docstring that contradicts its own assertion is how the stale expectation survived in the first place. The inputs do discriminate: `trailing` is non-zero and the pad width is 4 and 24, so neither subtest would hold under the other order. #621 left this file alone deliberately, because #612 owned it at the time, and merged two minutes ahead of the cross-review verdict that named it (#604, #621).
- **Fixed** -- `util/bump_version.py` left `CITATION.cff` naming the previous release. Nothing else in the repository maintains that file -- no workflow, hook or packaging file mentions it -- so every bump since it landed in #615 would have stranded the `version` and `date-released` it renders as GitHub's "Cite this repository" button and that citation managers, Zenodo and dependency inventories read directly. Both fields now move with `__version__`. They have the same standing, since the file's own header says both describe the newest *published* release, and moving only one would assert that 1.5.0b5 was released on the day 1.5.0b4 was; the date is taken in UTC, because seven of the thirty most recent bumps were made late evening in US-Eastern where a local date is a day behind the publish it describes. That the two are the same day at all is measured rather than assumed: the bump is what triggers `create-release.yml`, the median gap to the PyPI upload is three minutes, and the UTC calendar dates agree 30 times out of 30. The rewrite is line-oriented, so the comment header, key ordering and each field's existing quoting survive -- `cff-version` and a `references` entry's own `version` are anchored out at column zero -- and the result is checked with `cffconvert --validate`. An absent file is reported on stderr and skipped rather than failing the vendor cron before its `git commit`, which would discard the whole registry crawl for the sake of a documentation file; a file present with no `version` field raises instead, before anything is written, because rewriting nothing while reporting success is the staleness this fixes. Two things came with it. The script gains a `main()` guard, having previously run the entire bump at import, which is why it had no testable surface; and the `import pcapkit` fallback in its version reader, which returned `"1.5.0b4'\n"` -- closing quote and newline included, which `packaging` rejects -- is fixed, a path that had never worked and went unnoticed because the only caller installs the package first. A new gate asserts the committed file still names the packaged version, covering the version changes made by hand, which never run this script at all -- 40 of the 159 commits that have moved `__version__` on `main`, a quarter over the project's life and 11 of the most recent 25 (#625).
- **Fixed** -- `_missing_` in the four Mobility Header flag enumerations ended in `return cls(value)`, the same constructor that had just failed to find the value, so every in-range value that is not already a member re-entered `_missing_` unbounded and raised `RecursionError`. `BindingACKFlag`, `BindingUpdateFlag`, `HandoverACKFlag` and `HandoverInitiateFlag` are `IntFlag` types whose members are single bits, so the hole was not some exotic bit pattern: `F(0)` -- no flags set, the most ordinary value a flag octet can carry -- recursed on all four, and so did every composite of two defined bits, such as `BindingACKFlag(0x06)`. Defining `_missing_` at all is what caused it, because it shadowed the `aenum` `Flag` machinery that resolves exactly those values; `pcapkit/const/tcp/flags.py` defines no `_missing_` and has never had the defect. All four now end in `return super()._missing_(value)`, which is what `pcapkit/vendor/default.py` emits for every other generated enumeration and what 75 of the 117 modules under `pcapkit/const/` already do, so `F(0)` is an empty flag and `BindingACKFlag(0x06)` is `S|D`. The `extend_enum` idiom that `pcapkit/const/pcapng/record_type.py` and `secrets_type.py` use to mint a member for an unassigned integer -- the only other two modules whose `_missing_` ends in `return cls(value)`, and which do not recurse precisely because that `extend_enum` runs first -- was considered and rejected for a flag type: naming `0x06` `Unassigned_0x06` would hide the composite and pollute `_member_map_` with an entry per bit pattern, up to 65536 of them for `BindingUpdateFlag`. The range guard above it is untouched, so an out-of-range or non-integer value is still the same `ValueError`. Fixed in the four `pcapkit/vendor/mh/` templates and regenerated, the `pcapkit/const/mh/` modules being generated output that the next crawl would otherwise revert (#623).
- **Fixed** -- the HIP `SOLUTION` builder sized the parameter with `4 + math.ceil(max(random.bit_length(), solution.bit_length()) / 4)`, which is an invalid shorthand for the two fields it actually has to describe. [RFC 7401 Section 5.2.5](https://datatracker.ietf.org/doc/html/rfc7401#section-5.2.5) gives the parameter's `Length` as `4 + RHASH_len / 4` over a `Random #I` and a `Puzzle solution #J` of `RHASH_len / 8` octets **each**, and `/ 4` equals twice `/ 8` only because `RHASH_len` -- the natural output length of a hash function, in bits -- is a whole number of octets. Applied to an arbitrary `bit_length()` the identity fails, and the builder emitted an *odd* contents width, which its own reader then refused: `_read_param_solution`'s `(schema.len - 4) % 2` guard exists because `SolutionParameter` splits that width into two equal `(len - 4) // 2` halves, so an odd width cannot be framed at all. `random=0x1` with `solution=0xfff` declared `len=7` and raised `ProtocolError: HIPv2: [ParamNo 321] invalid format` on input the library had itself produced. The undersized length was the worse half of it: because both fields take their width from that same `len`, `solution=0xfff` was packed into the one octet it allowed and came back as `0xff` -- silent truncation, nothing raised. Fixed to `4 + 2 * math.ceil(max(...) / 8)`, the form the sibling `PUZZLE` builder already uses, which is even by construction and so can never trip the guard. Loosening the reader was rejected as the alternative: an odd contents width has no meaning in the wire format, since the RFC makes the two fields equal-width and says nothing about which would take an extra octet, and a laxer guard would still have truncated the value (#608).
- **Added** -- `SOLUTION` parameter width coverage in `tests/protocols/internet/test_hip_unit.py`: eight widths asserting the declared length, the reader's acceptance and the construct-pack-parse cycle, plus a 57-bit case pinning the 20-octet length [RFC 5201 Section 5.2.5](https://datatracker.ietf.org/doc/html/rfc5201#section-5.2.5) requires of HIPv1. Five of the eight widths -- 1, 9, 12, 17 and 25 bits -- are deliberately *not* multiples of eight, because at a multiple of eight the defective `ceil(bits / 4)` coincides with the correct width, which is why every fixture that reached this builder passed through it unharmed; the round-trip case in `examples/generators/options.py` supplies no `random` or `solution` at all, so it exercised the formula at zero bits. 57 bits is what discriminates on the HIPv1 path for the same reason 64 does not: both formulas give 20 at a full-width value (#608).

Expand Down
25 changes: 25 additions & 0 deletions docs/source/changelog/1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,31 @@ pull requests between #326 and #509.
version, covering the version changes made by hand, which never run this script at
all -- 40 of the 159 commits that have moved ``__version__`` on ``main``, a quarter
over the project's life and 11 of the most recent 25 (#625).
* **Fixed** -- ``_missing_`` in the four Mobility Header flag enumerations ended in
``return cls(value)``, the same constructor that had just failed to find the value,
so every in-range value that is not already a member re-entered ``_missing_``
unbounded and raised ``RecursionError``. ``BindingACKFlag``, ``BindingUpdateFlag``,
``HandoverACKFlag`` and ``HandoverInitiateFlag`` are ``IntFlag`` types whose
members are single bits, so the hole was not some exotic bit pattern: ``F(0)`` --
no flags set, the most ordinary value a flag octet can carry -- recursed on all
four, and so did every composite of two defined bits, such as
``BindingACKFlag(0x06)``. Defining ``_missing_`` at all is what caused it, because
it shadowed the ``aenum`` ``Flag`` machinery that resolves exactly those values;
``pcapkit/const/tcp/flags.py`` defines no ``_missing_`` and has never had the
defect. All four now end in ``return super()._missing_(value)``, which is what
``pcapkit/vendor/default.py`` emits for every other generated enumeration and what
75 of the 117 modules under ``pcapkit/const/`` already do, so ``F(0)`` is an empty
flag and ``BindingACKFlag(0x06)`` is ``S|D``. The ``extend_enum`` idiom that
``pcapkit/const/pcapng/record_type.py`` and ``secrets_type.py`` use to mint a
member for an unassigned integer -- the only other two modules whose ``_missing_``
ends in ``return cls(value)``, and which do not recurse precisely because that
``extend_enum`` runs first -- was considered and rejected for a flag type: naming
``0x06`` ``Unassigned_0x06`` would hide the composite and pollute ``_member_map_``
with an entry per bit pattern, up to 65536 of them for ``BindingUpdateFlag``. The
range guard above it is untouched, so an out-of-range or non-integer value is
still the same ``ValueError``. Fixed in the four ``pcapkit/vendor/mh/`` templates
and regenerated, the ``pcapkit/const/mh/`` modules being generated output that the
next crawl would otherwise revert (#623).
* **Fixed** -- the HIP ``SOLUTION`` builder sized the parameter with
``4 + math.ceil(max(random.bit_length(), solution.bit_length()) / 4)``, which is
an invalid shorthand for the two fields it actually has to describe.
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/const/mh/binding_ack_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ def _missing_(cls, value: 'int') -> 'BindingACKFlag':
"""
if not (isinstance(value, int) and 0 <= value <= 0xFF):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
2 changes: 1 addition & 1 deletion pcapkit/const/mh/binding_update_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ def _missing_(cls, value: 'int') -> 'BindingUpdateFlag':
"""
if not (isinstance(value, int) and 0 <= value <= 0xFFFF):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
2 changes: 1 addition & 1 deletion pcapkit/const/mh/handover_ack_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ def _missing_(cls, value: 'int') -> 'HandoverACKFlag':
"""
if not (isinstance(value, int) and 0 <= value <= 0xFF):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
2 changes: 1 addition & 1 deletion pcapkit/const/mh/handover_initiate_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ def _missing_(cls, value: 'int') -> 'HandoverInitiateFlag':
"""
if not (isinstance(value, int) and 0 <= value <= 0xFF):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
2 changes: 1 addition & 1 deletion pcapkit/vendor/mh/binding_ack_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def _missing_(cls, value: 'int') -> '{NAME}':
"""
if not ({FLAG}):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
''' # type: Callable[[str, str, str, str, str], str]


Expand Down
2 changes: 1 addition & 1 deletion pcapkit/vendor/mh/binding_update_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def _missing_(cls, value: 'int') -> '{NAME}':
"""
if not ({FLAG}):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
''' # type: Callable[[str, str, str, str, str], str]


Expand Down
2 changes: 1 addition & 1 deletion pcapkit/vendor/mh/handover_ack_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def _missing_(cls, value: 'int') -> '{NAME}':
"""
if not ({FLAG}):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
''' # type: Callable[[str, str, str, str, str], str]


Expand Down
2 changes: 1 addition & 1 deletion pcapkit/vendor/mh/handover_initiate_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def _missing_(cls, value: 'int') -> '{NAME}':
"""
if not ({FLAG}):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
return cls(value)
return super()._missing_(value)
''' # type: Callable[[str, str, str, str, str], str]


Expand Down
Loading
Loading