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