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