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