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