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