geop_ops_assembly/urdf/
zip.rs1use geop_core_math::geop_error::{GeopError, GeopResult};
11
12fn crc32(bytes: &[u8]) -> u32 {
14 let mut crc = !0u32;
15 for &byte in bytes {
16 crc ^= u32::from(byte);
17 for _ in 0..8 {
18 let mask = (crc & 1).wrapping_neg();
19 crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
20 }
21 }
22 !crc
23}
24
25pub fn zip(files: &[(String, Vec<u8>)]) -> GeopResult<Vec<u8>> {
29 let small = |n: usize, what: &str| {
30 u32::try_from(n)
31 .map_err(|_| GeopError::new(format!("{what} is too large for a ZIP archive")))
32 };
33 let mut out = Vec::new();
34 let mut central = Vec::new();
35 for (path, bytes) in files {
36 let offset = small(out.len(), "the archive")?;
37 let size = small(bytes.len(), path)?;
38 let name_len = u16::try_from(path.len()).map_err(|_| {
39 GeopError::new(format!("the path {path:?} is too long for a ZIP archive"))
40 })?;
41 let crc = crc32(bytes);
42 let mut common = Vec::new();
46 common.extend(10u16.to_le_bytes());
47 common.extend((1u16 << 11).to_le_bytes());
48 common.extend(0u16.to_le_bytes());
49 common.extend(0u16.to_le_bytes());
50 common.extend(((1u16 << 5) | 1).to_le_bytes());
51 common.extend(crc.to_le_bytes());
52 common.extend(size.to_le_bytes());
53 common.extend(size.to_le_bytes());
54 common.extend(name_len.to_le_bytes());
55
56 out.extend(0x0403_4b50u32.to_le_bytes());
57 out.extend(&common);
58 out.extend(0u16.to_le_bytes()); out.extend(path.as_bytes());
60 out.extend(bytes);
61
62 central.extend(0x0201_4b50u32.to_le_bytes());
63 central.extend(20u16.to_le_bytes()); central.extend(&common);
65 central.extend([0u8; 12]); central.extend(offset.to_le_bytes());
67 central.extend(path.as_bytes());
68 }
69 let count = u16::try_from(files.len())
70 .map_err(|_| GeopError::new("too many files for a ZIP archive"))?;
71 let start = small(out.len(), "the archive")?;
72 let length = small(central.len(), "the archive's directory")?;
73 out.extend(central);
74 out.extend(0x0605_4b50u32.to_le_bytes());
75 out.extend([0u8; 4]); out.extend(count.to_le_bytes());
77 out.extend(count.to_le_bytes());
78 out.extend(length.to_le_bytes());
79 out.extend(start.to_le_bytes());
80 out.extend(0u16.to_le_bytes()); Ok(out)
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 fn u16_at(bytes: &[u8], at: usize) -> usize {
89 u16::from_le_bytes([bytes[at], bytes[at + 1]]) as usize
90 }
91
92 fn u32_at(bytes: &[u8], at: usize) -> usize {
93 u32::from_le_bytes(bytes[at..at + 4].try_into().unwrap()) as usize
94 }
95
96 #[test]
98 fn crc_of_the_check_string() {
99 assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
100 assert_eq!(crc32(b""), 0);
101 }
102
103 #[test]
106 fn files_are_found_through_the_central_directory() {
107 let files = vec![
108 ("robot.urdf".to_string(), b"<robot/>".to_vec()),
109 ("meshes/link.stl".to_string(), vec![0, 1, 2, 255]),
110 ];
111 let archive = zip(&files).unwrap();
112 let end = archive.len() - 22;
113 assert_eq!(u32_at(&archive, end), 0x0605_4b50);
114 assert_eq!(u16_at(&archive, end + 10), 2);
115 let mut entry = u32_at(&archive, end + 16);
116 for (path, bytes) in &files {
117 assert_eq!(u32_at(&archive, entry), 0x0201_4b50);
118 assert_eq!(u32_at(&archive, entry + 16), crc32(bytes) as usize);
119 let name_len = u16_at(&archive, entry + 28);
120 assert_eq!(&archive[entry + 46..entry + 46 + name_len], path.as_bytes());
121 let local = u32_at(&archive, entry + 42);
122 assert_eq!(u32_at(&archive, local), 0x0403_4b50);
123 let size = u32_at(&archive, local + 22);
124 let data = local + 30 + u16_at(&archive, local + 26) + u16_at(&archive, local + 28);
125 assert_eq!(&archive[data..data + size], bytes.as_slice());
126 entry += 46 + name_len;
127 }
128 assert_eq!(entry, end);
129 }
130}