fix(core): reassembled observations must decode canonically
The reassembler decoded with postcard::from_bytes, which accepts trailing bytes. It now uses take_from_bytes and rejects a non-empty remainder, as Frame::decode does (spec §10 F13). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+37
-2
@@ -178,8 +178,10 @@ impl Reassembler {
|
||||
total += l;
|
||||
}
|
||||
|
||||
match postcard::from_bytes::<Observation>(&recon[..total]) {
|
||||
Ok(obs) if obs.id() == id => {
|
||||
// Require a canonical encoding (F13): the reassembled bytes must be
|
||||
// exactly one observation, with no trailing bytes.
|
||||
match postcard::take_from_bytes::<Observation>(&recon[..total]) {
|
||||
Ok((obs, rest)) if rest.is_empty() && obs.id() == id => {
|
||||
self.reset_slot();
|
||||
Some(obs)
|
||||
}
|
||||
@@ -578,6 +580,39 @@ mod tests {
|
||||
assert!(last.is_none(), "corrupted reassembly must not yield an obs");
|
||||
}
|
||||
|
||||
/// F13: a reassembled observation must decode canonically. The
|
||||
/// observation's encoding followed by one trailing byte is rejected even
|
||||
/// though its prefix decodes to the right id.
|
||||
#[test]
|
||||
fn reassembled_trailing_byte_is_rejected() {
|
||||
let obs = obs_with_payload([8; 16], 1024);
|
||||
let mut buf = [0u8; consts::MAX_FRAME * 2];
|
||||
let mut bytes = postcard::to_slice(&obs, &mut buf).unwrap().to_vec();
|
||||
bytes.push(0);
|
||||
let id = obs.id();
|
||||
let chunk = 600;
|
||||
let count = bytes.len().div_ceil(chunk) as u8;
|
||||
let mut r = Reassembler::new();
|
||||
let mut last = None;
|
||||
for (i, data) in bytes.chunks(chunk).enumerate() {
|
||||
last = r.feed(id, i as u8, count, data, 0);
|
||||
}
|
||||
assert!(
|
||||
last.is_none(),
|
||||
"trailing bytes after the observation are malformed"
|
||||
);
|
||||
|
||||
// Without the trailing byte the same stream reassembles.
|
||||
bytes.pop();
|
||||
let count = bytes.len().div_ceil(chunk) as u8;
|
||||
let mut r = Reassembler::new();
|
||||
let mut last = None;
|
||||
for (i, data) in bytes.chunks(chunk).enumerate() {
|
||||
last = r.feed(id, i as u8, count, data, 0);
|
||||
}
|
||||
assert_eq!(last, Some(obs));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fragment_too_large_returns_error() {
|
||||
let obs = obs_with_payload([9; 16], consts::MAX_PAYLOAD);
|
||||
|
||||
Reference in New Issue
Block a user