Skip to main content

fpzip_rs/
header.rs

1use crate::codec::range_decoder::RangeDecoder;
2use crate::codec::range_encoder::RangeEncoder;
3use crate::error::{FpZipError, Result};
4
5/// C++ fpzip major version.
6pub const FPZ_MAJ_VERSION: u32 = 0x0110;
7
8/// C++ fpzip minor version (FPZIP_FP_INT = 4).
9pub const FPZ_MIN_VERSION: u32 = 4;
10
11/// Type of floating-point data stored in the compressed stream.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u8)]
14pub enum FpZipType {
15    /// 32-bit IEEE 754 single-precision float.
16    Float = 0,
17    /// 64-bit IEEE 754 double-precision float.
18    Double = 1,
19}
20
21/// Metadata header for FpZip compressed data.
22///
23/// Contains the data type, bit precision, and array dimensions.
24/// The header is encoded through the arithmetic range coder as part of the
25/// compressed stream (not as raw bytes), matching the C++ fpzip wire format.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct FpZipHeader {
28    /// The floating-point type (float or double).
29    pub data_type: FpZipType,
30    /// Bit precision. Full precision for the type means lossless (32 for float, 64 for double).
31    pub prec: u32,
32    /// X dimension size.
33    pub nx: u32,
34    /// Y dimension size.
35    pub ny: u32,
36    /// Z dimension size.
37    pub nz: u32,
38    /// Number of fields (4th dimension).
39    pub nf: u32,
40}
41
42impl FpZipHeader {
43    pub fn new(data_type: FpZipType, nx: u32, ny: u32, nz: u32, nf: u32) -> Self {
44        let prec = match data_type {
45            FpZipType::Float => 32,
46            FpZipType::Double => 64,
47        };
48        Self {
49            data_type,
50            prec,
51            nx,
52            ny,
53            nz,
54            nf,
55        }
56    }
57
58    /// Total number of elements.
59    pub fn total_elements(&self) -> u64 {
60        self.nx as u64 * self.ny as u64 * self.nz as u64 * self.nf as u64
61    }
62
63    /// Writes the header through the range encoder (C++ compatible format).
64    pub fn write_to_encoder(&self, enc: &mut RangeEncoder) {
65        // magic: 'f', 'p', 'z', '\0'
66        enc.encode_uint(b'f' as u32, 8);
67        enc.encode_uint(b'p' as u32, 8);
68        enc.encode_uint(b'z' as u32, 8);
69        enc.encode_uint(0, 8);
70
71        // format version
72        enc.encode_uint(FPZ_MAJ_VERSION, 16);
73        enc.encode_uint(FPZ_MIN_VERSION, 8);
74
75        // type (1 bit) and precision (7 bits)
76        enc.encode_uint(self.data_type as u32, 1);
77        enc.encode_uint(self.prec, 7);
78
79        // array dimensions
80        enc.encode_uint(self.nx, 32);
81        enc.encode_uint(self.ny, 32);
82        enc.encode_uint(self.nz, 32);
83        enc.encode_uint(self.nf, 32);
84    }
85
86    /// Reads the header through the range decoder (C++ compatible format).
87    pub fn read_from_decoder(dec: &mut RangeDecoder) -> Result<Self> {
88        // magic
89        let f = dec.decode_uint(8);
90        let p = dec.decode_uint(8);
91        let z = dec.decode_uint(8);
92        let nul = dec.decode_uint(8);
93        if f != b'f' as u32 || p != b'p' as u32 || z != b'z' as u32 || nul != 0 {
94            let magic = f | (p << 8) | (z << 16) | (nul << 24);
95            return Err(FpZipError::InvalidMagic(magic));
96        }
97
98        // format version
99        let maj = dec.decode_uint(16);
100        let min = dec.decode_uint(8);
101        if maj != FPZ_MAJ_VERSION {
102            return Err(FpZipError::UnsupportedVersion(maj as u16));
103        }
104        if min != FPZ_MIN_VERSION {
105            return Err(FpZipError::UnsupportedVersion(min as u16));
106        }
107
108        // type (1 bit) and precision (7 bits)
109        let type_bit = dec.decode_uint(1);
110        let prec = dec.decode_uint(7);
111
112        let data_type = match type_bit {
113            0 => FpZipType::Float,
114            1 => FpZipType::Double,
115            _ => return Err(FpZipError::InvalidDataType(type_bit as u8)),
116        };
117
118        // array dimensions
119        let nx = dec.decode_uint(32);
120        let ny = dec.decode_uint(32);
121        let nz = dec.decode_uint(32);
122        let nf = dec.decode_uint(32);
123
124        Ok(Self {
125            data_type,
126            prec,
127            nx,
128            ny,
129            nz,
130            nf,
131        })
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn round_trip_header_via_encoder() {
141        let h = FpZipHeader::new(FpZipType::Float, 10, 20, 30, 2);
142        let mut enc = RangeEncoder::new();
143        h.write_to_encoder(&mut enc);
144        let data = enc.finish();
145
146        let mut dec = RangeDecoder::new(&data);
147        dec.init();
148        let h2 = FpZipHeader::read_from_decoder(&mut dec).unwrap();
149        assert_eq!(h.data_type, h2.data_type);
150        assert_eq!(h.nx, h2.nx);
151        assert_eq!(h.ny, h2.ny);
152        assert_eq!(h.nz, h2.nz);
153        assert_eq!(h.nf, h2.nf);
154    }
155
156    #[test]
157    fn total_elements() {
158        let h = FpZipHeader::new(FpZipType::Double, 10, 20, 30, 2);
159        assert_eq!(h.total_elements(), 12000);
160    }
161
162    #[test]
163    fn invalid_magic_returns_error() {
164        // Encode garbage instead of 'fpz\0'
165        let mut enc = RangeEncoder::new();
166        enc.encode_uint(b'X' as u32, 8);
167        enc.encode_uint(b'Y' as u32, 8);
168        enc.encode_uint(b'Z' as u32, 8);
169        enc.encode_uint(0, 8);
170        // fill rest so decoder doesn't EOF
171        enc.encode_uint(FPZ_MAJ_VERSION, 16);
172        enc.encode_uint(FPZ_MIN_VERSION, 8);
173        enc.encode_uint(0, 1);
174        enc.encode_uint(32, 7);
175        enc.encode_uint(1, 32);
176        enc.encode_uint(1, 32);
177        enc.encode_uint(1, 32);
178        enc.encode_uint(1, 32);
179        let data = enc.finish();
180
181        let mut dec = RangeDecoder::new(&data);
182        dec.init();
183        assert!(matches!(
184            FpZipHeader::read_from_decoder(&mut dec),
185            Err(FpZipError::InvalidMagic(_))
186        ));
187    }
188
189    #[test]
190    fn round_trip_double_header() {
191        let h = FpZipHeader::new(FpZipType::Double, 65, 64, 63, 3);
192        let mut enc = RangeEncoder::new();
193        h.write_to_encoder(&mut enc);
194        let data = enc.finish();
195
196        let mut dec = RangeDecoder::new(&data);
197        dec.init();
198        let h2 = FpZipHeader::read_from_decoder(&mut dec).unwrap();
199        assert_eq!(h, h2);
200    }
201}