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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ Preceded by `1.5.0a1` (2026-09-15), `1.5.0b1` and `1.5.0b2` (both 2026-09-18) an

#### Changed

- **a breaking change to** `OSPF`, `RARP` and `DRARP`: they move from `pcapkit.protocols.link` to `pcapkit.protocols.application`, with no deprecated re-export at the old path ([#719](https://github.com/JarryShaw/PyPCAPKit/issues/719)). Layer is decided by designed function, with the IETF as the single source of truth -- [RFC 1812 Section 7](https://datatracker.ietf.org/doc/html/rfc1812#section-7) places routing protocols in the application layer and [RFC 1122 Section 1.1.3](https://datatracker.ietf.org/doc/html/rfc1122#section-1.1.3) lists RARP there -- and the rule is recorded in `docs/source/contributing/conventions/protocol-layer-placement.rst`.

Import paths, before to after:

- `pcapkit.protocols.link.ospf` to `pcapkit.protocols.application.ospf`
- `pcapkit.protocols.data.link.ospf` to `pcapkit.protocols.data.application.ospf`
- `pcapkit.protocols.schema.link.ospf` to `pcapkit.protocols.schema.application.ospf`
- `pcapkit.protocols.link.rarp` to `pcapkit.protocols.application.rarp`

`from pcapkit import OSPF` and `from pcapkit.protocols import OSPF` (likewise `RARP` and `DRARP`) are unaffected; `from pcapkit.protocols.link import OSPF` and the deep paths above break. RARP has no `data` or `schema` module of its own, as it reuses ARP's.

`layer` changes from `'Link'` to `'Application'` for all three. `OSPF` now takes `Application` as its only base, and `RARP` becomes `class RARP(Application, ARP)` -- layer base first, because `ARP`'s chain reaches `Link`, which owns `__layer__`. `ARP` and `InARP` stay `'Link'`. Dispatch keys are unchanged: OSPF is still reached from `Internet.__proto__` at `TransType.OSPFIGP` and RARP from `Link.__proto__` by EtherType. Leaving `Link` repoints `OSPF.__proto__` at `ProtocolBase.__proto__`, so OSPF no longer sees Link's EtherType entries -- which nothing used, since it is reached by protocol number, and a `-1` lookup falls back to `Raw` in either registry without inserting. `register` and `_read_protos` still resolve, on `ProtocolBase` rather than `Link`. `RARP` keeps Link's through `ARP`.

Layer-limited extraction: `layer='link'` no longer sets the termination flag on OSPF and `layer='application'` now does -- the same inversion applies to RARP -- but the extracted protochain is unchanged at every `layer=` value, including `'internet'` (`IPv4:OSPFIGP` before and after, as IPv4 ends the chain itself). Measured: `IPv4:OSPFv2:Raw` for a headerless `IPV4` capture, `Ethernet:RARP:Raw` for a padded RARP frame.
- renames with no compatibility alias left behind: PCAP-NG `Option` subclasses spell the namespace class keyword `ns=` instead of `namespace=` ([#439](https://github.com/JarryShaw/PyPCAPKit/issues/439)).
- `tests/protocols/transport/test_tcp_udp_unit.py` now reaches the MP_JOIN dispatchers through `TCP()` itself, instead of assigning a Python `set` to `_flags` on a bare `TCP.__new__(TCP)`. A `set` answers the membership tests `_make_mptcp_join` and `_read_mptcp_join` use, so every flag branch ran and both TCP modules read 100% coverage, while the attribute had neither the `aenum.IntFlag` type production assigns nor the ordering that governs when it exists -- how [#587](https://github.com/JarryShaw/PyPCAPKit/issues/587) stayed invisible behind that number, and the `cast('Enum_Flags', 0)` no-op behind it. Reverting [#587](https://github.com/JarryShaw/PyPCAPKit/issues/587)'s hoist now fails two of the file's 17 tests with `AttributeError: 'TCP' object has no attribute '_flags'`, and restoring the `cast` fails two with `TypeError: argument of type 'int' is not a container or iterable`; all 17 passed before. The library is unchanged and coverage does not move -- the point is what the same numbers are now worth ([#603](https://github.com/JarryShaw/PyPCAPKit/issues/603)).
- building a protocol through its constructor with a keyword that names nothing now raises `UnsupportedCall` instead of discarding it. **This is a behaviour change to a public API**: every `make` in the tree ends its signature with `**kwargs` and reads nothing out of it, so a misspelled keyword was accepted, dropped, and the field it named kept its default -- wrong octets, nothing said. That is what [#602](https://github.com/JarryShaw/PyPCAPKit/issues/602) cost: `examples/generators/options.py` asked for `seq=1` where `TCP.make` spells it `seq_no`, and 25 generated fixture frames carried sequence number `0`. [#541](https://github.com/JarryShaw/PyPCAPKit/issues/541) and [#556](https://github.com/JarryShaw/PyPCAPKit/issues/556) were the same silence. The schema layer was never so permissive (`Schema.__update__` warns `UnknownFieldWarning`), and that asymmetry is what this closes. The check sits in `ProtocolBase.__init__` rather than `make`, because `__post_init__` passes one `**kwargs` to the construction *and* to the parse of what it has just constructed, so a keyword declared only by `read` legitimately travels through `make` (`HIP.read` declares `extension`, which `HIP.make` does not, and the option generator depends on it). The accepted set is the union of every keyword-taking parameter of `make`, `read`, `pack`, `unpack`, `__post_init__` and `__init__` across the MRO, computed once per class from `inspect.signature`. Parsing is deliberately untouched: there the keywords are whatever the engines and the four `_import_next_layer` implementations forward, a protocol cannot know which its parent passed on, and a dropped parse keyword changes how a packet is read, not what its octets say. Two opt-in escapes exist per class through a new `__keywords__`: a set, for a keyword read out of `**kwargs` by name (`ESP.read` with `packet`); and `None`, for a dispatcher whose real signature belongs to a class chosen at call time (only `HTTP.make` forwarding to `HTTPv1`/`HTTPv2`). The message names the near neighbour (`seq` reports *did you mean 'seq_no'?*). `from_data` warns `UnknownFieldWarning` rather than raising, because its keywords are whatever `_make_data` returned, so the defect is a key of that mapping disagreeing with the signature it is spread into, and the person who meets it cannot fix it. This surfaced four latent bugs in this repository, all residue of [#602](https://github.com/JarryShaw/PyPCAPKit/issues/602) and fixed here: the `_TCP_BASE` of `examples/generators/dispatch.py` and three stale copies under `tests/protocols/transport/`, each building segments with sequence number `0`. Three more are reported, not fixed, being a defect per protocol: `Frame._make_data` returns `ts_src` where `make` declares `ts_sec`, `L2TPv2._make_data` returns `prio` where it declares `priority`, and `Header._make_data` returns a `magic_number` that `Header.make` does not take, so `from_data` has been dropping a frame's timestamp, an L2TPv2 priority bit and a capture's byte order, and now says so. One limitation: a *direct* `SomeProtocol.make(...)` call is not checked and still discards silently, since the check sits where every producer's keywords converge rather than inside each of the 30 `make` implementations; `object.__new__(cls).make(**kwargs)`, the idiom `HTTP.make` uses, reaches it ([#617](https://github.com/JarryShaw/PyPCAPKit/issues/617)).
Expand Down
36 changes: 36 additions & 0 deletions docs/source/changelog/1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,42 @@ Added
Changed
~~~~~~~

* **a breaking change to** ``OSPF``, ``RARP`` and ``DRARP``: they move from
``pcapkit.protocols.link`` to ``pcapkit.protocols.application``, with no deprecated
re-export at the old path (:issue:`719`). Layer is decided by designed function, with
the IETF as the single source of truth -- :rfc:`1812#section-7` places routing
protocols in the application layer and :rfc:`1122#section-1.1.3` lists RARP there --
and the rule is recorded in ``docs/source/contributing/conventions/protocol-layer-placement.rst``.

Import paths, before to after:

* ``pcapkit.protocols.link.ospf`` to ``pcapkit.protocols.application.ospf``
* ``pcapkit.protocols.data.link.ospf`` to ``pcapkit.protocols.data.application.ospf``
* ``pcapkit.protocols.schema.link.ospf`` to ``pcapkit.protocols.schema.application.ospf``
* ``pcapkit.protocols.link.rarp`` to ``pcapkit.protocols.application.rarp``

``from pcapkit import OSPF`` and ``from pcapkit.protocols import OSPF`` (likewise
``RARP`` and ``DRARP``) are unaffected; ``from pcapkit.protocols.link import OSPF``
and the deep paths above break. RARP has no ``data`` or ``schema`` module of its own, as
it reuses ARP's.

``layer`` changes from ``'Link'`` to ``'Application'`` for all three. ``OSPF`` now takes
``Application`` as its only base, and ``RARP`` becomes ``class RARP(Application, ARP)``
-- layer base first, because ``ARP``'s chain reaches ``Link``, which owns
``__layer__``. ``ARP`` and ``InARP`` stay ``'Link'``. Dispatch keys are unchanged:
OSPF is still reached from ``Internet.__proto__`` at ``TransType.OSPFIGP`` and RARP
from ``Link.__proto__`` by EtherType. Leaving ``Link`` repoints ``OSPF.__proto__`` at
``ProtocolBase.__proto__``, so OSPF no longer sees Link's EtherType entries -- which
nothing used, since it is reached by protocol number, and a ``-1`` lookup falls back to
``Raw`` in either registry without inserting. ``register`` and ``_read_protos`` still
resolve, on ``ProtocolBase`` rather than ``Link``. ``RARP`` keeps Link's through ``ARP``.

Layer-limited extraction: ``layer='link'`` no longer sets the termination flag on OSPF
and ``layer='application'`` now does -- the same inversion applies to RARP -- but the
extracted protochain is unchanged at every
``layer=`` value, including ``'internet'`` (``IPv4:OSPFIGP`` before and after, as IPv4
ends the chain itself). Measured: ``IPv4:OSPFv2:Raw`` for a headerless ``IPV4`` capture,
``Ethernet:RARP:Raw`` for a padded RARP frame.
* renames with no compatibility alias left behind: PCAP-NG ``Option`` subclasses spell
the namespace class keyword ``ns=`` instead of ``namespace=`` (:issue:`439`).
* ``tests/protocols/transport/test_tcp_udp_unit.py`` now reaches the MP_JOIN
Expand Down
3 changes: 2 additions & 1 deletion docs/source/contributing/conventions/documentation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ check the members one at a time.** Several of :issue:`719`'s findings were of ex
shape:

* A sweep asserted that every ``.. module::`` target in the documentation resolved.
One did not: :file:`docs/source/pcapkit/protocols/link/rarp.rst` declared
One did not: :file:`docs/source/pcapkit/protocols/link/rarp.rst` (its path at the
time; now under :file:`application/`) declared
``pcapkit.protocols.data.link.rarp``, which has never existed, because RARP and
DRARP reuse ARP's data class. Fixed in ``68fbccd90``.
* :issue:`911`'s ruling -- export the sentinel objects and leave their types out -- was
Expand Down
2 changes: 2 additions & 0 deletions docs/source/contributing/conventions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ House Conventions
:mod:`pcapkit.const`, which is where the settled questions have mostly
arisen. :ref:`sentinel-convention` governs :mod:`pcapkit.corekit`,
:ref:`extension-header-subclassing` a protocol class hierarchy,
:ref:`protocol-layer-placement` which subpackage a dissector belongs in,
:ref:`process` the repository rather than any of its code, and
:ref:`documentation` the prose itself -- on these pages and in the API
reference -- rather than any code at all.
Expand All @@ -31,5 +32,6 @@ House Conventions
sentinel-convention
registry-protocol
extension-header-subclassing
protocol-layer-placement
process
documentation
2 changes: 1 addition & 1 deletion docs/source/contributing/conventions/process.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ commands down instead of a figure that will be stale by the next merge:
The grouping scheme was settled on :issue:`918`: **a section per top-level module, with**
``Added``/``Changed``/``Fixed`` **nested inside each** -- module granularity, not
per-file and not per-subpackage. The file carries **9** module-level sections holding
161 entries, and no entry carries an inline kind label::
162 entries, and no entry carries an inline kind label::

$ grep -cE '^\* \*\*(Added|Changed|Fixed)\*\*' docs/source/changelog/1.5.0.rst
0
Expand Down
Loading
Loading