Pin the size gate for a second message_id length
F2: the boundary test always used message_id "m-0", so a gate that measures payload.len() + 231 -- exactly that id's fixed overhead -- passed unnoticed. message_id rides the encoded envelope and echoes the client's own value, so a longer id changes the overhead the gate must measure honestly. Parametrize synthetic/sized_exactly by message_id and add a second boundary test at a 40-byte id: 1,228,800 bytes still passes and 1,228,801 still refuses. Add mutation entry D20L6, the "+231" formula, which dies against the new test and survived the old one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
4fb5742a7d
commit
7f60310039
2 changed files with 73 additions and 11 deletions
|
|
@ -659,12 +659,15 @@ mod tests {
|
|||
encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0).unwrap().len() as u64
|
||||
}
|
||||
|
||||
/// A reply carrying `payload_len` bytes of payload: the envelope a view
|
||||
/// answer travels in, with the document replaced by filler, so a size can
|
||||
/// be chosen instead of found.
|
||||
fn synthetic(payload_len: usize) -> CultNetMessage {
|
||||
/// A reply carrying `payload_len` bytes of payload under a chosen
|
||||
/// `message_id`: the envelope a view answer travels in, with the document
|
||||
/// replaced by filler, so a size can be chosen instead of found. The
|
||||
/// `message_id` is part of the encoded envelope too, so pinning the gate
|
||||
/// against one length alone (every other fixture uses `m-0`) hides a gate
|
||||
/// that hard-codes that length's contribution instead of measuring it.
|
||||
fn synthetic_with_id(message_id: &str, payload_len: usize) -> CultNetMessage {
|
||||
CultNetMessage::OperationResponse {
|
||||
message_id: "m-0".into(),
|
||||
message_id: message_id.into(),
|
||||
service_id: MIND_SERVICE_ID.into(),
|
||||
operation: "view".into(),
|
||||
status: "accepted".into(),
|
||||
|
|
@ -676,17 +679,26 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// One whose encoded envelope is exactly `target` bytes. The envelope's
|
||||
/// own overhead is measured rather than assumed: the length prefix is the
|
||||
/// same width on either side of a megabyte, so one probe fixes it.
|
||||
fn sized_exactly(target: u64) -> CultNetMessage {
|
||||
fn synthetic(payload_len: usize) -> CultNetMessage {
|
||||
synthetic_with_id("m-0", payload_len)
|
||||
}
|
||||
|
||||
/// One whose encoded envelope is exactly `target` bytes, under a chosen
|
||||
/// `message_id`. The envelope's own overhead is measured rather than
|
||||
/// assumed: the length prefix is the same width on either side of a
|
||||
/// megabyte, so one probe fixes it.
|
||||
fn sized_exactly_with_id(message_id: &str, target: u64) -> CultNetMessage {
|
||||
let probe = 1_200_000_usize;
|
||||
let at_probe = encoded_len(&synthetic(probe));
|
||||
let message = synthetic((probe as i64 + (target as i64 - at_probe as i64)) as usize);
|
||||
let at_probe = encoded_len(&synthetic_with_id(message_id, probe));
|
||||
let message = synthetic_with_id(message_id, (probe as i64 + (target as i64 - at_probe as i64)) as usize);
|
||||
assert_eq!(encoded_len(&message), target);
|
||||
message
|
||||
}
|
||||
|
||||
fn sized_exactly(target: u64) -> CultNetMessage {
|
||||
sized_exactly_with_id("m-0", target)
|
||||
}
|
||||
|
||||
/// A connected session whose accept has been acknowledged, so its window
|
||||
/// holds `MAX_PENDING_RELIABLE_PACKETS` and not one less. See the module
|
||||
/// documentation: a test that measures the window exactly and does not
|
||||
|
|
@ -775,6 +787,39 @@ mod tests {
|
|||
.expect_err("the accepted answer filled the window, so nothing follows it");
|
||||
}
|
||||
|
||||
/// The boundary above is pinned for one envelope shape: every reply in it
|
||||
/// carries `message_id` `m-0`, three bytes, so a gate that measures
|
||||
/// `payload.len() + 231` — exactly the encoded envelope's overhead for
|
||||
/// that one id — passes it unnoticed. The reply echoes the client's own
|
||||
/// `message_id`, so that overhead is under the client's control: at a
|
||||
/// longer id, such a gate is wrong, and wrong on the side that lets an
|
||||
/// oversize answer through the size check to a send that then fails
|
||||
/// silently. Forty bytes is used because it is far from `m-0`'s length in
|
||||
/// either direction, not chosen to sit near some other boundary.
|
||||
#[test]
|
||||
fn the_gates_boundary_holds_for_a_second_message_id_length() {
|
||||
let long_id = format!("m-{}", "0".repeat(38));
|
||||
assert_eq!(long_id.len(), 40, "a message_id far from m-0's own length");
|
||||
|
||||
let at = sized_exactly_with_id(&long_id, MAX_RESPONSE_BYTES);
|
||||
let over = sized_exactly_with_id(&long_id, MAX_RESPONSE_BYTES + 1);
|
||||
assert_eq!(
|
||||
within_window(at.clone(), &long_id, "view", "huginn-yggdrasil"),
|
||||
at,
|
||||
"exactly the limit passes at this id's length too"
|
||||
);
|
||||
|
||||
let refused = within_window(over.clone(), &long_id, "view", "huginn-yggdrasil");
|
||||
let (correlation, answered) = decode_response(&refused).unwrap();
|
||||
assert_eq!(correlation, long_id);
|
||||
let HuginnMindResponse::Refused(MindRefusal::ResponseTooLarge { bytes, limit }) =
|
||||
answered.expect("a refusal is an answer, not an envelope failure")
|
||||
else {
|
||||
panic!("one byte over the limit was not refused: {refused:?}");
|
||||
};
|
||||
assert_eq!((bytes, limit), (MAX_RESPONSE_BYTES + 1, MAX_RESPONSE_BYTES));
|
||||
}
|
||||
|
||||
/// One cut spec's own reference, by its cut label.
|
||||
fn spec_ref(daemon: &mut Daemon<OwnedRedbMessagePackBackingStore, NoIndex>, cut: &str) -> PipelineRef {
|
||||
let query = PipelineQuery {
|
||||
|
|
|
|||
|
|
@ -358,6 +358,23 @@ pub fn require_grammatical_instance(declared: &Slug) -> Result<(), MindRefusal>
|
|||
CultNetMessage::OperationResponse { payload, .. } => payload.len() as u64,
|
||||
_ => bytes.len() as u64,
|
||||
},
|
||||
'@
|
||||
}
|
||||
@{
|
||||
Id = 'D20L6'
|
||||
Rule = 'What is measured holds for more than one envelope shape: a longer `message_id` changes the encoded envelope, so a formula that hard-codes one length''s overhead is wrong at another.'
|
||||
Test = 'serve::tests::the_gates_boundary_holds_for_a_second_message_id_length'
|
||||
File = 'crates/huginn-daemon/src/serve.rs'
|
||||
Old = @'
|
||||
let encoded = match encode_cultnet_message_to_vec(&reply, CultNetWireContract::CultNetSchemaV0) {
|
||||
Ok(bytes) => bytes.len() as u64,
|
||||
'@
|
||||
New = @'
|
||||
let encoded = match encode_cultnet_message_to_vec(&reply, CultNetWireContract::CultNetSchemaV0) {
|
||||
Ok(bytes) => match &reply {
|
||||
CultNetMessage::OperationResponse { payload, .. } => payload.len() as u64 + 231,
|
||||
_ => bytes.len() as u64,
|
||||
},
|
||||
'@
|
||||
}
|
||||
@{
|
||||
|
|
|
|||
Loading…
Reference in a new issue