1use crate::error::{DecodeError, Reason};
9
10type Result<T, E = DecodeError> = std::result::Result<T, E>;
11
12pub const SEMI: u32 = 0x0312B;
14pub const MAX_DEPTH: usize = 64;
16const PROLOG_RANGE: std::ops::RangeInclusive<u32> = 0x02600..=0x02FFF;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum ObjectType {
23 SystemBinary,
25 Real,
27 LongReal,
29 Complex,
31 LongComplex,
33 Character,
35 Array,
37 LinkedArray,
39 String,
41 BinaryInteger,
43 List,
45 Directory,
47 Algebraic,
49 Unit,
51 Tagged,
53 Graphic,
55 Library,
57 Backup,
59 LibraryData,
61 Program,
63 Code,
65 GlobalName,
67 LocalName,
69 XlibName,
71 Integer,
73 SymbolicMatrix,
75 LongReal49,
77 LongComplex49,
79 FlashPointer,
81 Aplet,
83 MiniFont,
85 ExtendedPointer,
87 Extended2,
89 Extended3,
91 Extended4,
93}
94
95#[derive(Clone, Copy, Debug)]
97enum Extent {
98 Fixed(usize),
100 Lengths(usize),
103 Composite,
105 Tagged,
107 Name,
109 Directory,
111}
112
113use Extent::{Composite, Fixed, Lengths, Name};
114
115const TYPES: [(ObjectType, u32, &str, Extent); 35] = [
117 (
118 ObjectType::SystemBinary,
119 0x02911,
120 "System Binary",
121 Fixed(10),
122 ),
123 (ObjectType::Real, 0x02933, "Real Number", Fixed(21)),
124 (ObjectType::LongReal, 0x02955, "Long Real", Fixed(26)),
125 (ObjectType::Complex, 0x02977, "Complex Number", Fixed(37)),
126 (ObjectType::LongComplex, 0x0299D, "Long Complex", Fixed(47)),
127 (ObjectType::Character, 0x029BF, "Character", Fixed(7)),
128 (ObjectType::Array, 0x029E8, "Array", Lengths(1)),
129 (ObjectType::LinkedArray, 0x02A0A, "Linked Array", Lengths(1)),
130 (ObjectType::String, 0x02A2C, "String", Lengths(1)),
131 (
132 ObjectType::BinaryInteger,
133 0x02A4E,
134 "Binary Integer",
135 Lengths(1),
136 ),
137 (ObjectType::List, 0x02A74, "List", Composite),
138 (
139 ObjectType::Directory,
140 0x02A96,
141 "Directory",
142 Extent::Directory,
143 ),
144 (ObjectType::Algebraic, 0x02AB8, "Algebraic", Composite),
145 (ObjectType::Unit, 0x02ADA, "Unit", Composite),
146 (ObjectType::Tagged, 0x02AFC, "Tagged", Extent::Tagged),
147 (ObjectType::Graphic, 0x02B1E, "Graphic", Lengths(1)),
148 (ObjectType::Library, 0x02B40, "Library", Lengths(1)),
149 (ObjectType::Backup, 0x02B62, "Backup", Lengths(1)),
150 (ObjectType::LibraryData, 0x02B88, "Library Data", Lengths(1)),
151 (ObjectType::Program, 0x02D9D, "Program", Composite),
152 (ObjectType::Code, 0x02DCC, "Code", Lengths(1)),
153 (ObjectType::GlobalName, 0x02E48, "Global Name", Name),
154 (ObjectType::LocalName, 0x02E6D, "Local Name", Name),
155 (ObjectType::XlibName, 0x02E92, "XLIB Name", Fixed(11)),
156 (ObjectType::Integer, 0x02614, "Integer", Lengths(1)),
157 (
158 ObjectType::SymbolicMatrix,
159 0x02686,
160 "Symbolic Matrix",
161 Composite,
162 ),
163 (
165 ObjectType::LongReal49,
166 0x0263A,
167 "Long Real (49G)",
168 Lengths(2),
169 ),
170 (
172 ObjectType::LongComplex49,
173 0x02660,
174 "Long Complex (49G)",
175 Lengths(4),
176 ),
177 (
179 ObjectType::FlashPointer,
180 0x026AC,
181 "Flash Pointer",
182 Fixed(12),
183 ),
184 (ObjectType::Aplet, 0x026D5, "Aplet", Lengths(1)),
186 (ObjectType::MiniFont, 0x026FE, "Mini Font", Lengths(1)),
187 (
189 ObjectType::ExtendedPointer,
190 0x02BAA,
191 "Extended Pointer",
192 Fixed(15),
193 ),
194 (ObjectType::Extended2, 0x02BCC, "Extended 2", Lengths(1)),
195 (ObjectType::Extended3, 0x02BEE, "Extended 3", Lengths(1)),
196 (ObjectType::Extended4, 0x02C10, "Extended 4", Lengths(1)),
197];
198
199impl ObjectType {
200 pub fn from_prolog(prolog: u32) -> Option<Self> {
202 TYPES.iter().find(|t| t.1 == prolog).map(|t| t.0)
203 }
204
205 fn entry(self) -> &'static (ObjectType, u32, &'static str, Extent) {
206 &TYPES[self as usize]
208 }
209
210 pub fn prolog(self) -> u32 {
212 self.entry().1
213 }
214
215 pub fn name(self) -> &'static str {
217 self.entry().2
218 }
219}
220
221pub fn read_field(n: &[u8], at: usize, width: usize) -> Option<u32> {
224 if width > 8 {
225 return None;
226 }
227 let f = n.get(at..at.checked_add(width)?)?;
228 Some(
229 f.iter()
230 .rev()
231 .fold(0, |acc, &d| (acc << 4) | u32::from(d & 0xF)),
232 )
233}
234
235fn field(n: &[u8], at: usize, width: usize) -> Result<usize> {
236 let v = read_field(n, at, width).ok_or(DecodeError::new(at, Reason::Truncated))?;
237 usize::try_from(v).map_err(|_| DecodeError::new(at, Reason::SizeOverflow))
238}
239
240fn add(a: usize, b: usize) -> Result<usize> {
241 a.checked_add(b)
242 .ok_or(DecodeError::new(a, Reason::SizeOverflow))
243}
244
245pub fn object_size(n: &[u8], at: usize) -> Result<usize> {
249 walk(n, at, 0)
250}
251
252pub fn element_size(n: &[u8], at: usize, depth: usize) -> Result<usize> {
256 let p = field(n, at, 5)? as u32;
257 if ObjectType::from_prolog(p).is_some() {
258 walk(n, at, depth)
259 } else if PROLOG_RANGE.contains(&p) {
260 Err(DecodeError::new(at, Reason::UnknownProlog(p)))
261 } else {
262 Ok(5)
263 }
264}
265
266fn walk(n: &[u8], at: usize, depth: usize) -> Result<usize> {
267 if depth > MAX_DEPTH {
268 return Err(DecodeError::new(at, Reason::TooDeep));
269 }
270 let prolog = field(n, at, 5)? as u32;
271 let ty = ObjectType::from_prolog(prolog)
272 .ok_or(DecodeError::new(at, Reason::UnknownProlog(prolog)))?;
273 let body = add(at, 5)?;
274 let size = match ty.entry().3 {
275 Fixed(size) => size,
276 Lengths(count) => {
277 let mut pos = body;
278 for _ in 0..count {
279 let len = field(n, pos, 5)?;
280 if len < 5 {
281 return Err(DecodeError::new(pos, Reason::LengthTooShort(len)));
282 }
283 pos = add(pos, len)?;
284 }
285 pos - at
286 }
287 Composite => {
288 let mut pos = body;
289 while field(n, pos, 5)? != SEMI as usize {
290 pos = add(pos, element_size(n, pos, depth + 1)?)?;
291 }
292 add(pos, 5)? - at
293 }
294 Extent::Tagged => {
295 let inner = add(body, 2 + 2 * field(n, body, 2)?)?;
296 add(inner, element_size(n, inner, depth + 1)?)? - at
297 }
298 Name => 7 + 2 * field(n, body, 2)?,
299 Extent::Directory => directory_size(n, at, depth)?,
300 };
301 if add(at, size)? > n.len() {
302 return Err(DecodeError::new(
303 at,
304 Reason::DoesNotFit {
305 kind: ty,
306 size,
307 left: n.len().saturating_sub(at),
308 },
309 ));
310 }
311 Ok(size)
312}
313
314#[derive(Clone, Copy, Debug, PartialEq, Eq)]
321pub struct Record {
322 pub start: usize,
324 pub name_at: usize,
326 pub name_len: usize,
328 pub object: usize,
330 pub object_size: usize,
332}
333
334impl Record {
335 pub fn at(n: &[u8], start: usize, depth: usize) -> Result<Record> {
337 let name_at = add(start, 5)?;
338 let name_len = field(n, name_at, 2)?;
339 let mut object = add(name_at, 2 + 2 * name_len)?;
340 if name_len != 0 {
341 object = add(object, 2)?;
342 }
343 let object_size = element_size(n, object, depth + 1)?;
344 Ok(Record {
345 start,
346 name_at,
347 name_len,
348 object,
349 object_size,
350 })
351 }
352
353 pub fn end(&self) -> usize {
355 self.object + self.object_size
356 }
357}
358
359pub fn records(n: &[u8], offset_at: usize, depth: usize) -> Result<Vec<Record>> {
362 let offset = field(n, offset_at, 5)?;
363 let mut out = Vec::new();
364 if offset == 0 {
365 return Ok(out);
366 }
367 let last = add(offset_at, offset)?;
368 let mut pos = add(offset_at, 5)?;
369 loop {
370 let r = Record::at(n, pos, depth)?;
371 if r.name_at > last {
372 return Err(DecodeError::new(offset_at, Reason::DirectoryOffset));
373 }
374 pos = r.end();
375 let done = r.name_at == last;
376 out.push(r);
377 if done {
378 return Ok(out);
379 }
380 }
381}
382
383fn directory_size(n: &[u8], at: usize, depth: usize) -> Result<usize> {
387 let offset_at = add(at, 8)?;
388 let end = records(n, offset_at, depth)?
389 .last()
390 .map_or(offset_at + 5, Record::end);
391 Ok(end - at)
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 fn nib(s: &str) -> Vec<u8> {
399 s.chars()
400 .map(|c| c.to_digit(16).unwrap_or(0) as u8)
401 .collect()
402 }
403
404 #[test]
405 fn table_is_in_declaration_order() {
406 for (i, t) in TYPES.iter().enumerate() {
407 assert_eq!(t.0 as usize, i, "{:?}", t.0);
408 assert_eq!(ObjectType::from_prolog(t.1), Some(t.0));
409 }
410 }
411
412 #[test]
413 fn sizes() {
414 assert_eq!(object_size(&nib("339200000000000000050"), 0).ok(), Some(21));
415 assert_eq!(
417 object_size(&nib("47A209C2A2C2A207000014B2130"), 0).ok(),
418 Some(27)
419 );
420 assert!(object_size(&nib("3392000"), 0).is_err());
421 assert!(object_size(&nib("C2A2030000"), 0).is_err());
422 let d = nib("69A20FF7A00000000010A510339200000000000000050");
424 assert_eq!(object_size(&d, 0).ok(), Some(45));
425 let rs = records(&d, 8, 0).unwrap_or_default();
426 assert_eq!(rs.len(), 1);
427 assert_eq!(
428 (rs[0].name_len, rs[0].object, rs[0].object_size),
429 (1, 24, 21)
430 );
431 assert_eq!(object_size(&nib("69A20FF700000"), 0).ok(), Some(13));
433 }
434}