1use alloc::vec::Vec;
9use core::fmt;
10
11const WORD_BYTES: usize = 4;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum WireError {
16 PayloadTooLarge { len: usize },
17}
18
19impl fmt::Display for WireError {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 WireError::PayloadTooLarge { len } => {
23 write!(f, "payload length {len} exceeds u32 framing limit")
24 }
25 }
26 }
27}
28
29fn frame_len_word(len: usize) -> Result<u32, WireError> {
30 u32::try_from(len).map_err(|_| WireError::PayloadTooLarge { len })
31}
32
33pub fn read_framed_bytes_with(mut read_word: impl FnMut() -> u32) -> Vec<u8> {
37 let len = read_word() as usize;
38 let words_needed = len.div_ceil(WORD_BYTES);
39
40 let mut bytes = Vec::with_capacity(len);
41 let mut remaining = len;
42 for _ in 0..words_needed {
43 let word_bytes = read_word().to_be_bytes();
44 let bytes_to_take = remaining.min(WORD_BYTES);
45 bytes.extend_from_slice(&word_bytes[..bytes_to_take]);
46 remaining -= bytes_to_take;
47 }
48
49 bytes
50}
51
52pub fn frame_words_from_bytes(bytes: &[u8]) -> Result<Vec<u32>, WireError> {
54 let len_word = frame_len_word(bytes.len())?;
55 let word_count = bytes.len().div_ceil(WORD_BYTES);
56 let mut words = Vec::with_capacity(1 + word_count);
57 words.push(len_word);
58 for chunk in bytes.chunks(WORD_BYTES) {
59 let mut padded = [0u8; WORD_BYTES];
60 padded[..chunk.len()].copy_from_slice(chunk);
61 words.push(u32::from_be_bytes(padded));
62 }
63 Ok(words)
64}
65
66#[cfg(test)]
67mod tests {
68 use super::{frame_len_word, frame_words_from_bytes, read_framed_bytes_with, WireError};
69
70 #[test]
71 fn framing_roundtrip() {
72 let bytes = b"airbender";
73 let words = frame_words_from_bytes(bytes).expect("frame words");
74 assert_eq!(words[0], bytes.len() as u32);
75 let mut cursor = 0;
76 let reconstructed = read_framed_bytes_with(|| {
77 let word = words[cursor];
78 cursor += 1;
79 word
80 });
81 assert_eq!(reconstructed, bytes);
82 }
83
84 #[test]
85 fn closure_reader_handles_partial_word() {
86 let bytes = [0x12u8, 0x34, 0x56];
87 let words = frame_words_from_bytes(&bytes).expect("frame words");
88 let mut cursor = 0;
89 let reconstructed = read_framed_bytes_with(|| {
90 let word = words[cursor];
91 cursor += 1;
92 word
93 });
94 assert_eq!(reconstructed, bytes);
95 }
96
97 #[test]
98 fn rejects_lengths_above_u32_max() {
99 let err = frame_len_word(usize::MAX).expect_err("must reject oversized length");
100 assert_eq!(err, WireError::PayloadTooLarge { len: usize::MAX });
101 }
102}