diff --git a/crates/busbar-contract/include/busbar_plugin.h b/crates/busbar-contract/include/busbar_plugin.h index 0f431b1b54..b4dff4957b 100644 --- a/crates/busbar-contract/include/busbar_plugin.h +++ b/crates/busbar-contract/include/busbar_plugin.h @@ -3322,6 +3322,8 @@ struct bb_transport_EmitIn { bb_transport_FramerSink sink; uint64_t deadline_ns; const bb_transport_DatagramLane *lane; + uint32_t status; + uint32_t _status_reserved; }; /* `encode`'s `in`. The rendered bytes go into `sink.wire`. */ @@ -5708,7 +5710,7 @@ BB_ASSERT(offsetof(bb_transport_IngestIn, end) == 112, "bb_transport_IngestIn.en BB_ASSERT(offsetof(bb_transport_IngestIn, _reserved) == 116, "bb_transport_IngestIn._reserved: offset"); BB_ASSERT(offsetof(bb_transport_IngestIn, sink) == 120, "bb_transport_IngestIn.sink: offset"); BB_ASSERT(offsetof(bb_transport_IngestIn, lane) == 200, "bb_transport_IngestIn.lane: offset"); -BB_ASSERT(sizeof(bb_transport_EmitIn) == 224, "bb_transport_EmitIn: size"); +BB_ASSERT(sizeof(bb_transport_EmitIn) == 232, "bb_transport_EmitIn: size"); BB_ASSERT(BB_ALIGNOF(bb_transport_EmitIn) == 8, "bb_transport_EmitIn: alignment"); BB_ASSERT(offsetof(bb_transport_EmitIn, head) == 0, "bb_transport_EmitIn.head: offset"); BB_ASSERT(offsetof(bb_transport_EmitIn, framing) == 88, "bb_transport_EmitIn.framing: offset"); @@ -5720,6 +5722,8 @@ BB_ASSERT(offsetof(bb_transport_EmitIn, flags) == 124, "bb_transport_EmitIn.flag BB_ASSERT(offsetof(bb_transport_EmitIn, sink) == 128, "bb_transport_EmitIn.sink: offset"); BB_ASSERT(offsetof(bb_transport_EmitIn, deadline_ns) == 208, "bb_transport_EmitIn.deadline_ns: offset"); BB_ASSERT(offsetof(bb_transport_EmitIn, lane) == 216, "bb_transport_EmitIn.lane: offset"); +BB_ASSERT(offsetof(bb_transport_EmitIn, status) == 224, "bb_transport_EmitIn.status: offset"); +BB_ASSERT(offsetof(bb_transport_EmitIn, _status_reserved) == 228, "bb_transport_EmitIn._status_reserved: offset"); BB_ASSERT(sizeof(bb_transport_EncodeIn) == 232, "bb_transport_EncodeIn: size"); BB_ASSERT(BB_ALIGNOF(bb_transport_EncodeIn) == 8, "bb_transport_EncodeIn: alignment"); BB_ASSERT(offsetof(bb_transport_EncodeIn, head) == 0, "bb_transport_EncodeIn.head: offset"); diff --git a/crates/busbar-contract/src/abi/tests/transport_check_tests.rs b/crates/busbar-contract/src/abi/tests/transport_check_tests.rs index 56d9cddeb4..6dc34ca349 100644 --- a/crates/busbar-contract/src/abi/tests/transport_check_tests.rs +++ b/crates/busbar-contract/src/abi/tests/transport_check_tests.rs @@ -917,3 +917,29 @@ fn a_streams_final_status_is_judged_against_its_claims_numbering() { f(Rule::NullWithCount, "finish.final_bytes") ); } + +/// THE ANSWER'S STATUS ON AN EMIT (INBOUND-LISTEN, l.4913): stated on the emit that carries an +/// accepted stream's answer head, and on no other emit. +#[test] +fn an_emit_states_a_status_only_on_an_accepted_streams_answer_head() { + let mut i: EmitIn = z(); + assert_eq!(check_emit_status(&i, false), Ok(())); + i.status = 200; + assert_eq!(check_emit_status(&i, true), Ok(())); + i.status = 101; + assert_eq!(check_emit_status(&i, true), Ok(())); + // RED: a status on an emit that carries no answer head. + assert_eq!( + check_emit_status(&i, false), + f(Rule::Contradiction, "emit.status") + ); + // RED: an accepted stream's answer head with no status. + i.status = 0; + assert_eq!(check_emit_status(&i, true), f(Rule::Missing, "emit.status")); + // RED: the padding is not a channel. + i._status_reserved = 1; + assert_eq!( + check_emit_status(&i, false), + f(Rule::UnknownCode, "emit._status_reserved") + ); +} diff --git a/crates/busbar-contract/src/abi/transport/check.rs b/crates/busbar-contract/src/abi/transport/check.rs index c438ee6681..668dd854bd 100644 --- a/crates/busbar-contract/src/abi/transport/check.rs +++ b/crates/busbar-contract/src/abi/transport/check.rs @@ -13,13 +13,14 @@ pub use crate::abi::mechanism::check::{Fault, Rule}; use super::{ - AcceptOut, ArrivalOut, Claim, ConnFacts, FinishIn, FramePiece, FrameSpan, FramerOut, HeadSlots, - IoOut, ListenOut, LocateOut, SettingDecl, StatusRow, TransportTail, CANCEL_COMPLETED, - CANCEL_NOTHING_MOVED, FACT_DECODES_PAYLOAD, FACT_SIGNS_NOTHING_AFTER_AUTH, FRAMING_DATAGRAM, - FRAMING_STREAM, MAX_ADDR, PIECE_CONTINUED, PIECE_END_OF_FRAME, PIECE_FIELDS, PIECE_HAS_CODE, - PIECE_HAS_RETRY_AFTER, PIECE_STREAM_FAILED, PIECE_TEXT, PIECE_WRITABLE, ROLE_CARRIER, - ROLE_FRAMER, SETTING_FLAG, SETTING_TEXT, STATUS_AT_TERMINAL, STATUS_OTHER, STATUS_SUCCESS, - UNIT0_HANDSHAKE, YIELD_ENDED, YIELD_HAS_DEADLINE, YIELD_MORE, YIELD_STREAM_FULL, + AcceptOut, ArrivalOut, Claim, ConnFacts, EmitIn, FinishIn, FramePiece, FrameSpan, FramerOut, + HeadSlots, IoOut, ListenOut, LocateOut, SettingDecl, StatusRow, TransportTail, + CANCEL_COMPLETED, CANCEL_NOTHING_MOVED, FACT_DECODES_PAYLOAD, FACT_SIGNS_NOTHING_AFTER_AUTH, + FRAMING_DATAGRAM, FRAMING_STREAM, MAX_ADDR, PIECE_CONTINUED, PIECE_END_OF_FRAME, PIECE_FIELDS, + PIECE_HAS_CODE, PIECE_HAS_RETRY_AFTER, PIECE_STREAM_FAILED, PIECE_TEXT, PIECE_WRITABLE, + ROLE_CARRIER, ROLE_FRAMER, SETTING_FLAG, SETTING_TEXT, STATUS_AT_TERMINAL, STATUS_OTHER, + STATUS_SUCCESS, UNIT0_HANDSHAKE, YIELD_ENDED, YIELD_HAS_DEADLINE, YIELD_MORE, + YIELD_STREAM_FULL, }; use super::{ DatagramRoute, KeyingMaterial, RendezvousTerms, FINGERPRINT_BYTES, HANDSHAKE_ANSWERS, @@ -687,6 +688,24 @@ pub fn check_final_status(i: &FinishIn, rows: &[StatusRow], claim: u32) -> Resul Ok(()) } +/// An `emit`'s STATUS ([`EmitIn::status`], INBOUND-LISTEN, `BUSBAR-1.6.0.md` l.4913): stated on +/// the emit that carries an ACCEPTED stream's answer head (`answers_head`), and on no other. The +/// framer refuses the emit that breaks it, before any byte of it reaches the wire. +/// +/// # Errors +/// +/// The rule the emit breaks. +pub fn check_emit_status(i: &EmitIn, answers_head: bool) -> Result<(), Fault> { + if i._status_reserved != 0 { + return Err(fault(Rule::UnknownCode, "emit._status_reserved")); + } + match (answers_head, i.status) { + (true, 0) => Err(fault(Rule::Missing, "emit.status")), + (false, s) if s != 0 => Err(fault(Rule::Contradiction, "emit.status")), + _ => Ok(()), + } +} + /// Every setting: a path and a known kind. /// /// # Errors diff --git a/crates/busbar-contract/src/abi/transport/mod.rs b/crates/busbar-contract/src/abi/transport/mod.rs index adaa649124..699e3bab5b 100644 --- a/crates/busbar-contract/src/abi/transport/mod.rs +++ b/crates/busbar-contract/src/abi/transport/mod.rs @@ -1062,6 +1062,15 @@ pub struct EmitIn { pub deadline_ns: u64, /// The datagram lane ([`datagram`]): NULL for a stream framer. A tail addition (pre-tag v1). pub lane: *const DatagramLane, + /// THE ANSWER'S STATUS (INBOUND-LISTEN, `BUSBAR-1.6.0.md` l.4913): on an ACCEPTED stream's FIRST + /// emit, the one carrying the answer's head (its bytes `encode` rendered), the status the answer + /// carries, in the numbering of the claim the stream arrived on; a status that asks the far end + /// to switch protocols hands the connection to the claimed upgrade framer ([`slot::DETACH`]). + /// `0` on every other emit, where a stated status is refused + /// ([`check::check_emit_status`]). Never a `:status` field. A tail addition (pre-tag v1). + pub status: u32, + /// Alignment padding. + pub _status_reserved: u32, } /// `encode`'s `in`. The rendered bytes go into `sink.wire`. diff --git a/crates/busbar-contract/tests/golden/abi-layout.golden b/crates/busbar-contract/tests/golden/abi-layout.golden index d613b94692..3741f9921a 100644 --- a/crates/busbar-contract/tests/golden/abi-layout.golden +++ b/crates/busbar-contract/tests/golden/abi-layout.golden @@ -1800,7 +1800,9 @@ tkind::EmitIn.flags=124 tkind::EmitIn.sink=128 tkind::EmitIn.deadline_ns=208 tkind::EmitIn.lane=216 -tkind::EmitIn.__size=224 +tkind::EmitIn.status=224 +tkind::EmitIn._status_reserved=228 +tkind::EmitIn.__size=232 tkind::EmitIn.__align=8 tkind::EncodeIn.head=0 tkind::EncodeIn.fields=88 diff --git a/crates/busbar-contract/tests/layout_golden.rs b/crates/busbar-contract/tests/layout_golden.rs index 3a86dc8e1c..06219e70a0 100644 --- a/crates/busbar-contract/tests/layout_golden.rs +++ b/crates/busbar-contract/tests/layout_golden.rs @@ -1613,7 +1613,9 @@ fn compute_layout() -> String { flags, sink, deadline_ns, - lane + lane, + status, + _status_reserved ] ); record!(