work
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 25 Jul 2023 00:34:56 +0000 (17:34 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 25 Jul 2023 00:34:56 +0000 (17:34 -0700)
rust/src/lib.rs

index 2bde62b758b0de13c74896205a3a9a905cd1a717..64e9801447fb4e9ad6f1d567c214cdbe89b6631d 100644 (file)
@@ -12,6 +12,9 @@ pub enum Error {
     #[error("Not an SPSS system file")]
     NotASystemFile,
 
+    #[error("Invalid magic number {0:?}")]
+    BadMagic([u8; 4]),
+
     #[error("I/O error ({source})")]
     Io {
         #[from]
@@ -95,26 +98,9 @@ pub struct Reader<R: Read> {
     zheader: Option<ZHeader>,
 }
 
-/// Magic number for a regular system file.
-pub const ASCII_MAGIC: &[u8; 4] = b"$FL2";
-
-/// Magic number for a system file that contains zlib-compressed data.
-pub const ASCII_ZMAGIC: &[u8; 4] = b"$FL3";
-
-/// Magic number for an EBDIC-encoded system file.  This is `$FL2` encoded in
-/// EBCDIC.
-pub const EBCDIC_MAGIC: &[u8; 4] = &[0x5b, 0xc6, 0xd3, 0xf2];
-
 pub struct FileHeader {
-    /// First 4 bytes of the file, one of `ASCII_MAGIC`, `ASCII_ZMAGIC`, and
-    /// `EBCDIC_MAGIC`.
-    pub magic: [u8; 4],
-
-    /// True if `magic` indicates that this file contained zlib-compressed data.
-    pub is_zsav: bool,
-
-    /// True if `magic` indicates that this file contained EBCDIC data.
-    pub is_ebcdic: bool,
+    /// Magic number.
+    pub magic: Magic,
 
     /// Endianness of the data in the file header.
     pub endianness: Endian,
@@ -141,6 +127,33 @@ pub struct FileHeader {
     pub file_label: [u8; 64],
 }
 
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+pub struct Magic([u8; 4]);
+
+impl Magic {
+    /// Magic number for a regular system file.
+    pub const SAV: Magic = Magic(*b"$FL2");
+
+    /// Magic number for a system file that contains zlib-compressed data.
+    pub const ZSAV: Magic = Magic(*b"$FL3");
+
+    /// Magic number for an EBDIC-encoded system file.  This is `$FL2` encoded
+    /// in EBCDIC.
+    pub const EBCDIC: Magic = Magic([0x5b, 0xc6, 0xd3, 0xf2]);
+}
+
+impl TryFrom<[u8; 4]> for Magic {
+    type Error = Error;
+
+    fn try_from(value: [u8; 4]) -> Result<Self, Self::Error> {
+        let magic = Magic(value);
+        match magic {
+            Magic::SAV | Magic::ZSAV | Magic::EBCDIC => Ok(magic),
+            _ => Err(Error::BadMagic(value)),
+        }
+    }
+}
+
 impl<R: Read + Seek> Reader<R> {
     pub fn new(r: R, warn: impl Fn(Warning)) -> Result<Reader<R>, Error> {
         let mut r = BufReader::new(r);
@@ -165,9 +178,9 @@ impl<R: Read + Seek> Reader<R> {
             }
         }
         let _: [u8; 4] = read_bytes(&mut r)?;
-        let zheader = match header.is_zsav {
-            true => Some(read_zheader(&mut r, e)?),
-            false => None,
+        let zheader = match header.magic {
+            Magic::ZSAV => Some(read_zheader(&mut r, e)?),
+            _ => None,
         };
 
         Ok(Reader {
@@ -183,12 +196,7 @@ impl<R: Read + Seek> Reader<R> {
 
 fn read_header<R: Read>(r: &mut R, warn: impl Fn(Warning)) -> Result<FileHeader, Error> {
     let magic: [u8; 4] = read_bytes(r)?;
-    let (is_zsav, is_ebcdic) = match &magic {
-        ASCII_MAGIC => (false, false),
-        ASCII_ZMAGIC => (true, false),
-        EBCDIC_MAGIC => (false, true),
-        _ => return Err(Error::NotASystemFile),
-    };
+    let magic: Magic = magic.try_into().map_err(|_| Error::NotASystemFile)?;
 
     let eye_catcher: [u8; 60] = read_bytes(r)?;
     let layout_code: [u8; 4] = read_bytes(r)?;
@@ -201,12 +209,12 @@ fn read_header<R: Read>(r: &mut R, warn: impl Fn(Warning)) -> Result<FileHeader,
         (nominal_case_size <= i32::MAX as u32 / 16).then_some(nominal_case_size);
 
     let compression_code: u32 = endianness.parse(read_bytes(r)?);
-    let compression = match (is_zsav, compression_code) {
-        (false, 0) => None,
-        (false, 1) => Some(Compression::Simple),
-        (true, 2) => Some(Compression::ZLib),
-        (false, code) => return Err(Error::InvalidSavCompression(code)),
-        (true, code) => return Err(Error::InvalidZsavCompression(code)),
+    let compression = match (magic, compression_code) {
+        (Magic::ZSAV, 2) => Some(Compression::ZLib),
+        (Magic::ZSAV, code) => return Err(Error::InvalidZsavCompression(code)),
+        (_, 0) => None,
+        (_, 1) => Some(Compression::Simple),
+        (_, code) => return Err(Error::InvalidSavCompression(code)),
     };
 
     let weight_index: u32 = endianness.parse(read_bytes(r)?);
@@ -227,8 +235,6 @@ fn read_header<R: Read>(r: &mut R, warn: impl Fn(Warning)) -> Result<FileHeader,
 
     Ok(FileHeader {
         magic,
-        is_zsav,
-        is_ebcdic,
         endianness,
         weight_index,
         nominal_case_size,