rust_hdf5/format/messages/
external_file_list.rs1use crate::format::bytes::{read_le_addr as read_addr, read_le_uint as read_uint};
33use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
34
35const VERSION: u8 = 1;
36
37pub const UNLIMITED: u64 = u64::MAX;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct ExternalFileSlot {
47 pub name_offset: u64,
50 pub offset: u64,
53 pub size: u64,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct ExternalFileListMessage {
62 pub heap_addr: u64,
64 pub slots: Vec<ExternalFileSlot>,
67}
68
69impl ExternalFileListMessage {
70 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
76 let sa = ctx.sizeof_addr as usize;
77 let ss = ctx.sizeof_size as usize;
78 let nused = self.slots.len() as u16;
79 let mut buf = Vec::with_capacity(8 + sa + self.slots.len() * 3 * ss);
80 buf.push(VERSION);
81 buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(&nused.to_le_bytes()); buf.extend_from_slice(&nused.to_le_bytes()); buf.extend_from_slice(&self.heap_addr.to_le_bytes()[..sa]);
85 for slot in &self.slots {
86 buf.extend_from_slice(&slot.name_offset.to_le_bytes()[..ss]);
87 buf.extend_from_slice(&slot.offset.to_le_bytes()[..ss]);
88 buf.extend_from_slice(&slot.size.to_le_bytes()[..ss]);
89 }
90 buf
91 }
92
93 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
94 let sa = ctx.sizeof_addr as usize;
95 let ss = ctx.sizeof_size as usize;
96
97 if buf.len() < 4 + 4 {
98 return Err(FormatError::BufferTooShort {
99 needed: 4 + 4,
100 available: buf.len(),
101 });
102 }
103 let version = buf[0];
104 if version != VERSION {
105 return Err(FormatError::InvalidVersion(version));
106 }
107 let nalloc = u16::from_le_bytes([buf[4], buf[5]]);
109 let nused = u16::from_le_bytes([buf[6], buf[7]]);
110 if nalloc == 0 {
111 return Err(FormatError::InvalidData(
112 "external file list message declares zero allocated slots".into(),
113 ));
114 }
115 if nused > nalloc {
116 return Err(FormatError::InvalidData(format!(
117 "external file list message has {nused} in-use slots but only {nalloc} allocated"
118 )));
119 }
120
121 let mut pos = 8;
122 if buf.len() < pos + sa {
123 return Err(FormatError::BufferTooShort {
124 needed: pos + sa,
125 available: buf.len(),
126 });
127 }
128 let heap_addr = read_addr(&buf[pos..], sa);
129 pos += sa;
130 if heap_addr == UNDEF_ADDR {
131 return Err(FormatError::InvalidData(
132 "external file list message has an undefined local heap address".into(),
133 ));
134 }
135
136 let slot_len = ss * 3;
137 let mut slots = Vec::with_capacity(nused as usize);
138 for _ in 0..nused {
139 if buf.len() < pos + slot_len {
140 return Err(FormatError::BufferTooShort {
141 needed: pos + slot_len,
142 available: buf.len(),
143 });
144 }
145 let name_offset = read_uint(&buf[pos..], ss);
146 pos += ss;
147 let offset = read_uint(&buf[pos..], ss);
148 pos += ss;
149 let size = read_uint(&buf[pos..], ss);
150 pos += ss;
151 slots.push(ExternalFileSlot {
152 name_offset,
153 offset,
154 size,
155 });
156 }
157
158 Ok((Self { heap_addr, slots }, pos))
159 }
160}
161
162#[cfg(test)]
165mod tests {
166 use super::*;
167
168 fn ctx8() -> FormatContext {
169 FormatContext {
170 sizeof_addr: 8,
171 sizeof_size: 8,
172 }
173 }
174
175 fn single_slot_buf() -> Vec<u8> {
179 let mut buf = vec![VERSION, 0, 0, 0];
180 buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&1072u64.to_le_bytes()); buf.extend_from_slice(&8u64.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&64u64.to_le_bytes()); buf
187 }
188
189 #[test]
190 fn decode_single_slot() {
191 let buf = single_slot_buf();
192 let (msg, consumed) = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap();
193 assert_eq!(consumed, buf.len());
194 assert_eq!(msg.heap_addr, 1072);
195 assert_eq!(
196 msg.slots,
197 vec![ExternalFileSlot {
198 name_offset: 8,
199 offset: 0,
200 size: 64,
201 }]
202 );
203 }
204
205 #[test]
206 fn decode_multi_slot() {
207 let mut buf = vec![VERSION, 0, 0, 0];
208 buf.extend_from_slice(&2u16.to_le_bytes()); buf.extend_from_slice(&2u16.to_le_bytes()); buf.extend_from_slice(&2000u64.to_le_bytes()); buf.extend_from_slice(&8u64.to_le_bytes());
212 buf.extend_from_slice(&0u64.to_le_bytes());
213 buf.extend_from_slice(&32u64.to_le_bytes());
214 buf.extend_from_slice(&20u64.to_le_bytes());
215 buf.extend_from_slice(&0u64.to_le_bytes());
216 buf.extend_from_slice(&32u64.to_le_bytes());
217 let (msg, consumed) = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap();
218 assert_eq!(consumed, buf.len());
219 assert_eq!(msg.slots.len(), 2);
220 assert_eq!(msg.slots[0].size, 32);
221 assert_eq!(msg.slots[1].name_offset, 20);
222 }
223
224 #[test]
225 fn decode_rejects_bad_version() {
226 let mut buf = single_slot_buf();
227 buf[0] = 2;
228 let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
229 assert!(matches!(err, FormatError::InvalidVersion(2)));
230 }
231
232 #[test]
233 fn decode_rejects_zero_nalloc() {
234 let mut buf = single_slot_buf();
235 buf[4] = 0;
236 buf[5] = 0;
237 let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
238 assert!(matches!(err, FormatError::InvalidData(_)));
239 }
240
241 #[test]
242 fn decode_rejects_nused_over_nalloc() {
243 let mut buf = single_slot_buf();
244 buf[6] = 2; let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
246 assert!(matches!(err, FormatError::InvalidData(_)));
247 }
248
249 #[test]
250 fn decode_rejects_undefined_heap_addr() {
251 let mut buf = single_slot_buf();
252 buf[8..16].copy_from_slice(&[0xFFu8; 8]);
253 let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
254 assert!(matches!(err, FormatError::InvalidData(_)));
255 }
256
257 #[test]
258 fn decode_truncated() {
259 let buf = single_slot_buf();
260 let err = ExternalFileListMessage::decode(&buf[..20], &ctx8()).unwrap_err();
261 assert!(matches!(err, FormatError::BufferTooShort { .. }));
262 }
263
264 #[test]
265 fn decode_ctx4() {
266 let ctx = FormatContext {
267 sizeof_addr: 4,
268 sizeof_size: 4,
269 };
270 let mut buf = vec![VERSION, 0, 0, 0];
271 buf.extend_from_slice(&1u16.to_le_bytes());
272 buf.extend_from_slice(&1u16.to_le_bytes());
273 buf.extend_from_slice(&0x800u32.to_le_bytes()); buf.extend_from_slice(&8u32.to_le_bytes());
275 buf.extend_from_slice(&0u32.to_le_bytes());
276 buf.extend_from_slice(&64u32.to_le_bytes());
277 let (msg, consumed) = ExternalFileListMessage::decode(&buf, &ctx).unwrap();
278 assert_eq!(consumed, buf.len());
279 assert_eq!(msg.heap_addr, 0x800);
280 assert_eq!(msg.slots[0].size, 64);
281 }
282
283 #[test]
288 fn decode_preserves_unlimited_sentinel() {
289 let mut buf = single_slot_buf();
290 let last = buf.len() - 8;
291 buf[last..].copy_from_slice(&[0xFFu8; 8]);
292 let (msg, _) = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap();
293 assert_eq!(msg.slots[0].size, UNLIMITED);
294 }
295
296 #[test]
299 fn encode_matches_the_captured_single_slot_message() {
300 let msg = ExternalFileListMessage {
301 heap_addr: 1072,
302 slots: vec![ExternalFileSlot {
303 name_offset: 8,
304 offset: 0,
305 size: 64,
306 }],
307 };
308 assert_eq!(msg.encode(&ctx8()), single_slot_buf());
309 }
310
311 #[test]
312 fn encode_roundtrips_multi_slot_at_ctx4() {
313 let ctx = FormatContext {
314 sizeof_addr: 4,
315 sizeof_size: 4,
316 };
317 let msg = ExternalFileListMessage {
318 heap_addr: 0x800,
319 slots: vec![
320 ExternalFileSlot {
321 name_offset: 8,
322 offset: 0,
323 size: 32,
324 },
325 ExternalFileSlot {
326 name_offset: 20,
327 offset: 16,
328 size: 32,
329 },
330 ],
331 };
332 let buf = msg.encode(&ctx);
333 let (back, consumed) = ExternalFileListMessage::decode(&buf, &ctx).unwrap();
334 assert_eq!(consumed, buf.len());
335 assert_eq!(back, msg);
336 }
337}