1use flate2::read::ZlibDecoder;
12use flate2::write::ZlibEncoder;
13use flate2::Compression;
14use std::io::{Read, Write};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct WireFactory {
19 pub key: Vec<Vec<u8>>,
21 pub parents: Option<Vec<Vec<Vec<u8>>>>,
23 pub start: u64,
24 pub end: u64,
25}
26
27#[derive(Debug)]
28pub enum Error {
29 UnknownStorageKind(Vec<u8>),
30 InvalidLength(&'static str),
31 InvalidInteger,
32 MissingTrailingNewline,
33 NotMultipleOfFour,
34 Decompress(std::io::Error),
35}
36
37impl std::fmt::Display for Error {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Error::UnknownStorageKind(b) => {
41 write!(f, "unknown storage kind: {}", String::from_utf8_lossy(b))
42 }
43 Error::InvalidLength(msg) => write!(f, "invalid length: {}", msg),
44 Error::InvalidInteger => write!(f, "invalid integer in wire header"),
45 Error::MissingTrailingNewline => {
46 write!(f, "header lines did not end with a trailing newline")
47 }
48 Error::NotMultipleOfFour => {
49 write!(f, "header was not an even multiple of 4 lines")
50 }
51 Error::Decompress(e) => write!(f, "zlib decompression failed: {}", e),
52 }
53 }
54}
55
56impl std::error::Error for Error {}
57
58#[derive(Debug)]
61pub struct WireFrame<'a> {
62 pub factories: Vec<WireFactory>,
63 pub block_bytes: &'a [u8],
65}
66
67pub fn parse_wire(bytes: &[u8]) -> Result<WireFrame<'_>, Error> {
71 let mut splits = bytes.splitn(5, |&b| b == b'\n');
74 let storage_kind = splits.next().ok_or(Error::InvalidLength("storage kind"))?;
75 let z_header_len = splits.next().ok_or(Error::InvalidLength("z_header_len"))?;
76 let header_len = splits.next().ok_or(Error::InvalidLength("header_len"))?;
77 let block_len = splits.next().ok_or(Error::InvalidLength("block_len"))?;
78 let rest = splits.next().ok_or(Error::InvalidLength("rest"))?;
79
80 if storage_kind != b"groupcompress-block" {
81 return Err(Error::UnknownStorageKind(storage_kind.to_vec()));
82 }
83
84 let z_header_len = parse_int(z_header_len)? as usize;
85 let header_len = parse_int(header_len)? as usize;
86 let block_len = parse_int(block_len)? as usize;
87
88 if rest.len() < z_header_len {
89 return Err(Error::InvalidLength("compressed header shorter than rest"));
90 }
91 let z_header = &rest[..z_header_len];
92 let block_bytes = &rest[z_header_len..];
93 if block_bytes.len() != block_len {
94 return Err(Error::InvalidLength("block bytes length mismatch"));
95 }
96
97 let mut header = Vec::with_capacity(header_len);
98 ZlibDecoder::new(z_header)
99 .read_to_end(&mut header)
100 .map_err(Error::Decompress)?;
101 if header.len() != header_len {
102 return Err(Error::InvalidLength("decompressed header length mismatch"));
103 }
104
105 let factories = parse_header_lines(&header)?;
106 Ok(WireFrame {
107 factories,
108 block_bytes,
109 })
110}
111
112pub fn build_header_lines(factories: &[WireFactory]) -> Vec<u8> {
118 let mut out = Vec::new();
119 for factory in factories {
120 let mut first = true;
122 for segment in &factory.key {
123 if !first {
124 out.push(b'\x00');
125 }
126 first = false;
127 out.extend_from_slice(segment);
128 }
129 out.push(b'\n');
130 match &factory.parents {
132 None => out.extend_from_slice(b"None:"),
133 Some(parents) => {
134 let mut first_parent = true;
135 for parent in parents {
136 if !first_parent {
137 out.push(b'\t');
138 }
139 first_parent = false;
140 let mut first_seg = true;
141 for seg in parent {
142 if !first_seg {
143 out.push(b'\x00');
144 }
145 first_seg = false;
146 out.extend_from_slice(seg);
147 }
148 }
149 }
150 }
151 out.push(b'\n');
152 out.extend_from_slice(format!("{}", factory.start).as_bytes());
154 out.push(b'\n');
155 out.extend_from_slice(format!("{}", factory.end).as_bytes());
157 out.push(b'\n');
158 }
159 out
160}
161
162pub fn build_wire_prefix(
168 factories: &[WireFactory],
169 block_bytes_len: usize,
170) -> std::io::Result<Vec<u8>> {
171 let header = build_header_lines(factories);
172 let header_len = header.len();
173 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
174 encoder.write_all(&header)?;
175 let z_header = encoder.finish()?;
176 let z_header_len = z_header.len();
177
178 let mut prefix = Vec::with_capacity(64 + z_header_len);
179 prefix.extend_from_slice(b"groupcompress-block\n");
180 prefix.extend_from_slice(
181 format!("{}\n{}\n{}\n", z_header_len, header_len, block_bytes_len).as_bytes(),
182 );
183 prefix.extend_from_slice(&z_header);
184 Ok(prefix)
185}
186
187fn parse_int(b: &[u8]) -> Result<u64, Error> {
188 std::str::from_utf8(b)
189 .map_err(|_| Error::InvalidInteger)?
190 .parse()
191 .map_err(|_| Error::InvalidInteger)
192}
193
194fn parse_header_lines(header: &[u8]) -> Result<Vec<WireFactory>, Error> {
195 let mut lines: Vec<&[u8]> = header.split(|&b| b == b'\n').collect();
199 let trailing = lines.pop().ok_or(Error::MissingTrailingNewline)?;
200 if !trailing.is_empty() {
201 return Err(Error::MissingTrailingNewline);
202 }
203 if lines.len() % 4 != 0 {
204 return Err(Error::NotMultipleOfFour);
205 }
206 let mut out = Vec::with_capacity(lines.len() / 4);
207 for chunk in lines.chunks_exact(4) {
208 let key = chunk[0]
209 .split(|&b| b == b'\x00')
210 .map(|s| s.to_vec())
211 .collect();
212 let parents = if chunk[1] == b"None:" {
213 None
214 } else {
215 Some(
216 chunk[1]
217 .split(|&b| b == b'\t')
218 .filter(|seg| !seg.is_empty())
219 .map(|seg| {
220 seg.split(|&b| b == b'\x00')
221 .map(|s| s.to_vec())
222 .collect::<Vec<Vec<u8>>>()
223 })
224 .collect(),
225 )
226 };
227 let start = parse_int(chunk[2])?;
228 let end = parse_int(chunk[3])?;
229 out.push(WireFactory {
230 key,
231 parents,
232 start,
233 end,
234 });
235 }
236 Ok(out)
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use flate2::write::ZlibEncoder;
243 use flate2::Compression;
244 use std::io::Write;
245
246 fn build_wire(header_lines: &[u8], block_bytes: &[u8]) -> Vec<u8> {
247 let mut z = ZlibEncoder::new(Vec::new(), Compression::default());
248 z.write_all(header_lines).unwrap();
249 let z_header = z.finish().unwrap();
250 let mut out = Vec::new();
251 out.extend_from_slice(b"groupcompress-block\n");
252 out.extend_from_slice(format!("{}\n", z_header.len()).as_bytes());
253 out.extend_from_slice(format!("{}\n", header_lines.len()).as_bytes());
254 out.extend_from_slice(format!("{}\n", block_bytes.len()).as_bytes());
255 out.extend_from_slice(&z_header);
256 out.extend_from_slice(block_bytes);
257 out
258 }
259
260 #[test]
261 fn round_trip_single_factory() {
262 let header = b"file-id\x00rev\nNone:\n0\n42\n";
263 let block = b"BLOCK_PAYLOAD";
264 let wire = build_wire(header, block);
265 let frame = parse_wire(&wire).unwrap();
266 assert_eq!(frame.block_bytes, block);
267 assert_eq!(frame.factories.len(), 1);
268 let f = &frame.factories[0];
269 assert_eq!(f.key, vec![b"file-id".to_vec(), b"rev".to_vec()]);
270 assert!(f.parents.is_none());
271 assert_eq!(f.start, 0);
272 assert_eq!(f.end, 42);
273 }
274
275 #[test]
276 fn parents_split_on_tab_and_nul() {
277 let header = b"k\nf\x00p1\tf\x00p2\n0\n10\n";
278 let wire = build_wire(header, b"");
279 let frame = parse_wire(&wire).unwrap();
280 let parents = frame.factories[0].parents.as_ref().unwrap();
281 assert_eq!(
282 *parents,
283 vec![
284 vec![b"f".to_vec(), b"p1".to_vec()],
285 vec![b"f".to_vec(), b"p2".to_vec()],
286 ]
287 );
288 }
289
290 #[test]
291 fn empty_parents_list_is_some_empty() {
292 let header = b"k\n\n0\n10\n";
295 let wire = build_wire(header, b"");
296 let frame = parse_wire(&wire).unwrap();
297 assert_eq!(frame.factories[0].parents.as_ref().unwrap().len(), 0);
298 }
299
300 #[test]
301 fn rejects_unknown_storage_kind() {
302 let bytes = b"something-else\n0\n0\n0\n";
303 assert!(matches!(
304 parse_wire(bytes),
305 Err(Error::UnknownStorageKind(_))
306 ));
307 }
308
309 #[test]
310 fn rejects_non_multiple_of_four_header_lines() {
311 let header = b"a\nb\nc\n";
312 let wire = build_wire(header, b"");
313 assert!(matches!(parse_wire(&wire), Err(Error::NotMultipleOfFour)));
314 }
315
316 #[test]
317 fn build_header_lines_emits_python_format() {
318 let factories = vec![WireFactory {
319 key: vec![b"file-id".to_vec(), b"rev".to_vec()],
320 parents: None,
321 start: 0,
322 end: 42,
323 }];
324 assert_eq!(
325 build_header_lines(&factories),
326 b"file-id\x00rev\nNone:\n0\n42\n"
327 );
328 }
329
330 #[test]
331 fn build_header_lines_emits_multiple_parents_with_tab_separator() {
332 let factories = vec![WireFactory {
333 key: vec![b"k".to_vec()],
334 parents: Some(vec![
335 vec![b"f".to_vec(), b"p1".to_vec()],
336 vec![b"f".to_vec(), b"p2".to_vec()],
337 ]),
338 start: 1,
339 end: 2,
340 }];
341 assert_eq!(
342 build_header_lines(&factories),
343 b"k\nf\x00p1\tf\x00p2\n1\n2\n"
344 );
345 }
346
347 #[test]
348 fn build_header_lines_empty_parents_list_emits_empty_line() {
349 let factories = vec![WireFactory {
352 key: vec![b"k".to_vec()],
353 parents: Some(vec![]),
354 start: 0,
355 end: 1,
356 }];
357 assert_eq!(build_header_lines(&factories), b"k\n\n0\n1\n");
358 }
359
360 #[test]
361 fn build_wire_prefix_round_trips_via_parse_wire() {
362 let factories = vec![
363 WireFactory {
364 key: vec![b"file-a".to_vec(), b"rev1".to_vec()],
365 parents: None,
366 start: 0,
367 end: 32,
368 },
369 WireFactory {
370 key: vec![b"file-b".to_vec(), b"rev2".to_vec()],
371 parents: Some(vec![vec![b"file-a".to_vec(), b"rev1".to_vec()]]),
372 start: 32,
373 end: 96,
374 },
375 ];
376 let block = b"BLOCK_PAYLOAD";
377 let mut wire = build_wire_prefix(&factories, block.len()).unwrap();
378 wire.extend_from_slice(block);
379
380 let frame = parse_wire(&wire).unwrap();
381 assert_eq!(frame.block_bytes, block);
382 assert_eq!(frame.factories, factories);
383 }
384}