1use crate::error::{Error, Result};
6use crate::layout::MAX_ENTRY_LEN;
7
8pub fn encode_bundle<E: AsRef<[u8]>>(entries: &[E]) -> Result<Vec<u8>> {
10 let mut out = Vec::new();
11 for entry in entries {
12 let entry = entry.as_ref();
13 if entry.len() > MAX_ENTRY_LEN {
14 return Err(Error::EntryTooLong(entry.len()));
15 }
16 out.extend_from_slice(&(entry.len() as u16).to_be_bytes());
17 out.extend_from_slice(entry);
18 }
19 Ok(out)
20}
21
22pub fn decode_bundle(path: &str, bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
25 let mut entries = Vec::new();
26 let mut at = 0usize;
27 while at < bytes.len() {
28 let Some(prefix) = bytes.get(at..at + 2) else {
29 return Err(Error::Corrupt {
30 path: path.to_string(),
31 reason: format!("a length prefix is cut at byte {at}"),
32 });
33 };
34 let len = usize::from(u16::from_be_bytes([prefix[0], prefix[1]]));
35 at += 2;
36 let Some(entry) = bytes.get(at..at + len) else {
37 return Err(Error::Corrupt {
38 path: path.to_string(),
39 reason: format!("an entry of {len} bytes is cut at byte {at}"),
40 });
41 };
42 entries.push(entry.to_vec());
43 at += len;
44 }
45 Ok(entries)
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn a_bundle_round_trips_including_an_empty_entry() {
54 let entries: Vec<Vec<u8>> = vec![b"one".to_vec(), Vec::new(), vec![7u8; 65535]];
55 let bytes = encode_bundle(&entries).unwrap();
56 assert_eq!(bytes.len(), 2 + 3 + 2 + 2 + 65535);
57 assert_eq!(decode_bundle("b", &bytes).unwrap(), entries);
58 assert!(decode_bundle("b", &bytes[..bytes.len() - 1]).is_err());
59 assert!(decode_bundle("b", &[0]).is_err());
60 assert!(decode_bundle("b", &[]).unwrap().is_empty());
61 }
62
63 #[test]
64 fn an_entry_past_the_prefix_is_refused() {
65 let long = vec![0u8; 65536];
66 assert!(matches!(
67 encode_bundle(&[long]),
68 Err(Error::EntryTooLong(65536))
69 ));
70 }
71}