Skip to content

transport: the framer's head piece states its declared body length (declared_len), for 1.5.5's pre-body download cap - #1032

Open
MattJackson wants to merge 2 commits into
predevfrom
lane-conn-declared-len
Open

MattJackson wants to merge 2 commits into
predevfrom
lane-conn-declared-len

Conversation

@MattJackson

@MattJackson MattJackson commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Why (ARCHITECT ruling, 1.5.5 parity)

1.5.5's plugins.fetch downloader refused an honest over-cap declared length before reading any body byte: "GET {url}: declared Content-Length {len} exceeds the {cap}-byte plugins.fetch download cap" (v1.5.5 source; the same text sits in predev's crates/busbar-kernel/src/preflight.rs today). In 1.6.0 the download moves onto the host connector (#805, crates/busbar-core-connector/src/fetch.rs), and the connector cannot see the declared length:

  • the head field block never carries it: crates/busbar-contract/src/abi/transport/fields.rs:14-16 says the framer drops it because the pieces carry the body the framer already unframed;
  • busbar_contract::conn::Piece had no length field.

fields.rs:14-16 stays true. The declared length is not a field and never enters the block. It travels as its own typed value on the head piece, the way code and retry_after_secs already do.

The change

A neutral declared_len: "the body length the message's head declared on the wire, if it stated one". It is a statement, not a count of delivered bytes.

  • Transport ABI abi::transport::FramePiece appends declared_len: u64 at offset 40 (size 40 -> 48). It is present under the new flag PIECE_HAS_DECLARED_LEN = 128, the HAS-flag encoding code/retry_after_secs use (absent = flag clear, value 0). This is a pre-release layout change and the transport ABI version is unchanged (ARCHITECT). tests/golden/abi-layout.golden, tests/layout_golden.rs and include/busbar_plugin.h are updated together (#define, struct field, size and offset asserts, in the generator's format). check_framer admits the bit only on a field block; anywhere else it is framer.piece.declared_len_not_fields.
  • SDK sdk_hyper::Piece gains declared_len: Option<u64>, and fill sets the flag and value, so a Rust framer states it.
  • Connection table conn::Piece gains declared_len: Option<u64>. Its mechanical lowering abi::host::conn::WirePiece appends declared_len at offset 72 (72 -> 80) under WirePiece flag PIECE_HAS_DECLARED_LEN = 4, so a plugin reading the table sees it too.
  • Connector carries it from the framer's piece (Got) into the table's piece untouched, with no interpretation. Every other piece literal in the workspace names it absent.

RED

  • busbar-core-connector connector_tests::a_framers_declared_body_length_reaches_the_tables_head_piece: a TestDoor framer (new fields / declared_len knobs), driven over a real loopback far end through the connector's table, states 2^40 on its head field block. The table's Fields piece carries declared_len == Some(2^40). A framer stating none yields None.
  • conn_lowering_tests::every_operation_crosses_the_lowering: it crosses the WirePiece lowering both ways (Some/None).
  • sdk_hyper fill_states_a_heads_declared_length_where_it_declared_one.
  • transport_check_tests::a_declared_length_rides_a_field_block_only.
  • The layout golden and the header stay in sync (layout_golden, abi-header).

No skip, no #[ignore].

Next

#805's fetch.rs consumes Piece::declared_len to refuse an over-cap download before the first body byte. No oracle cell covers the over-cap refusal; the url cell is egress.fetch|plugins|url.

Why every transport plugin repins past this (row 81)

FramePiece grows 40 -> 48 bytes and WirePiece 72 -> 80. Both are array-element / out structs, so the stride changes. No check crosses the host/plugin boundary for them: FramePiece has no size field and each side only asserts its own build (const _: () = assert!(size_of::<FramePiece>() == ..)); WirePiece.size is written by the host and validated by no plugin; the loader (plugin-loader dispatch/load.rs table::<K>) checks only the ops table's slots/size. A transport framer built at an older pin and dropped in would therefore mis-stride the piece arrays while passing the unchanged ABI version check. Pre-tag, appends are guarded by the layout golden without a version bump (BUSBAR-1.6.0.md l.1211; R9), so the guard is row 81: every roster transport plugin (tcp, stdio, webrtc; http/ws/grpc with their door changes) is repinned past this commit.

…eclared_len)

ARCHITECT ruling (1.5.5 parity, plugins.fetch's pre-body download cap): 1.5.5 refused an honest
over-cap declared body length before reading a body byte. In 1.6.0 the download runs on the host
connector, which sees only the table's pieces: the head field block never carries the declared
length (abi::transport::fields, its own rule, unchanged), and conn::Piece had no field for it.

The framer's HEAD piece gains a neutral declared_len, "the body length the message's head declared
on the wire, if it stated one":

* abi::transport::FramePiece appends `declared_len: u64` @40 (size 40 -> 48), present under the new
  flag PIECE_HAS_DECLARED_LEN = 128, the encoding `code` and `retry_after_secs` already use.
  Pre-tag v1 layout edit, transport ABI version unchanged; abi-layout.golden, layout_golden.rs and
  include/busbar_plugin.h updated together. check_framer admits the bit, on a field block only
  (framer.piece.declared_len_not_fields otherwise).
* sdk_hyper::Piece gains `declared_len: Option<u64>`; fill sets the flag and value.
* conn::Piece gains `declared_len: Option<u64>`, lowered as WirePiece.declared_len @72 (72 -> 80)
  under WirePiece PIECE_HAS_DECLARED_LEN = 4, so a plugin reading the table sees it too.
* busbar-core-connector carries it from the framer's piece (Got) into the table's piece untouched;
  every other piece literal names it absent.

RED: connector_tests::a_framers_declared_body_length_reaches_the_tables_head_piece (a TestDoor
framer over a real far end states 2^40 on its head field block; the table's Fields piece carries
Some(2^40); a framer stating none yields None). Also conn_lowering_tests (it crosses the WirePiece
lowering), sdk_hyper fill_states_a_heads_declared_length_where_it_declared_one, and
transport_check_tests a_declared_length_rides_a_field_block_only.
MattJackson added a commit that referenced this pull request Oct 14, 2026
… base, so it compiles against #787's FramePiece (fault in the old _reserved byte)

#787's FramePiece states the framer's fault reading in the byte that was `_reserved` (the breaker
plane-fault work); the datagram rig landed on predev against the old layout and named
`_reserved: 0`, which #787's tree does not have (E0560). FramePiece has no constructor or Default,
so the literal names the four fields the rig sets and takes the rest from an all-zero piece (code,
status class, FAULT_NONE, no retry), as the contract and loader tests' `z()` pieces do. An appended
field (#1032's declared_len) no longer breaks it. Every other FramePiece and WirePiece literal in
the tree was checked: the rest are built from `z()` or already name the current fields.
… appended declared_len at zero

compile/clippy/test were red on busbar-transport-ws (pinned f8ba71d), whose door builds a FramePiece
literal field by field and so missed the appended declared_len. The pin moves to 140691cf2e, f8ba71d
plus one change: the literal ends in `..std::mem::zeroed()` (FramePiece is plain repr(C) integers;
zero is every field's absent value), so this and any later append compile. The framer states no
declared length. ws's own dev carries the off-line door changes (#1012/#1019) and repins with them.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant