Repository navigation
fix(protocols): let Application carry an undissected remainder (#719) - #1030
Conversation
`Application` forbade payload dispatch outright, which is stricter than the invariant it means to encode. Ruled on #719. * `_decode_next_layer` and `_import_next_layer` raised `UnsupportedCall` unconditionally. They now accept the `-1` sentinel -- the rest of the packet, undissected -- and delegate to `ProtocolBase`, which resolves it to `Raw`, or to `NoPayload` when nothing remains. Any other `proto` is still refused, and the message now names the protocol number rather than claiming the attribute does not exist. * `__post_init__` unconditionally overwrote `_next` with `NoPayload()` and rebuilt `_protos` after `read()`, discarding whatever a dispatch had just set. It now fills them only when `read` left `_next` unset. * The class docstring said "transport layer protocol family" and said nothing about the dispatch contract. Both corrected. The invariant is "no further *protocol* layer above this one". Undissected trailer bytes are not a protocol layer, so refusing `-1` was refusing something the invariant permits -- and it is what blocked a functionally application-layer protocol that still has a trailer from naming `Application` as its base at all. Breaking in two ways a third party can observe. A subclass calling either method with `-1` changes from raising to succeeding; and a subclass whose `read` assigns `_next` itself used to have it overwritten with `NoPayload` after `read()` returned, and now keeps it. No subclass in the package does either -- the five are `FTP`, `HTTP`, `HTTPv1`, `HTTPv2` and `NGAP`, none of which overrides the two dispatch methods or `__post_init__` -- so nothing in the tree changes behaviour. `_import_next_layer` keeps its `# type: ignore[override]` and its `super()` call gains `[call-arg,misc]`. The base is wrapped in `@beholder`, which mypy sees as a no-argument method, so both are load-bearing rather than decorative; `protocol.py:598` carries the same pair for the same reason. Dropping the override ignore drew five new errors in this file. `_decode_next_layer` needs none: widening `proto` to `Optional[int]` is LSP-legal. Measured -- mypy on this file reports zero errors on both this change and main, against 305 elsewhere either way. New test pins both halves. Three of its five cases fail on main -- the two sentinel dispatches and the `_import_next_layer` acceptance -- and the other two are regression guards on what must not change: a real protocol number is still refused, and an application protocol that never dispatches still ends in `NoPayload`. tests/protocols/application + tests/project: 399 passed, 1 skipped, 1305 subtests passed.
|
Cross-review verdict on The defect: I deleted a
The mechanism is worth recording: And the PR body's mypy sentence was false as written — I claimed none of mypy's errors sit on a line this change touches. Corrected. On the What it confirmed independently, re-deriving rather than accepting: the sentinel accepted and every other One correction it raised against my own prose on #719, which belongs to the move rather than here: my 00:11Z comment said Resetting |
55dc55b to
0e6e5f3
Compare
|
Cross-review verdict on The mypy regression is fixed and re-measured independently: no line in The diff scope held, which is what let the rest of the first pass carry forward untouched. It also agreed with my reading of the removed one: widening One cosmetic staleness it caught, now fixed: the body's "Verified on" line still named Swapping to |
`process.rst:104` pinned 159 entries while the file holds 161, so `tests/project` was failing on `main` again. * #1025, #1028 and #1030 each measured 159 against a 158-entry base, which was correct for each branch in isolation. Merging all three added three entries and left the pin two behind. * This is the second time the same collision has landed, after `51100da7e` fixed a two-way version of it. Measuring per branch is not sufficient -- the pin is only correct at the moment it is measured against the tree it will merge into, so it needs re-measuring at merge time or the check will keep going red whenever two changelog-touching branches land together. Measured rather than incremented: `grep -cE '^\* ' docs/source/changelog/1.5.0.rst` gives 161. tests/project: 268 passed, 1 skipped, 864 subtests passed. The failing test was test_the_page_pins_its_own_measured_numbers.
Please follow the guide below
make pylint,make mypy,make isort) — measured per file: mypy reports 0 errors inapplication.pyon this change and onmainalike. An earlier revision of this branch introduced 5 there by dropping a load-bearing# type: ignore[override]; that is fixed. mypy is not a clean baseline onmaineither way — 305 errors in 33 other files on this invocation, none on a line this change touches.tests/protocols/application/test_application_dispatch_unit.py, new here.make testitself was not run: the full suite reaches ~56 GB on this host and gets OOM-killed, so the selections below were run instead.docs/source/changelog/and regeneratedCHANGELOG.mdWhat is the purpose of your pull request?
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Implements the ruling on #719: loosen
Applicationitself rather than work around it per class.Applicationforbade payload dispatch outright, which is stricter than the invariant it means to encode — "no further protocol layer above this one". Undissected trailer bytes are not a protocol layer, so refusing them was refusing something the invariant permits._decode_next_layerand_import_next_layerraisedUnsupportedCallunconditionally. They now accept the-1sentinel — the rest of the packet, undissected — and delegate toProtocolBase, which resolves it toRaw, or toNoPayloadwhen nothing remains. Any otherprotois still refused, and the message now names the protocol number instead of claiming the attribute does not exist.__post_init__unconditionally overwrote_nextwithNoPayload()and rebuilt_protosafterread(), discarding whatever a dispatch had just set. It now fills them only whenreadleft_nextunset.Why it matters beyond these two methods: this is what blocked a functionally application-layer protocol that still carries a trailer from naming
Applicationas its base at all. The OSPF/RARP layer move rides on top of this and is a separate pull request.Breaking in two ways a third party can observe. A subclass calling either method with
-1changes from raising to succeeding; and a subclass whosereadassigns_nextitself used to have it overwritten withNoPayloadafterread()returned, and now keeps it. Neither needs a code change by the caller to observe, which is the convention page's test. No subclass in the package does either — the five areFTP,HTTP,HTTPv1,HTTPv2andNGAP, enumerated by walkingApplication.__subclasses__()recursively, and none overrides the two dispatch methods or__post_init__(FTP_DATAsubclassesRaw, notApplication) — so nothing in the tree changes behaviour.Verified on
0e6e5f3cf:tests/protocols/application+tests/projectgive 399 passed, 1 skipped, 1305 subtests. Three of the new test's five cases fail whenapplication.pyis reverted tomain— the two sentinel dispatches and the_import_next_layeracceptance — and the other two are regression guards on what must not change: a real protocol number still refused, and an application protocol that never dispatches still ending inNoPayload.