1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use crate::error::FormatError;
7
8const GCOL_SIGNATURE: [u8; 4] = [b'G', b'C', b'O', b'L'];
10
11#[derive(Debug, Clone)]
13pub struct GlobalHeapCollection {
14 pub collection_size: u64,
16 pub objects: Vec<GlobalHeapObject>,
18}
19
20#[derive(Debug, Clone)]
22pub struct GlobalHeapObject {
23 pub index: u16,
25 pub reference_count: u16,
27 pub data: Vec<u8>,
29}
30
31fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
32 match offset.checked_add(needed) {
33 Some(end) if end <= data.len() => Ok(()),
34 _ => Err(FormatError::UnexpectedEof {
35 expected: offset.saturating_add(needed),
36 available: data.len(),
37 }),
38 }
39}
40
41fn read_length(data: &[u8], offset: usize, length_size: u8) -> Result<u64, FormatError> {
42 let s = length_size as usize;
43 ensure_len(data, offset, s)?;
44 let slice = &data[offset..offset + s];
45 Ok(match length_size {
46 2 => u16::from_le_bytes([slice[0], slice[1]]) as u64,
47 4 => u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as u64,
48 8 => u64::from_le_bytes([
49 slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
50 ]),
51 _ => return Err(FormatError::InvalidLengthSize(length_size)),
52 })
53}
54
55fn pad8(x: usize) -> usize {
57 (x + 7) & !7
58}
59
60impl GlobalHeapCollection {
61 pub fn parse(
63 file_data: &[u8],
64 offset: usize,
65 length_size: u8,
66 ) -> Result<GlobalHeapCollection, FormatError> {
67 let header_size = 8 + length_size as usize;
69 ensure_len(file_data, offset, header_size)?;
70
71 if file_data[offset..offset + 4] != GCOL_SIGNATURE {
72 return Err(FormatError::InvalidGlobalHeapSignature);
73 }
74
75 let version = file_data[offset + 4];
76 if version != 1 {
77 return Err(FormatError::InvalidGlobalHeapVersion(version));
78 }
79
80 let collection_size = read_length(file_data, offset + 8, length_size)?;
81 let collection_end = offset + collection_size as usize;
82
83 let mut pos = offset + header_size;
84 let mut objects = Vec::new();
85
86 while pos + 2 <= collection_end {
88 ensure_len(file_data, pos, 2)?;
89 let object_index = u16::from_le_bytes([file_data[pos], file_data[pos + 1]]);
90
91 if object_index == 0 {
92 break;
94 }
95
96 let obj_header_size = 8 + length_size as usize;
98 ensure_len(file_data, pos, obj_header_size)?;
99
100 let reference_count =
101 u16::from_le_bytes([file_data[pos + 2], file_data[pos + 3]]);
102 let object_size = read_length(file_data, pos + 8, length_size)? as usize;
103
104 pos += obj_header_size;
105 ensure_len(file_data, pos, object_size)?;
106 let data = file_data[pos..pos + object_size].to_vec();
107
108 objects.push(GlobalHeapObject {
109 index: object_index,
110 reference_count,
111 data,
112 });
113
114 pos += pad8(object_size);
116 }
117
118 Ok(GlobalHeapCollection {
119 collection_size,
120 objects,
121 })
122 }
123
124 pub fn get_object(&self, index: u16) -> Option<&GlobalHeapObject> {
126 self.objects.iter().find(|o| o.index == index)
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 fn build_collection(
136 objects: &[(u16, u16, &[u8])], length_size: u8,
138 ) -> Vec<u8> {
139 let ls = length_size as usize;
140
141 let header_size = 8 + ls;
143 let mut obj_size_total = 0usize;
144 for (_, _, data) in objects {
145 let obj_header = 8 + ls;
146 obj_size_total += obj_header + pad8(data.len());
147 }
148 obj_size_total += 2;
150 let collection_size = header_size + obj_size_total;
151
152 let mut buf = Vec::new();
153 buf.extend_from_slice(&GCOL_SIGNATURE);
154 buf.push(1); buf.extend_from_slice(&[0u8; 3]); match length_size {
159 4 => buf.extend_from_slice(&(collection_size as u32).to_le_bytes()),
160 8 => buf.extend_from_slice(&(collection_size as u64).to_le_bytes()),
161 _ => panic!("unsupported length_size"),
162 }
163
164 for (index, ref_count, data) in objects {
166 buf.extend_from_slice(&index.to_le_bytes());
167 buf.extend_from_slice(&ref_count.to_le_bytes());
168 buf.extend_from_slice(&[0u8; 4]); match length_size {
170 4 => buf.extend_from_slice(&(data.len() as u32).to_le_bytes()),
171 8 => buf.extend_from_slice(&(data.len() as u64).to_le_bytes()),
172 _ => panic!("unsupported"),
173 }
174 buf.extend_from_slice(data);
175 let padded = pad8(data.len());
177 for _ in data.len()..padded {
178 buf.push(0);
179 }
180 }
181
182 buf.extend_from_slice(&0u16.to_le_bytes());
184
185 buf
186 }
187
188 #[test]
189 fn parse_collection_two_objects() {
190 let data = build_collection(
191 &[
192 (1, 1, b"hello"),
193 (2, 1, b"world!!!"),
194 ],
195 8,
196 );
197 let coll = GlobalHeapCollection::parse(&data, 0, 8).unwrap();
198 assert_eq!(coll.objects.len(), 2);
199 assert_eq!(coll.objects[0].index, 1);
200 assert_eq!(coll.objects[0].data, b"hello");
201 assert_eq!(coll.objects[1].index, 2);
202 assert_eq!(coll.objects[1].data, b"world!!!");
203 }
204
205 #[test]
206 fn get_object_by_index() {
207 let data = build_collection(
208 &[
209 (1, 1, b"aaa"),
210 (3, 2, b"bbb"),
211 ],
212 8,
213 );
214 let coll = GlobalHeapCollection::parse(&data, 0, 8).unwrap();
215 let obj = coll.get_object(3).unwrap();
216 assert_eq!(obj.data, b"bbb");
217 assert_eq!(obj.reference_count, 2);
218 assert!(coll.get_object(99).is_none());
219 }
220
221 #[test]
222 fn free_space_terminates_parsing() {
223 let mut data = Vec::new();
225 data.extend_from_slice(&GCOL_SIGNATURE);
226 data.push(1);
227 data.extend_from_slice(&[0u8; 3]);
228 let size = 8u64 + 8 + 2; data.extend_from_slice(&size.to_le_bytes());
230 data.extend_from_slice(&0u16.to_le_bytes()); let coll = GlobalHeapCollection::parse(&data, 0, 8).unwrap();
233 assert_eq!(coll.objects.len(), 0);
234 }
235
236 #[test]
237 fn invalid_signature_error() {
238 let mut data = build_collection(&[(1, 1, b"x")], 8);
239 data[0] = b'X'; let err = GlobalHeapCollection::parse(&data, 0, 8).unwrap_err();
241 assert_eq!(err, FormatError::InvalidGlobalHeapSignature);
242 }
243
244 #[test]
245 fn invalid_version_error() {
246 let mut data = build_collection(&[(1, 1, b"x")], 8);
247 data[4] = 2; let err = GlobalHeapCollection::parse(&data, 0, 8).unwrap_err();
249 assert_eq!(err, FormatError::InvalidGlobalHeapVersion(2));
250 }
251
252 #[test]
253 fn parse_with_4byte_length() {
254 let data = build_collection(&[(1, 1, b"test")], 4);
255 let coll = GlobalHeapCollection::parse(&data, 0, 4).unwrap();
256 assert_eq!(coll.objects.len(), 1);
257 assert_eq!(coll.objects[0].data, b"test");
258 }
259}