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