work
[pspp] / rust / src / lib.rs
1 #![allow(unused_variables)]
2 use endian::{Endian, Parse, ToBytes};
3 use num::Integer;
4 use num_derive::FromPrimitive;
5 use std::{
6     collections::VecDeque,
7     io::{BufReader, Error as IoError, Read, Seek},
8 };
9 use thiserror::Error;
10
11 pub mod endian;
12
13 #[derive(Error, Debug)]
14 pub enum Error {
15     #[error("Not an SPSS system file")]
16     NotASystemFile,
17
18     #[error("Invalid magic number {0:?}")]
19     BadMagic([u8; 4]),
20
21     #[error("I/O error ({0})")]
22     Io(#[from] IoError),
23
24     #[error("Invalid SAV compression code {0}")]
25     InvalidSavCompression(u32),
26
27     #[error("Invalid ZSAV compression code {0}")]
28     InvalidZsavCompression(u32),
29
30     #[error("Variable record at offset {offset:#x} specifies width {width} not in valid range [-1,255).")]
31     BadVariableWidth { offset: u64, width: i32 },
32
33     #[error("Misplaced type 4 record near offset {0:#x}.")]
34     MisplacedType4Record(u64),
35
36     #[error("Document record at offset {offset:#x} has document line count ({n}) greater than the maximum number {max}.")]
37     BadDocumentLength { offset: u64, n: u32, max: u32 },
38
39     #[error("At offset {offset:#x}, Unrecognized record type {rec_type}.")]
40     BadRecordType { offset: u64, rec_type: u32 },
41
42     #[error("At offset {offset:#x}, variable label code ({code}) is not 0 or 1.")]
43     BadVariableLabelCode { offset: u64, code: u32 },
44
45     #[error(
46         "At offset {offset:#x}, numeric missing value code ({code}) is not -3, -2, 0, 1, 2, or 3."
47     )]
48     BadNumericMissingValueCode { offset: u64, code: i32 },
49
50     #[error("At offset {offset:#x}, string missing value code ({code}) is not 0, 1, 2, or 3.")]
51     BadStringMissingValueCode { offset: u64, code: i32 },
52
53     #[error("At offset {offset:#x}, number of value labels ({n}) is greater than the maximum number {max}.")]
54     BadNumberOfValueLabels { offset: u64, n: u32, max: u32 },
55
56     #[error("At offset {offset:#x}, variable index record (type 4) does not immediately follow value label record (type 3) as it should.")]
57     MissingVariableIndexRecord { offset: u64 },
58
59     #[error("At offset {offset:#x}, number of variables indexes ({n}) is greater than the maximum number ({max}).")]
60     BadNumberOfVarIndexes { offset: u64, n: u32, max: u32 },
61
62     #[error("At offset {offset:#x}, record type 7 subtype {subtype} is too large with element size {size} and {count} elements.")]
63     ExtensionRecordTooLarge {
64         offset: u64,
65         subtype: u32,
66         size: u32,
67         count: u32,
68     },
69
70     #[error("Wrong ZLIB data header offset {zheader_offset:#x} (expected {offset:#x}).")]
71     BadZlibHeaderOffset { offset: u64, zheader_offset: u64 },
72
73     #[error("At offset {offset:#x}, impossible ZLIB trailer offset {ztrailer_offset:#x}.")]
74     BadZlibTrailerOffset { offset: u64, ztrailer_offset: u64 },
75
76     #[error("At offset {offset:#x}, impossible ZLIB trailer length {ztrailer_len}.")]
77     BadZlibTrailerLen { offset: u64, ztrailer_len: u64 },
78
79     #[error("Unexpected end of file at offset {offset:#x}, {case_ofs} bytes into a {case_len}-byte case.")]
80     EofInCase {
81         offset: u64,
82         case_ofs: u64,
83         case_len: usize,
84     },
85
86     #[error(
87         "Unexpected end of file at offset {offset:#x}, {case_ofs} bytes into a compressed case."
88     )]
89     EofInCompressedCase { offset: u64, case_ofs: u64 },
90
91     #[error("Data ends at offset {offset:#x}, {case_ofs} bytes into a compressed case.")]
92     PartialCompressedCase { offset: u64, case_ofs: u64 },
93
94     #[error("At {case_ofs} bytes into compressed case starting at offset {offset:#x}, a string was found where a number was expected.")]
95     CompressedNumberExpected { offset: u64, case_ofs: u64 },
96
97     #[error("At {case_ofs} bytes into compressed case starting at offset {offset:#x}, a number was found where a string was expected.")]
98     CompressedStringExpected { offset: u64, case_ofs: u64 },
99 }
100
101 #[derive(Error, Debug)]
102 pub enum Warning {
103     #[error("Unexpected floating-point bias {0} or unrecognized floating-point format.")]
104     UnexpectedBias(f64),
105
106     #[error("Duplicate type 6 (document) record.")]
107     DuplicateDocumentRecord,
108 }
109
110 #[derive(Copy, Clone, Debug)]
111 pub enum Compression {
112     Simple,
113     ZLib,
114 }
115
116 pub enum Record {
117     Header(Header),
118     Document(Document),
119     Variable(Variable),
120     ValueLabel(ValueLabel),
121     VarIndexes(VarIndexes),
122     Extension(Extension),
123     EndOfHeaders,
124     Case(Vec<Value>),
125 }
126
127 pub struct Header {
128     /// Magic number.
129     pub magic: Magic,
130
131     /// Eye-catcher string, product name, in the file's encoding.  Padded
132     /// on the right with spaces.
133     pub eye_catcher: [u8; 60],
134
135     /// Layout code, normally either 2 or 3.
136     pub layout_code: u32,
137
138     /// Number of variable positions, or `None` if the value in the file is
139     /// questionably trustworthy.
140     pub nominal_case_size: Option<u32>,
141
142     /// Compression type, if any,
143     pub compression: Option<Compression>,
144
145     /// 0-based variable index of the weight variable, or `None` if the file is
146     /// unweighted.
147     pub weight_index: Option<u32>,
148
149     /// Claimed number of cases, if known.
150     pub n_cases: Option<u32>,
151
152     /// Compression bias, usually 100.0.
153     pub bias: f64,
154
155     /// `dd mmm yy` in the file's encoding.
156     pub creation_date: [u8; 9],
157
158     /// `HH:MM:SS` in the file's encoding.
159     pub creation_time: [u8; 8],
160
161     /// File label, in the file's encoding.  Padded on the right with spaces.
162     pub file_label: [u8; 64],
163
164     /// Endianness of the data in the file header.
165     pub endianness: Endian,
166 }
167
168 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
169 pub struct Magic([u8; 4]);
170
171 impl Magic {
172     /// Magic number for a regular system file.
173     pub const SAV: Magic = Magic(*b"$FL2");
174
175     /// Magic number for a system file that contains zlib-compressed data.
176     pub const ZSAV: Magic = Magic(*b"$FL3");
177
178     /// Magic number for an EBDIC-encoded system file.  This is `$FL2` encoded
179     /// in EBCDIC.
180     pub const EBCDIC: Magic = Magic([0x5b, 0xc6, 0xd3, 0xf2]);
181 }
182
183 impl TryFrom<[u8; 4]> for Magic {
184     type Error = Error;
185
186     fn try_from(value: [u8; 4]) -> Result<Self, Self::Error> {
187         let magic = Magic(value);
188         match magic {
189             Magic::SAV | Magic::ZSAV | Magic::EBCDIC => Ok(magic),
190             _ => Err(Error::BadMagic(value)),
191         }
192     }
193 }
194
195 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
196 pub enum VarType {
197     Number,
198     String,
199 }
200
201 impl VarType {
202     fn from_width(width: i32) -> VarType {
203         match width {
204             0 => VarType::Number,
205             _ => VarType::String,
206         }
207     }
208 }
209
210 pub struct Reader<R: Read> {
211     r: BufReader<R>,
212     var_types: Vec<VarType>,
213     state: ReaderState,
214 }
215
216 enum ReaderState {
217     Start,
218     Headers(Endian, Option<Compression>),
219     Data(Endian),
220     CompressedData(Endian, VecDeque<u8>),
221     End,
222 }
223
224 #[derive(Copy, Clone)]
225 pub enum Value {
226     Number(Option<f64>),
227     String([u8; 8]),
228 }
229
230 impl Value {
231     pub fn from_raw(var_type: VarType, raw: [u8; 8], endian: Endian) -> Value {
232         match var_type {
233             VarType::String => Value::String(raw),
234             VarType::Number => {
235                 let number: f64 = endian.parse(raw);
236                 Value::Number((number != -f64::MAX).then_some(number))
237             }
238         }
239     }
240 }
241
242 impl<R: Read + Seek> Reader<R> {
243     pub fn new(r: R) -> Result<Reader<R>, Error> {
244         Ok(Reader {
245             r: BufReader::new(r),
246             var_types: Vec::new(),
247             state: ReaderState::Start,
248         })
249     }
250     fn _next(&mut self) -> Result<Option<Record>, Error> {
251         match self.state {
252             ReaderState::Start => {
253                 let header = read_header(&mut self.r)?;
254                 self.state = ReaderState::Headers(header.endianness, header.compression);
255                 Ok(Some(Record::Header(header)))
256             }
257             ReaderState::Headers(endian, compression) => {
258                 let rec_type: u32 = endian.parse(read_bytes(&mut self.r)?);
259                 let record = match rec_type {
260                     2 => {
261                         let variable = read_variable_record(&mut self.r, endian)?;
262                         self.var_types.push(VarType::from_width(variable.width));
263                         Record::Variable(variable)
264                     }
265                     3 => Record::ValueLabel(read_value_label_record(&mut self.r, endian)?),
266                     4 => Record::VarIndexes(read_var_indexes_record(&mut self.r, endian)?),
267                     6 => Record::Document(read_document_record(&mut self.r, endian)?),
268                     7 => Record::Extension(read_extension_record(&mut self.r, endian)?),
269                     999 => {
270                         let _: [u8; 4] = read_bytes(&mut self.r)?;
271                         self.state = match compression {
272                             None => ReaderState::Data(endian),
273                             Some(Compression::Simple) => {
274                                 ReaderState::CompressedData(endian, VecDeque::new())
275                             }
276                             _ => ReaderState::End,
277                         };
278                         return Ok(Some(Record::EndOfHeaders));
279                     }
280                     _ => {
281                         return Err(Error::BadRecordType {
282                             offset: self.r.stream_position()?,
283                             rec_type,
284                         })
285                     }
286                 };
287                 Ok(Some(record))
288             }
289             ReaderState::Data(endian) => {
290                 let case_start = self.r.stream_position()?;
291                 let mut values = Vec::with_capacity(self.var_types.len());
292                 for (i, &var_type) in self.var_types.iter().enumerate() {
293                     let Some(raw) = try_read_bytes(&mut self.r)? else {
294                         if i == 0 {
295                             return Ok(None);
296                         } else {
297                             let offset = self.r.stream_position()?;
298                             return Err(Error::EofInCase { offset, case_ofs: offset - case_start, case_len: self.var_types.len() * 8});
299                         }
300                     };
301                     values.push(Value::from_raw(var_type, raw, endian));
302                 }
303                 Ok(Some(Record::Case(values)))
304             }
305             ReaderState::CompressedData(endian, ref mut codes) => {
306                 let case_start = self.r.stream_position()?;
307                 let mut values = Vec::with_capacity(self.var_types.len());
308                 let bias = 100.0; // XXX
309                 for (i, &var_type) in self.var_types.iter().enumerate() {
310                     let value = loop {
311                         let Some(code) = codes.pop_front() else {
312                             let Some(new_codes): Option<[u8; 8]> = try_read_bytes(&mut self.r)? else {
313                                 if i == 0 {
314                                     return Ok(None);
315                                 } else {
316                                     let offset = self.r.stream_position()?;
317                                     return Err(Error::EofInCompressedCase { offset, case_ofs: offset - case_start});
318                                 }
319                             };
320                             codes.extend(new_codes.into_iter());
321                             continue;
322                         };
323                         match code {
324                             0 => (),
325                             1..=251 => match var_type {
326                                 VarType::Number => break Value::Number(Some(code as f64 - bias)),
327                                 VarType::String => {
328                                     break Value::String(endian.to_bytes(code as f64 - bias))
329                                 }
330                             },
331                             252 => {
332                                 if i == 0 {
333                                     return Ok(None);
334                                 } else {
335                                     let offset = self.r.stream_position()?;
336                                     return Err(Error::PartialCompressedCase {
337                                         offset,
338                                         case_ofs: offset - case_start,
339                                     });
340                                 }
341                             }
342                             253 => break Value::from_raw(
343                                 var_type,
344                                 read_bytes(&mut self.r)?,
345                                 endian,
346                             ),
347                             254 => match var_type {
348                                 VarType::String => break Value::String(*b"        "), // XXX EBCDIC
349                                 VarType::Number => {
350                                     return Err(Error::CompressedStringExpected {
351                                         offset: case_start,
352                                         case_ofs: self.r.stream_position()? - case_start,
353                                     })
354                                 }
355                             },
356                             255 => match var_type {
357                                 VarType::Number => break Value::Number(None),
358                                 VarType::String => {
359                                     return Err(Error::CompressedNumberExpected {
360                                         offset: case_start,
361                                         case_ofs: self.r.stream_position()? - case_start,})
362                                 }
363                             }
364                         }
365                     };
366                     values.push(value);
367                 }
368                 Ok(Some(Record::Case(values)))
369             }
370             ReaderState::End => Ok(None),
371         }
372     }
373 }
374
375 impl<R: Read + Seek> Iterator for Reader<R> {
376     type Item = Result<Record, Error>;
377
378     fn next(&mut self) -> Option<Self::Item> {
379         let retval = self._next();
380         match retval {
381             Ok(None) => {
382                 self.state = ReaderState::End;
383                 None
384             }
385             Ok(Some(record)) => {
386                 Some(Ok(record))
387             }
388             Err(error) => {
389                 self.state = ReaderState::End;
390                 Some(Err(error))
391             }
392         }
393     }
394 }
395
396 fn read_header<R: Read>(r: &mut R) -> Result<Header, Error> {
397     let magic: [u8; 4] = read_bytes(r)?;
398     let magic: Magic = magic.try_into().map_err(|_| Error::NotASystemFile)?;
399
400     let eye_catcher: [u8; 60] = read_bytes(r)?;
401     let layout_code: [u8; 4] = read_bytes(r)?;
402     let endianness = Endian::identify_u32(2, layout_code)
403         .or_else(|| Endian::identify_u32(2, layout_code))
404         .ok_or_else(|| Error::NotASystemFile)?;
405     let layout_code = endianness.parse(layout_code);
406
407     let nominal_case_size: u32 = endianness.parse(read_bytes(r)?);
408     let nominal_case_size =
409         (nominal_case_size <= i32::MAX as u32 / 16).then_some(nominal_case_size);
410
411     let compression_code: u32 = endianness.parse(read_bytes(r)?);
412     let compression = match (magic, compression_code) {
413         (Magic::ZSAV, 2) => Some(Compression::ZLib),
414         (Magic::ZSAV, code) => return Err(Error::InvalidZsavCompression(code)),
415         (_, 0) => None,
416         (_, 1) => Some(Compression::Simple),
417         (_, code) => return Err(Error::InvalidSavCompression(code)),
418     };
419
420     let weight_index: u32 = endianness.parse(read_bytes(r)?);
421     let weight_index = (weight_index > 0).then_some(weight_index - 1);
422
423     let n_cases: u32 = endianness.parse(read_bytes(r)?);
424     let n_cases = (n_cases < i32::MAX as u32 / 2).then_some(n_cases);
425
426     let bias: f64 = endianness.parse(read_bytes(r)?);
427
428     let creation_date: [u8; 9] = read_bytes(r)?;
429     let creation_time: [u8; 8] = read_bytes(r)?;
430     let file_label: [u8; 64] = read_bytes(r)?;
431     let _: [u8; 3] = read_bytes(r)?;
432
433     Ok(Header {
434         magic,
435         layout_code,
436         nominal_case_size,
437         compression,
438         weight_index,
439         n_cases,
440         bias,
441         creation_date,
442         creation_time,
443         eye_catcher,
444         file_label,
445         endianness,
446     })
447 }
448
449 pub struct Variable {
450     /// Offset from the start of the file to the start of the record.
451     pub offset: u64,
452
453     /// Variable width, in the range -1..=255.
454     pub width: i32,
455
456     /// Variable name, padded on the right with spaces.
457     pub name: [u8; 8],
458
459     /// Print format.
460     pub print_format: u32,
461
462     /// Write format.
463     pub write_format: u32,
464
465     /// Missing value code, one of -3, -2, 0, 1, 2, or 3.
466     pub missing_value_code: i32,
467
468     /// Raw missing values, up to 3 of them.
469     pub missing: Vec<[u8; 8]>,
470
471     /// Optional variable label.
472     pub label: Option<Vec<u8>>,
473 }
474
475 fn read_variable_record<R: Read + Seek>(
476     r: &mut BufReader<R>,
477     e: Endian,
478 ) -> Result<Variable, Error> {
479     let offset = r.stream_position()?;
480     let width: i32 = e.parse(read_bytes(r)?);
481     let has_variable_label: u32 = e.parse(read_bytes(r)?);
482     let missing_value_code: i32 = e.parse(read_bytes(r)?);
483     let print_format: u32 = e.parse(read_bytes(r)?);
484     let write_format: u32 = e.parse(read_bytes(r)?);
485     let name: [u8; 8] = read_bytes(r)?;
486
487     let label = match has_variable_label {
488         0 => None,
489         1 => {
490             let len: u32 = e.parse(read_bytes(r)?);
491             let read_len = len.min(65535) as usize;
492             let label = Some(read_vec(r, read_len)?);
493
494             let padding_bytes = Integer::next_multiple_of(&len, &4) - len;
495             let _ = read_vec(r, padding_bytes as usize)?;
496
497             label
498         }
499         _ => {
500             return Err(Error::BadVariableLabelCode {
501                 offset,
502                 code: has_variable_label,
503             })
504         }
505     };
506
507     let mut missing = Vec::new();
508     if missing_value_code != 0 {
509         match (width, missing_value_code) {
510             (0, -3 | -2 | 1 | 2 | 3) => (),
511             (0, _) => {
512                 return Err(Error::BadNumericMissingValueCode {
513                     offset,
514                     code: missing_value_code,
515                 })
516             }
517             (_, 0..=3) => (),
518             (_, _) => {
519                 return Err(Error::BadStringMissingValueCode {
520                     offset,
521                     code: missing_value_code,
522                 })
523             }
524         }
525
526         for _ in 0..missing_value_code.abs() {
527             missing.push(read_bytes(r)?);
528         }
529     }
530
531     Ok(Variable {
532         offset,
533         width,
534         name,
535         print_format,
536         write_format,
537         missing_value_code,
538         missing,
539         label,
540     })
541 }
542
543 pub struct ValueLabel {
544     /// Offset from the start of the file to the start of the record.
545     pub offset: u64,
546
547     /// The labels.
548     pub labels: Vec<([u8; 8], Vec<u8>)>,
549 }
550
551 impl ValueLabel {
552     /// Maximum number of value labels in a record.
553     pub const MAX: u32 = u32::MAX / 8;
554 }
555
556 fn read_value_label_record<R: Read + Seek>(
557     r: &mut BufReader<R>,
558     e: Endian,
559 ) -> Result<ValueLabel, Error> {
560     let offset = r.stream_position()?;
561     let n: u32 = e.parse(read_bytes(r)?);
562     if n > ValueLabel::MAX {
563         return Err(Error::BadNumberOfValueLabels {
564             offset,
565             n,
566             max: ValueLabel::MAX,
567         });
568     }
569
570     let mut labels = Vec::new();
571     for _ in 0..n {
572         let value: [u8; 8] = read_bytes(r)?;
573         let label_len: u8 = e.parse(read_bytes(r)?);
574         let label_len = label_len as usize;
575         let padded_len = Integer::next_multiple_of(&(label_len + 1), &8);
576
577         let mut label = read_vec(r, padded_len)?;
578         label.truncate(label_len);
579         labels.push((value, label));
580     }
581     Ok(ValueLabel { offset, labels })
582 }
583
584 pub struct VarIndexes {
585     /// Offset from the start of the file to the start of the record.
586     pub offset: u64,
587
588     /// The 0-based indexes of the variable indexes.
589     pub var_indexes: Vec<u32>,
590 }
591
592 impl VarIndexes {
593     /// Maximum number of variable indexes in a record.
594     pub const MAX: u32 = u32::MAX / 8;
595 }
596
597 fn read_var_indexes_record<R: Read + Seek>(
598     r: &mut BufReader<R>,
599     e: Endian,
600 ) -> Result<VarIndexes, Error> {
601     let offset = r.stream_position()?;
602     let n: u32 = e.parse(read_bytes(r)?);
603     if n > VarIndexes::MAX {
604         return Err(Error::BadNumberOfVarIndexes {
605             offset,
606             n,
607             max: VarIndexes::MAX,
608         });
609     }
610     let mut var_indexes = Vec::with_capacity(n as usize);
611     for _ in 0..n {
612         var_indexes.push(e.parse(read_bytes(r)?));
613     }
614
615     Ok(VarIndexes {
616         offset,
617         var_indexes,
618     })
619 }
620
621 pub const DOC_LINE_LEN: u32 = 80;
622 pub const DOC_MAX_LINES: u32 = i32::MAX as u32 / DOC_LINE_LEN;
623
624 pub struct Document {
625     /// Offset from the start of the file to the start of the record.
626     pub pos: u64,
627
628     /// The document, as an array of 80-byte lines.
629     pub lines: Vec<[u8; DOC_LINE_LEN as usize]>,
630 }
631
632 fn read_document_record<R: Read + Seek>(
633     r: &mut BufReader<R>,
634     e: Endian,
635 ) -> Result<Document, Error> {
636     let offset = r.stream_position()?;
637     let n: u32 = e.parse(read_bytes(r)?);
638     match n {
639         0..=DOC_MAX_LINES => {
640             let pos = r.stream_position()?;
641             let mut lines = Vec::with_capacity(n as usize);
642             for _ in 0..n {
643                 let line: [u8; 80] = read_bytes(r)?;
644                 lines.push(line);
645             }
646             Ok(Document { pos, lines })
647         }
648         _ => Err(Error::BadDocumentLength {
649             offset,
650             n,
651             max: DOC_MAX_LINES,
652         }),
653     }
654 }
655
656 #[derive(FromPrimitive)]
657 enum ExtensionType {
658     /// Machine integer info.
659     Integer = 3,
660     /// Machine floating-point info.
661     Float = 4,
662     /// Variable sets.
663     VarSets = 5,
664     /// DATE.
665     Date = 6,
666     /// Multiple response sets.
667     Mrsets = 7,
668     /// SPSS Data Entry.
669     DataEntry = 8,
670     /// Extra product info text.
671     ProductInfo = 10,
672     /// Variable display parameters.
673     Display = 11,
674     /// Long variable names.
675     LongNames = 13,
676     /// Long strings.
677     LongStrings = 14,
678     /// Extended number of cases.
679     Ncases = 16,
680     /// Data file attributes.
681     FileAttrs = 17,
682     /// Variable attributes.
683     VarAttrs = 18,
684     /// Multiple response sets (extended).
685     Mrsets2 = 19,
686     /// Character encoding.
687     Encoding = 20,
688     /// Value labels for long strings.
689     LongLabels = 21,
690     /// Missing values for long strings.
691     LongMissing = 22,
692     /// "Format properties in dataview table".
693     Dataview = 24,
694 }
695
696 pub struct Extension {
697     /// Offset from the start of the file to the start of the record.
698     pub offset: u64,
699
700     /// Record subtype.
701     pub subtype: u32,
702
703     /// Size of each data element.
704     pub size: u32,
705
706     /// Number of data elements.
707     pub count: u32,
708
709     /// `size * count` bytes of data.
710     pub data: Vec<u8>,
711 }
712
713 fn extension_record_size_requirements(extension: ExtensionType) -> (u32, u32) {
714     match extension {
715         /* Implemented record types. */
716         ExtensionType::Integer => (4, 8),
717         ExtensionType::Float => (8, 3),
718         ExtensionType::VarSets => (1, 0),
719         ExtensionType::Mrsets => (1, 0),
720         ExtensionType::ProductInfo => (1, 0),
721         ExtensionType::Display => (4, 0),
722         ExtensionType::LongNames => (1, 0),
723         ExtensionType::LongStrings => (1, 0),
724         ExtensionType::Ncases => (8, 2),
725         ExtensionType::FileAttrs => (1, 0),
726         ExtensionType::VarAttrs => (1, 0),
727         ExtensionType::Mrsets2 => (1, 0),
728         ExtensionType::Encoding => (1, 0),
729         ExtensionType::LongLabels => (1, 0),
730         ExtensionType::LongMissing => (1, 0),
731
732         /* Ignored record types. */
733         ExtensionType::Date => (0, 0),
734         ExtensionType::DataEntry => (0, 0),
735         ExtensionType::Dataview => (0, 0),
736     }
737 }
738
739 fn read_extension_record<R: Read + Seek>(
740     r: &mut BufReader<R>,
741     e: Endian,
742 ) -> Result<Extension, Error> {
743     let subtype = e.parse(read_bytes(r)?);
744     let offset = r.stream_position()?;
745     let size: u32 = e.parse(read_bytes(r)?);
746     let count = e.parse(read_bytes(r)?);
747     let Some(product) = size.checked_mul(count) else {
748         return Err(Error::ExtensionRecordTooLarge {
749             offset,
750             subtype,
751             size,
752             count,
753         });
754     };
755     let offset = r.stream_position()?;
756     let data = read_vec(r, product as usize)?;
757     Ok(Extension {
758         offset,
759         subtype,
760         size,
761         count,
762         data,
763     })
764 }
765
766 struct ZHeader {
767     /// File offset to the start of the record.
768     offset: u64,
769
770     /// File offset to the ZLIB data header.
771     zheader_offset: u64,
772
773     /// File offset to the ZLIB trailer.
774     ztrailer_offset: u64,
775
776     /// Length of the ZLIB trailer in bytes.
777     ztrailer_len: u64,
778 }
779
780 fn read_zheader<R: Read + Seek>(r: &mut BufReader<R>, e: Endian) -> Result<ZHeader, Error> {
781     let offset = r.stream_position()?;
782     let zheader_offset: u64 = e.parse(read_bytes(r)?);
783     let ztrailer_offset: u64 = e.parse(read_bytes(r)?);
784     let ztrailer_len: u64 = e.parse(read_bytes(r)?);
785
786     Ok(ZHeader {
787         offset,
788         zheader_offset,
789         ztrailer_offset,
790         ztrailer_len,
791     })
792 }
793
794 fn try_read_bytes<const N: usize, R: Read>(r: &mut R) -> Result<Option<[u8; N]>, IoError> {
795     let mut buf = [0; N];
796     let n = r.read(&mut buf)?;
797     if n > 0 {
798         if n < N {
799             r.read_exact(&mut buf[n..])?;
800         }
801         Ok(Some(buf))
802     } else {
803         Ok(None)
804     }
805 }
806
807 fn read_bytes<const N: usize, R: Read>(r: &mut R) -> Result<[u8; N], IoError> {
808     let mut buf = [0; N];
809     r.read_exact(&mut buf)?;
810     Ok(buf)
811 }
812
813 fn read_vec<R: Read>(r: &mut BufReader<R>, n: usize) -> Result<Vec<u8>, IoError> {
814     let mut vec = vec![0; n];
815     r.read_exact(&mut vec)?;
816     Ok(vec)
817 }
818
819 /*
820 fn trim_end(mut s: Vec<u8>, c: u8) -> Vec<u8> {
821     while s.last() == Some(&c) {
822         s.pop();
823     }
824     s
825 }
826
827 fn skip_bytes<R: Read>(r: &mut R, mut n: u64) -> Result<(), IoError> {
828     let mut buf = [0; 1024];
829     while n > 0 {
830         let chunk = u64::min(n, buf.len() as u64);
831         r.read_exact(&mut buf[0..chunk as usize])?;
832         n -= chunk;
833     }
834     Ok(())
835 }
836
837 */