put common state into struct
[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 endian: 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 struct CommonState<R: Read + Seek> {
231     reader: R,
232     endian: Endian,
233     bias: f64,
234     compression: Option<Compression>,
235     var_types: Vec<VarType>,
236 }
237
238 impl<R: Read + Seek + 'static> State for Start<R> {
239     fn read(mut self: Box<Self>) -> Result<Option<(Record, Box<dyn State>)>, Error> {
240         let header = read_header(&mut self.reader)?;
241         let next_state = Headers(CommonState {
242             reader: self.reader,
243             endian: header.endian,
244             bias: header.bias,
245             compression: header.compression,
246             var_types: Vec::new(),
247         });
248         Ok(Some((Record::Header(header), Box::new(next_state))))
249     }
250 }
251
252 struct Headers<R: Read + Seek>(CommonState<R>);
253
254 impl<R: Read + Seek + 'static> State for Headers<R> {
255     fn read(mut self: Box<Self>) -> Result<Option<(Record, Box<dyn State>)>, Error> {
256         let endian = self.0.endian;
257         let rec_type: u32 = endian.parse(read_bytes(&mut self.0.reader)?);
258         let record = match rec_type {
259             2 => {
260                 let variable = read_variable_record(&mut self.0.reader, endian)?;
261                 self.0.var_types.push(VarType::from_width(variable.width));
262                 Record::Variable(variable)
263             }
264             3 => Record::ValueLabel(read_value_label_record(&mut self.0.reader, endian)?),
265             4 => Record::VarIndexes(read_var_indexes_record(&mut self.0.reader, endian)?),
266             6 => Record::Document(read_document_record(&mut self.0.reader, endian)?),
267             7 => Record::Extension(read_extension_record(&mut self.0.reader, endian)?),
268             999 => {
269                 let _: [u8; 4] = read_bytes(&mut self.0.reader)?;
270                 let next_state: Box<dyn State> = match self.0.compression {
271                     None => Box::new(Data(self.0)),
272                     Some(Compression::Simple) => Box::new(CompressedData::new(self.0)),
273                     Some(Compression::ZLib) => Box::new(CompressedData::new(CommonState {
274                         reader: ZlibDecodeMultiple::new(self.0.reader),
275                         endian: self.0.endian,
276                         bias: self.0.bias,
277                         compression: self.0.compression,
278                         var_types: self.0.var_types
279                     })),
280                 };
281                 return Ok(Some((Record::EndOfHeaders, next_state)));
282             }
283             _ => {
284                 return Err(Error::BadRecordType {
285                     offset: self.0.reader.stream_position()?,
286                     rec_type,
287                 })
288             }
289         };
290         Ok(Some((record, self)))
291     }
292 }
293
294 struct Data<R: Read + Seek>(CommonState<R>);
295
296 impl<R: Read + Seek + 'static> State for Data<R> {
297     fn read(mut self: Box<Self>) -> Result<Option<(Record, Box<dyn State>)>, Error> {
298         let case_start = self.0.reader.stream_position()?;
299         let mut values = Vec::with_capacity(self.0.var_types.len());
300         for (i, &var_type) in self.0.var_types.iter().enumerate() {
301             let Some(raw) = try_read_bytes(&mut self.0.reader)? else {
302                 if i == 0 {
303                     return Ok(None);
304                 } else {
305                     let offset = self.0.reader.stream_position()?;
306                     return Err(Error::EofInCase {
307                         offset,
308                         case_ofs: offset - case_start,
309                         case_len: self.0.var_types.len() * 8,
310                     });
311                 }
312             };
313             values.push(Value::from_raw(var_type, raw, self.0.endian));
314         }
315         Ok(Some((Record::Case(values), self)))
316     }
317 }
318
319 struct CompressedData<R: Read + Seek> {
320     common: CommonState<R>,
321     codes: VecDeque<u8>,
322 }
323
324 impl<R: Read + Seek + 'static> CompressedData<R> {
325     fn new(common: CommonState<R>) -> CompressedData<R> {
326         CompressedData { common, codes: VecDeque::new() }
327     }
328 }
329
330 impl<R: Read + Seek + 'static> State for CompressedData<R> {
331     fn read(mut self: Box<Self>) -> Result<Option<(Record, Box<dyn State>)>, Error> {
332         let case_start = self.common.reader.stream_position()?;
333         let mut values = Vec::with_capacity(self.common.var_types.len());
334         for (i, &var_type) in self.common.var_types.iter().enumerate() {
335             let value = loop {
336                 let Some(code) = self.codes.pop_front() else {
337                     let Some(new_codes): Option<[u8; 8]> = try_read_bytes(&mut self.common.reader)?
338                     else {
339                         if i == 0 {
340                             return Ok(None);
341                         } else {
342                             let offset = self.common.reader.stream_position()?;
343                             return Err(Error::EofInCompressedCase {
344                                 offset,
345                                 case_ofs: offset - case_start,
346                             });
347                         }
348                     };
349                     self.codes.extend(new_codes.into_iter());
350                     continue;
351                 };
352                 match code {
353                     0 => (),
354                     1..=251 => match var_type {
355                         VarType::Number => break Value::Number(Some(code as f64 - self.common.bias)),
356                         VarType::String => {
357                             break Value::String(self.common.endian.to_bytes(code as f64 - self.common.bias))
358                         }
359                     },
360                     252 => {
361                         if i == 0 {
362                             return Ok(None);
363                         } else {
364                             let offset = self.common.reader.stream_position()?;
365                             return Err(Error::PartialCompressedCase {
366                                 offset,
367                                 case_ofs: offset - case_start,
368                             });
369                         }
370                     }
371                     253 => {
372                         break Value::from_raw(
373                             var_type,
374                             read_bytes(&mut self.common.reader)?,
375                             self.common.endian,
376                         )
377                     }
378                     254 => match var_type {
379                         VarType::String => break Value::String(*b"        "), // XXX EBCDIC
380                         VarType::Number => {
381                             return Err(Error::CompressedStringExpected {
382                                 offset: case_start,
383                                 case_ofs: self.common.reader.stream_position()? - case_start,
384                             })
385                         }
386                     },
387                     255 => match var_type {
388                         VarType::Number => break Value::Number(None),
389                         VarType::String => {
390                             return Err(Error::CompressedNumberExpected {
391                                 offset: case_start,
392                                 case_ofs: self.common.reader.stream_position()? - case_start,
393                             })
394                         }
395                     },
396                 }
397             };
398             values.push(value);
399         }
400         Ok(Some((Record::Case(values), self)))
401     }
402 }
403
404 struct ZlibDecodeMultiple<R>
405 where
406     R: Read + Seek,
407 {
408     reader: Option<ZlibDecoder<R>>,
409 }
410
411 impl<R> ZlibDecodeMultiple<R>
412 where
413     R: Read + Seek,
414 {
415     fn new(reader: R) -> ZlibDecodeMultiple<R> {
416         ZlibDecodeMultiple {
417             reader: Some(ZlibDecoder::new(reader)),
418         }
419     }
420 }
421
422 impl<R> Read for ZlibDecodeMultiple<R>
423 where
424     R: Read + Seek,
425 {
426     fn read(&mut self, buf: &mut [u8]) -> Result<usize, IoError> {
427         loop {
428             match self.reader.as_mut().unwrap().read(buf)? {
429                 0 => {
430                     let inner = self.reader.take().unwrap().into_inner();
431                     self.reader = Some(ZlibDecoder::new(inner));
432                 }
433                 n => return Ok(n),
434             };
435         }
436     }
437 }
438
439 impl<R> Seek for ZlibDecodeMultiple<R>
440 where
441     R: Read + Seek,
442 {
443     fn seek(&mut self, pos: SeekFrom) -> Result<u64, IoError> {
444         unimplemented!();
445     }
446 }
447
448 #[derive(Copy, Clone)]
449 pub enum Value {
450     Number(Option<f64>),
451     String([u8; 8]),
452 }
453
454 impl Value {
455     pub fn from_raw(var_type: VarType, raw: [u8; 8], endian: Endian) -> Value {
456         match var_type {
457             VarType::String => Value::String(raw),
458             VarType::Number => {
459                 let number: f64 = endian.parse(raw);
460                 Value::Number((number != -f64::MAX).then_some(number))
461             }
462         }
463     }
464 }
465
466 pub struct Reader {
467     state: Option<Box<dyn State>>,
468 }
469
470 impl Reader {
471     pub fn new<R: Read + Seek + 'static>(reader: R) -> Result<Reader, Error> {
472         Ok(Reader {
473             state: Some(Box::new(Start { reader })),
474         })
475     }
476 }
477
478 impl Iterator for Reader {
479     type Item = Result<Record, Error>;
480
481     fn next(&mut self) -> Option<Self::Item> {
482         match self.state.take()?.read() {
483             Ok(Some((record, next_state))) => {
484                 self.state = Some(next_state);
485                 return Some(Ok(record));
486             }
487             Ok(None) => return None,
488             Err(error) => return Some(Err(error)),
489         }
490     }
491 }
492
493 impl FusedIterator for Reader {}
494
495 fn read_header<R: Read>(r: &mut R) -> Result<Header, Error> {
496     let magic: [u8; 4] = read_bytes(r)?;
497     let magic: Magic = magic.try_into().map_err(|_| Error::NotASystemFile)?;
498
499     let eye_catcher: [u8; 60] = read_bytes(r)?;
500     let layout_code: [u8; 4] = read_bytes(r)?;
501     let endian = Endian::identify_u32(2, layout_code)
502         .or_else(|| Endian::identify_u32(2, layout_code))
503         .ok_or_else(|| Error::NotASystemFile)?;
504     let layout_code = endian.parse(layout_code);
505
506     let nominal_case_size: u32 = endian.parse(read_bytes(r)?);
507     let nominal_case_size =
508         (nominal_case_size <= i32::MAX as u32 / 16).then_some(nominal_case_size);
509
510     let compression_code: u32 = endian.parse(read_bytes(r)?);
511     let compression = match (magic, compression_code) {
512         (Magic::ZSAV, 2) => Some(Compression::ZLib),
513         (Magic::ZSAV, code) => return Err(Error::InvalidZsavCompression(code)),
514         (_, 0) => None,
515         (_, 1) => Some(Compression::Simple),
516         (_, code) => return Err(Error::InvalidSavCompression(code)),
517     };
518
519     let weight_index: u32 = endian.parse(read_bytes(r)?);
520     let weight_index = (weight_index > 0).then_some(weight_index - 1);
521
522     let n_cases: u32 = endian.parse(read_bytes(r)?);
523     let n_cases = (n_cases < i32::MAX as u32 / 2).then_some(n_cases);
524
525     let bias: f64 = endian.parse(read_bytes(r)?);
526
527     let creation_date: [u8; 9] = read_bytes(r)?;
528     let creation_time: [u8; 8] = read_bytes(r)?;
529     let file_label: [u8; 64] = read_bytes(r)?;
530     let _: [u8; 3] = read_bytes(r)?;
531
532     Ok(Header {
533         magic,
534         layout_code,
535         nominal_case_size,
536         compression,
537         weight_index,
538         n_cases,
539         bias,
540         creation_date,
541         creation_time,
542         eye_catcher,
543         file_label,
544         endian,
545     })
546 }
547
548 pub struct Variable {
549     /// Offset from the start of the file to the start of the record.
550     pub offset: u64,
551
552     /// Variable width, in the range -1..=255.
553     pub width: i32,
554
555     /// Variable name, padded on the right with spaces.
556     pub name: [u8; 8],
557
558     /// Print format.
559     pub print_format: u32,
560
561     /// Write format.
562     pub write_format: u32,
563
564     /// Missing value code, one of -3, -2, 0, 1, 2, or 3.
565     pub missing_value_code: i32,
566
567     /// Raw missing values, up to 3 of them.
568     pub missing: Vec<[u8; 8]>,
569
570     /// Optional variable label.
571     pub label: Option<Vec<u8>>,
572 }
573
574 fn read_variable_record<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<Variable, Error> {
575     let offset = r.stream_position()?;
576     let width: i32 = endian.parse(read_bytes(r)?);
577     let has_variable_label: u32 = endian.parse(read_bytes(r)?);
578     let missing_value_code: i32 = endian.parse(read_bytes(r)?);
579     let print_format: u32 = endian.parse(read_bytes(r)?);
580     let write_format: u32 = endian.parse(read_bytes(r)?);
581     let name: [u8; 8] = read_bytes(r)?;
582
583     let label = match has_variable_label {
584         0 => None,
585         1 => {
586             let len: u32 = endian.parse(read_bytes(r)?);
587             let read_len = len.min(65535) as usize;
588             let label = Some(read_vec(r, read_len)?);
589
590             let padding_bytes = Integer::next_multiple_of(&len, &4) - len;
591             let _ = read_vec(r, padding_bytes as usize)?;
592
593             label
594         }
595         _ => {
596             return Err(Error::BadVariableLabelCode {
597                 offset,
598                 code: has_variable_label,
599             })
600         }
601     };
602
603     let mut missing = Vec::new();
604     if missing_value_code != 0 {
605         match (width, missing_value_code) {
606             (0, -3 | -2 | 1 | 2 | 3) => (),
607             (0, _) => {
608                 return Err(Error::BadNumericMissingValueCode {
609                     offset,
610                     code: missing_value_code,
611                 })
612             }
613             (_, 0..=3) => (),
614             (_, _) => {
615                 return Err(Error::BadStringMissingValueCode {
616                     offset,
617                     code: missing_value_code,
618                 })
619             }
620         }
621
622         for _ in 0..missing_value_code.abs() {
623             missing.push(read_bytes(r)?);
624         }
625     }
626
627     Ok(Variable {
628         offset,
629         width,
630         name,
631         print_format,
632         write_format,
633         missing_value_code,
634         missing,
635         label,
636     })
637 }
638
639 pub struct ValueLabel {
640     /// Offset from the start of the file to the start of the record.
641     pub offset: u64,
642
643     /// The labels.
644     pub labels: Vec<([u8; 8], Vec<u8>)>,
645 }
646
647 impl ValueLabel {
648     /// Maximum number of value labels in a record.
649     pub const MAX: u32 = u32::MAX / 8;
650 }
651
652 fn read_value_label_record<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<ValueLabel, Error> {
653     let offset = r.stream_position()?;
654     let n: u32 = endian.parse(read_bytes(r)?);
655     if n > ValueLabel::MAX {
656         return Err(Error::BadNumberOfValueLabels {
657             offset,
658             n,
659             max: ValueLabel::MAX,
660         });
661     }
662
663     let mut labels = Vec::new();
664     for _ in 0..n {
665         let value: [u8; 8] = read_bytes(r)?;
666         let label_len: u8 = endian.parse(read_bytes(r)?);
667         let label_len = label_len as usize;
668         let padded_len = Integer::next_multiple_of(&(label_len + 1), &8);
669
670         let mut label = read_vec(r, padded_len)?;
671         label.truncate(label_len);
672         labels.push((value, label));
673     }
674     Ok(ValueLabel { offset, labels })
675 }
676
677 pub struct VarIndexes {
678     /// Offset from the start of the file to the start of the record.
679     pub offset: u64,
680
681     /// The 0-based indexes of the variable indexes.
682     pub var_indexes: Vec<u32>,
683 }
684
685 impl VarIndexes {
686     /// Maximum number of variable indexes in a record.
687     pub const MAX: u32 = u32::MAX / 8;
688 }
689
690 fn read_var_indexes_record<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<VarIndexes, Error> {
691     let offset = r.stream_position()?;
692     let n: u32 = endian.parse(read_bytes(r)?);
693     if n > VarIndexes::MAX {
694         return Err(Error::BadNumberOfVarIndexes {
695             offset,
696             n,
697             max: VarIndexes::MAX,
698         });
699     }
700     let mut var_indexes = Vec::with_capacity(n as usize);
701     for _ in 0..n {
702         var_indexes.push(endian.parse(read_bytes(r)?));
703     }
704
705     Ok(VarIndexes {
706         offset,
707         var_indexes,
708     })
709 }
710
711 pub const DOC_LINE_LEN: u32 = 80;
712 pub const DOC_MAX_LINES: u32 = i32::MAX as u32 / DOC_LINE_LEN;
713
714 pub struct Document {
715     /// Offset from the start of the file to the start of the record.
716     pub pos: u64,
717
718     /// The document, as an array of 80-byte lines.
719     pub lines: Vec<[u8; DOC_LINE_LEN as usize]>,
720 }
721
722 fn read_document_record<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<Document, Error> {
723     let offset = r.stream_position()?;
724     let n: u32 = endian.parse(read_bytes(r)?);
725     match n {
726         0..=DOC_MAX_LINES => {
727             let pos = r.stream_position()?;
728             let mut lines = Vec::with_capacity(n as usize);
729             for _ in 0..n {
730                 let line: [u8; 80] = read_bytes(r)?;
731                 lines.push(line);
732             }
733             Ok(Document { pos, lines })
734         }
735         _ => Err(Error::BadDocumentLength {
736             offset,
737             n,
738             max: DOC_MAX_LINES,
739         }),
740     }
741 }
742
743 #[derive(FromPrimitive)]
744 enum ExtensionType {
745     /// Machine integer info.
746     Integer = 3,
747     /// Machine floating-point info.
748     Float = 4,
749     /// Variable sets.
750     VarSets = 5,
751     /// DATE.
752     Date = 6,
753     /// Multiple response sets.
754     Mrsets = 7,
755     /// SPSS Data Entry.
756     DataEntry = 8,
757     /// Extra product info text.
758     ProductInfo = 10,
759     /// Variable display parameters.
760     Display = 11,
761     /// Long variable names.
762     LongNames = 13,
763     /// Long strings.
764     LongStrings = 14,
765     /// Extended number of cases.
766     Ncases = 16,
767     /// Data file attributes.
768     FileAttrs = 17,
769     /// Variable attributes.
770     VarAttrs = 18,
771     /// Multiple response sets (extended).
772     Mrsets2 = 19,
773     /// Character encoding.
774     Encoding = 20,
775     /// Value labels for long strings.
776     LongLabels = 21,
777     /// Missing values for long strings.
778     LongMissing = 22,
779     /// "Format properties in dataview table".
780     Dataview = 24,
781 }
782
783 pub struct Extension {
784     /// Offset from the start of the file to the start of the record.
785     pub offset: u64,
786
787     /// Record subtype.
788     pub subtype: u32,
789
790     /// Size of each data element.
791     pub size: u32,
792
793     /// Number of data elements.
794     pub count: u32,
795
796     /// `size * count` bytes of data.
797     pub data: Vec<u8>,
798 }
799
800 fn extension_record_size_requirements(extension: ExtensionType) -> (u32, u32) {
801     match extension {
802         /* Implemented record types. */
803         ExtensionType::Integer => (4, 8),
804         ExtensionType::Float => (8, 3),
805         ExtensionType::VarSets => (1, 0),
806         ExtensionType::Mrsets => (1, 0),
807         ExtensionType::ProductInfo => (1, 0),
808         ExtensionType::Display => (4, 0),
809         ExtensionType::LongNames => (1, 0),
810         ExtensionType::LongStrings => (1, 0),
811         ExtensionType::Ncases => (8, 2),
812         ExtensionType::FileAttrs => (1, 0),
813         ExtensionType::VarAttrs => (1, 0),
814         ExtensionType::Mrsets2 => (1, 0),
815         ExtensionType::Encoding => (1, 0),
816         ExtensionType::LongLabels => (1, 0),
817         ExtensionType::LongMissing => (1, 0),
818
819         /* Ignored record types. */
820         ExtensionType::Date => (0, 0),
821         ExtensionType::DataEntry => (0, 0),
822         ExtensionType::Dataview => (0, 0),
823     }
824 }
825
826 fn read_extension_record<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<Extension, Error> {
827     let subtype = endian.parse(read_bytes(r)?);
828     let offset = r.stream_position()?;
829     let size: u32 = endian.parse(read_bytes(r)?);
830     let count = endian.parse(read_bytes(r)?);
831     let Some(product) = size.checked_mul(count) else {
832         return Err(Error::ExtensionRecordTooLarge {
833             offset,
834             subtype,
835             size,
836             count,
837         });
838     };
839     let offset = r.stream_position()?;
840     let data = read_vec(r, product as usize)?;
841     Ok(Extension {
842         offset,
843         subtype,
844         size,
845         count,
846         data,
847     })
848 }
849
850 pub struct ZHeader {
851     /// File offset to the start of the record.
852     pub offset: u64,
853
854     /// File offset to the ZLIB data header.
855     pub zheader_offset: u64,
856
857     /// File offset to the ZLIB trailer.
858     pub ztrailer_offset: u64,
859
860     /// Length of the ZLIB trailer in bytes.
861     pub ztrailer_len: u64,
862 }
863
864 fn read_zheader<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<ZHeader, Error> {
865     let offset = r.stream_position()?;
866     let zheader_offset: u64 = endian.parse(read_bytes(r)?);
867     let ztrailer_offset: u64 = endian.parse(read_bytes(r)?);
868     let ztrailer_len: u64 = endian.parse(read_bytes(r)?);
869
870     Ok(ZHeader {
871         offset,
872         zheader_offset,
873         ztrailer_offset,
874         ztrailer_len,
875     })
876 }
877
878 pub struct ZTrailer {
879     /// File offset to the start of the record.
880     pub offset: u64,
881
882     /// Compression bias as a negative integer, e.g. -100.
883     pub int_bias: i64,
884
885     /// Always observed as zero.
886     pub zero: u64,
887
888     /// Uncompressed size of each block, except possibly the last.  Only
889     /// `0x3ff000` has been observed so far.
890     pub block_size: u32,
891
892     /// Block descriptors, always `(ztrailer_len - 24) / 24)` of them.
893     pub blocks: Vec<ZBlock>,
894 }
895
896 pub struct ZBlock {
897     /// Offset of block of data if simple compression were used.
898     pub uncompressed_ofs: u64,
899
900     /// Actual offset within the file of the compressed data block.
901     pub compressed_ofs: u64,
902
903     /// The number of bytes in this data block after decompression.  This is
904     /// `block_size` in every data block but the last, which may be smaller.
905     pub uncompressed_size: u32,
906
907     /// The number of bytes in this data block, as stored compressed in this
908     /// file.
909     pub compressed_size: u32,
910 }
911
912 fn read_ztrailer<R: Read + Seek>(
913     r: &mut R,
914     endian: Endian,
915     ztrailer_ofs: u64,
916     ztrailer_len: u64,
917 ) -> Result<Option<ZTrailer>, Error> {
918     let start_offset = r.stream_position()?;
919     if r.seek(SeekFrom::Start(ztrailer_ofs)).is_err() {
920         return Ok(None);
921     }
922     let int_bias = endian.parse(read_bytes(r)?);
923     let zero = endian.parse(read_bytes(r)?);
924     let block_size = endian.parse(read_bytes(r)?);
925     let n_blocks: u32 = endian.parse(read_bytes(r)?);
926     let expected_n_blocks = (ztrailer_len - 24) / 24;
927     if n_blocks as u64 != expected_n_blocks {
928         return Err(Error::BadZlibTrailerNBlocks {
929             offset: ztrailer_ofs,
930             n_blocks,
931             expected_n_blocks,
932             ztrailer_len,
933         });
934     }
935     let mut blocks = Vec::with_capacity(n_blocks as usize);
936     for _ in 0..n_blocks {
937         let uncompressed_ofs = endian.parse(read_bytes(r)?);
938         let compressed_ofs = endian.parse(read_bytes(r)?);
939         let uncompressed_size = endian.parse(read_bytes(r)?);
940         let compressed_size = endian.parse(read_bytes(r)?);
941         blocks.push(ZBlock {
942             uncompressed_ofs,
943             compressed_ofs,
944             uncompressed_size,
945             compressed_size,
946         });
947     }
948     r.seek(SeekFrom::Start(start_offset))?;
949     Ok(Some(ZTrailer {
950         offset: ztrailer_ofs,
951         int_bias,
952         zero,
953         block_size,
954         blocks,
955     }))
956 }
957
958 fn try_read_bytes<const N: usize, R: Read>(r: &mut R) -> Result<Option<[u8; N]>, IoError> {
959     let mut buf = [0; N];
960     let n = r.read(&mut buf)?;
961     if n > 0 {
962         if n < N {
963             r.read_exact(&mut buf[n..])?;
964         }
965         Ok(Some(buf))
966     } else {
967         Ok(None)
968     }
969 }
970
971 fn read_bytes<const N: usize, R: Read>(r: &mut R) -> Result<[u8; N], IoError> {
972     let mut buf = [0; N];
973     r.read_exact(&mut buf)?;
974     Ok(buf)
975 }
976
977 fn read_vec<R: Read>(r: &mut R, n: usize) -> Result<Vec<u8>, IoError> {
978     let mut vec = vec![0; n];
979     r.read_exact(&mut vec)?;
980     Ok(vec)
981 }
982
983 /*
984 fn trim_end(mut s: Vec<u8>, c: u8) -> Vec<u8> {
985     while s.last() == Some(&c) {
986         s.pop();
987     }
988     s
989 }
990
991 fn skip_bytes<R: Read>(r: &mut R, mut n: u64) -> Result<(), IoError> {
992     let mut buf = [0; 1024];
993     while n > 0 {
994         let chunk = u64::min(n, buf.len() as u64);
995         r.read_exact(&mut buf[0..chunk as usize])?;
996         n -= chunk;
997     }
998     Ok(())
999 }
1000
1001 */