From 36e77b81b77aebadd5effee829dd3354abc1e157 Mon Sep 17 00:00:00 2001 From: eastagiletracker <310448263+eastagiletracker@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:06:39 +0000 Subject: [PATCH] btp: decode chunk headers from unaligned buffers Chunk::decode read the header with bytemuck::try_from_bytes, which requires the input slice to meet Header's 2-byte alignment. A receiver that passes a sub-slice of a larger packet buffer (an ATT payload sitting at +3, for instance) hands us an odd address, so every otherwise valid chunk was rejected with DecodeError::InvalidHeader and the message never reassembled. Read the header by value with try_pod_read_unaligned instead. Header is Copy and was already copied into the Chunk, so aligned input behaves exactly as before. --- btp/src/dechunk.rs | 5 +---- btp/src/lib.rs | 6 ++++-- btp/src/tests.rs | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/btp/src/dechunk.rs b/btp/src/dechunk.rs index 06adc9d2..2502281b 100644 --- a/btp/src/dechunk.rs +++ b/btp/src/dechunk.rs @@ -86,10 +86,7 @@ impl Chunk { let mut chunk = [0u8; CHUNK_DATA_SIZE]; chunk[..data_len].copy_from_slice(&chunk_data[..data_len]); - Ok(Chunk { - header: *header, - chunk, - }) + Ok(Chunk { header, chunk }) } } diff --git a/btp/src/lib.rs b/btp/src/lib.rs index c7698f7d..8682386f 100644 --- a/btp/src/lib.rs +++ b/btp/src/lib.rs @@ -39,8 +39,10 @@ impl Header { bytemuck::bytes_of(self) } + /// Reads a header out of `bytes`, which does not have to be aligned: + /// received packets are often a sub-slice of a larger transport buffer. #[inline] - fn from_bytes(bytes: &[u8]) -> Option<&Self> { - bytemuck::try_from_bytes::
(bytes).ok() + fn from_bytes(bytes: &[u8]) -> Option { + bytemuck::try_pod_read_unaligned::
(bytes).ok() } } diff --git a/btp/src/tests.rs b/btp/src/tests.rs index fdb4429a..3901705f 100644 --- a/btp/src/tests.rs +++ b/btp/src/tests.rs @@ -256,6 +256,44 @@ fn chunk_decode_and_insert() { assert_eq!(dechunker.data(), Some(data.to_vec())); } +/// Copies `chunk` into a buffer at an odd address and returns that sub-slice, +/// mimicking a receive path that hands us a view into a larger packet buffer. +fn unaligned<'a>(buf: &'a mut [u8], chunk: &[u8]) -> &'a [u8] { + let offset = if buf.as_ptr() as usize % 2 == 0 { 1 } else { 2 }; + buf[offset..offset + chunk.len()].copy_from_slice(chunk); + let slice = &buf[offset..offset + chunk.len()]; + assert_eq!(slice.as_ptr() as usize % 2, 1, "slice should be unaligned"); + slice +} + +#[test] +fn decode_unaligned_chunk() { + let data = b"Chunk arriving on an odd address"; + let chunks: Vec<_> = chunk(data).collect(); + + let mut buf = vec![0u8; APP_MTU + 2]; + let slice = unaligned(&mut buf, &chunks[0]); + + let decoded = Chunk::decode(slice).expect("decoding must not depend on buffer alignment"); + assert_eq!(decoded.as_slice(), data); +} + +#[test] +fn receive_unaligned_chunks() { + let data = vec![7u8; CHUNK_DATA_SIZE * 2 + 1]; + let chunks: Vec<_> = chunk(&data).collect(); + + let mut dechunker = Dechunker::new(); + for chunk in &chunks { + let mut buf = vec![0u8; APP_MTU + 2]; + dechunker + .receive(unaligned(&mut buf, chunk)) + .expect("unaligned chunk should be received"); + } + + assert_eq!(dechunker.data(), Some(data)); +} + #[test] fn chunk_decode_errors() { let small_data = vec![0u8; HEADER_SIZE - 1];