Skip to main content

grib_core/
binary.rs

1//! Shared binary encoding primitives used by GRIB section parsers and writers.
2
3use crate::error::{Error, Result};
4
5pub const U24_MAX: u32 = 0x00ff_ffff;
6
7const WMO_I8_MAX: i64 = 0x7f;
8const WMO_I16_MAX: i64 = 0x7fff;
9const WMO_I24_MAX: i64 = 0x7f_ffff;
10const WMO_I32_MAX: i64 = 0x7fff_ffff;
11const IBM_FRACTION_SCALE: f64 = 16_777_216.0;
12
13pub fn write_u8_be(out: &mut Vec<u8>, value: u8) -> Result<()> {
14    reserve_bytes(out, 1)?;
15    out.push(value);
16    Ok(())
17}
18
19pub fn write_u16_be(out: &mut Vec<u8>, value: u16) -> Result<()> {
20    reserve_bytes(out, 2)?;
21    out.extend_from_slice(&value.to_be_bytes());
22    Ok(())
23}
24
25pub fn write_u24_be(out: &mut Vec<u8>, value: u32) -> Result<()> {
26    if value > U24_MAX {
27        return Err(Error::Other(format!(
28            "value {value} does not fit in an unsigned 24-bit integer"
29        )));
30    }
31
32    reserve_bytes(out, 3)?;
33    out.extend_from_slice(&[
34        ((value >> 16) & 0xff) as u8,
35        ((value >> 8) & 0xff) as u8,
36        (value & 0xff) as u8,
37    ]);
38    Ok(())
39}
40
41pub fn write_u32_be(out: &mut Vec<u8>, value: u32) -> Result<()> {
42    reserve_bytes(out, 4)?;
43    out.extend_from_slice(&value.to_be_bytes());
44    Ok(())
45}
46
47pub fn write_u64_be(out: &mut Vec<u8>, value: u64) -> Result<()> {
48    reserve_bytes(out, 8)?;
49    out.extend_from_slice(&value.to_be_bytes());
50    Ok(())
51}
52
53pub fn read_u24_be(bytes: &[u8]) -> Option<u32> {
54    let bytes = bytes.get(..3)?;
55    Some((u32::from(bytes[0]) << 16) | (u32::from(bytes[1]) << 8) | u32::from(bytes[2]))
56}
57
58pub fn decode_wmo_i8(byte: u8) -> i16 {
59    let magnitude = i16::from(byte & 0x7f);
60    if byte & 0x80 == 0 {
61        magnitude
62    } else {
63        -magnitude
64    }
65}
66
67pub fn decode_wmo_i16(bytes: &[u8]) -> Option<i16> {
68    let raw = u16::from_be_bytes(bytes.get(..2)?.try_into().ok()?);
69    let magnitude = (raw & 0x7fff) as i16;
70    Some(if raw & 0x8000 == 0 {
71        magnitude
72    } else {
73        -magnitude
74    })
75}
76
77pub fn decode_wmo_i24(bytes: &[u8]) -> Option<i32> {
78    let raw = read_u24_be(bytes)?;
79    let magnitude = (raw & 0x7f_ffff) as i32;
80    Some(if raw & 0x80_0000 == 0 {
81        magnitude
82    } else {
83        -magnitude
84    })
85}
86
87pub fn decode_wmo_i32(bytes: &[u8]) -> Option<i32> {
88    let raw = u32::from_be_bytes(bytes.get(..4)?.try_into().ok()?);
89    let magnitude = (raw & 0x7fff_ffff) as i32;
90    Some(if raw & 0x8000_0000 == 0 {
91        magnitude
92    } else {
93        -magnitude
94    })
95}
96
97pub fn encode_wmo_i8(value: i16) -> Option<u8> {
98    let magnitude = checked_magnitude(value, WMO_I8_MAX)?;
99    Some(if value < 0 {
100        0x80 | magnitude as u8
101    } else {
102        magnitude as u8
103    })
104}
105
106pub fn encode_wmo_i16(value: i16) -> Option<[u8; 2]> {
107    let magnitude = checked_magnitude(value, WMO_I16_MAX)? as u16;
108    Some(if value < 0 {
109        (0x8000 | magnitude).to_be_bytes()
110    } else {
111        magnitude.to_be_bytes()
112    })
113}
114
115pub fn encode_wmo_i24(value: i32) -> Option<[u8; 3]> {
116    let magnitude = checked_magnitude(value, WMO_I24_MAX)? as u32;
117    let raw = if value < 0 {
118        0x80_0000 | magnitude
119    } else {
120        magnitude
121    };
122    Some([
123        ((raw >> 16) & 0xff) as u8,
124        ((raw >> 8) & 0xff) as u8,
125        (raw & 0xff) as u8,
126    ])
127}
128
129pub fn encode_wmo_i32(value: i32) -> Option<[u8; 4]> {
130    let magnitude = checked_magnitude(value, WMO_I32_MAX)? as u32;
131    Some(if value < 0 {
132        (0x8000_0000 | magnitude).to_be_bytes()
133    } else {
134        magnitude.to_be_bytes()
135    })
136}
137
138pub fn decode_ibm_f32(bytes: [u8; 4]) -> f32 {
139    if bytes == [0, 0, 0, 0] {
140        return 0.0;
141    }
142
143    let sign = if bytes[0] & 0x80 == 0 { 1.0 } else { -1.0 };
144    let exponent = i32::from(bytes[0] & 0x7f) - 64;
145    let mantissa = (u32::from(bytes[1]) << 16) | (u32::from(bytes[2]) << 8) | u32::from(bytes[3]);
146    let value = sign * f64::from(mantissa) / IBM_FRACTION_SCALE * 16f64.powi(exponent);
147    value as f32
148}
149
150pub fn decode_ibm_f32_slice(bytes: &[u8]) -> Option<f32> {
151    Some(decode_ibm_f32(bytes.get(..4)?.try_into().ok()?))
152}
153
154pub fn encode_ibm_f32(value: f32) -> Option<[u8; 4]> {
155    if value == 0.0 {
156        return Some([0, 0, 0, 0]);
157    }
158    if !value.is_finite() {
159        return None;
160    }
161
162    let sign = if value.is_sign_negative() { 0x80 } else { 0x00 };
163    let mut fraction = f64::from(value.abs());
164    let mut exponent = 64i32;
165
166    while fraction < 0.0625 {
167        fraction *= 16.0;
168        exponent -= 1;
169        if exponent < 0 {
170            return None;
171        }
172    }
173
174    while fraction >= 1.0 {
175        fraction /= 16.0;
176        exponent += 1;
177        if exponent > 0x7f {
178            return None;
179        }
180    }
181
182    let mut mantissa = (fraction * IBM_FRACTION_SCALE).round() as u32;
183    if mantissa == 0 {
184        return Some([0, 0, 0, 0]);
185    }
186    if mantissa >= 0x0100_0000 {
187        mantissa >>= 4;
188        exponent += 1;
189        if exponent > 0x7f {
190            return None;
191        }
192    }
193
194    Some([
195        sign | exponent as u8,
196        ((mantissa >> 16) & 0xff) as u8,
197        ((mantissa >> 8) & 0xff) as u8,
198        (mantissa & 0xff) as u8,
199    ])
200}
201
202fn reserve_bytes(out: &mut Vec<u8>, additional: usize) -> Result<()> {
203    out.try_reserve(additional)
204        .map_err(|e| Error::Other(format!("failed to reserve {additional} bytes: {e}")))
205}
206
207fn checked_magnitude<T>(value: T, max: i64) -> Option<i64>
208where
209    T: Into<i64>,
210{
211    let value = value.into();
212    let magnitude = value.checked_abs()?;
213    (magnitude <= max).then_some(magnitude)
214}
215
216#[cfg(test)]
217mod tests {
218    use super::{
219        decode_ibm_f32, decode_ibm_f32_slice, decode_wmo_i16, decode_wmo_i24, decode_wmo_i32,
220        decode_wmo_i8, encode_ibm_f32, encode_wmo_i16, encode_wmo_i24, encode_wmo_i32,
221        encode_wmo_i8, read_u24_be, write_u16_be, write_u24_be, write_u32_be, write_u64_be,
222        write_u8_be,
223    };
224
225    #[test]
226    fn writes_big_endian_unsigned_integers() {
227        let mut out = Vec::new();
228        write_u8_be(&mut out, 0x12).unwrap();
229        write_u16_be(&mut out, 0x3456).unwrap();
230        write_u24_be(&mut out, 0x789abc).unwrap();
231        write_u32_be(&mut out, 0xdef0_1234).unwrap();
232        write_u64_be(&mut out, 0x5678_9abc_def0_1234).unwrap();
233
234        assert_eq!(
235            out,
236            [
237                0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
238                0xde, 0xf0, 0x12, 0x34
239            ]
240        );
241    }
242
243    #[test]
244    fn rejects_unsigned_24_bit_overflow() {
245        let mut out = Vec::new();
246        assert!(write_u24_be(&mut out, 0x0100_0000).is_err());
247        assert!(out.is_empty());
248    }
249
250    #[test]
251    fn reads_unsigned_24_bit_integers() {
252        assert_eq!(read_u24_be(&[0x12, 0x34, 0x56]), Some(0x12_3456));
253        assert_eq!(read_u24_be(&[0x12, 0x34]), None);
254    }
255
256    #[test]
257    fn roundtrips_wmo_signed_integers() {
258        assert_eq!(decode_wmo_i8(encode_wmo_i8(-5).unwrap()), -5);
259        assert_eq!(decode_wmo_i16(&encode_wmo_i16(-500).unwrap()), Some(-500));
260        assert_eq!(
261            decode_wmo_i24(&encode_wmo_i24(-500_000).unwrap()),
262            Some(-500_000)
263        );
264        assert_eq!(
265            decode_wmo_i32(&encode_wmo_i32(-500_000_000).unwrap()),
266            Some(-500_000_000)
267        );
268    }
269
270    #[test]
271    fn matches_known_wmo_signed_byte_patterns() {
272        assert_eq!(decode_wmo_i8(0x85), -5);
273        assert_eq!(decode_wmo_i16(&[0x80, 0x05]), Some(-5));
274        assert_eq!(decode_wmo_i24(&[0x80, 0x00, 0x05]), Some(-5));
275        assert_eq!(decode_wmo_i32(&[0x80, 0x00, 0x00, 0x05]), Some(-5));
276        assert_eq!(encode_wmo_i8(5), Some(0x05));
277        assert_eq!(encode_wmo_i16(5), Some([0x00, 0x05]));
278        assert_eq!(encode_wmo_i24(5), Some([0x00, 0x00, 0x05]));
279        assert_eq!(encode_wmo_i32(5), Some([0x00, 0x00, 0x00, 0x05]));
280    }
281
282    #[test]
283    fn rejects_wmo_signed_integer_overflow() {
284        assert_eq!(encode_wmo_i8(128), None);
285        assert_eq!(encode_wmo_i16(i16::MIN), None);
286        assert_eq!(encode_wmo_i24(0x80_0000), None);
287        assert_eq!(encode_wmo_i32(i32::MIN), None);
288        assert_eq!(decode_wmo_i16(&[0x00]), None);
289        assert_eq!(decode_wmo_i24(&[0x00, 0x00]), None);
290        assert_eq!(decode_wmo_i32(&[0x00, 0x00, 0x00]), None);
291    }
292
293    #[test]
294    fn decodes_known_ibm_float_patterns() {
295        assert_eq!(decode_ibm_f32([0x00, 0x00, 0x00, 0x00]), 0.0);
296        assert_eq!(decode_ibm_f32([0x41, 0x10, 0x00, 0x00]), 1.0);
297        assert_eq!(decode_ibm_f32([0xc1, 0x20, 0x00, 0x00]), -2.0);
298        assert_eq!(decode_ibm_f32([0x41, 0xa0, 0x00, 0x00]), 10.0);
299        assert_eq!(decode_ibm_f32_slice(&[0x41, 0x10, 0x00, 0x00]), Some(1.0));
300        assert_eq!(decode_ibm_f32_slice(&[0x41, 0x10, 0x00]), None);
301    }
302
303    #[test]
304    fn roundtrips_finite_ibm_float_values() {
305        for value in [0.0_f32, 0.25, 0.5, 1.0, -2.0, 10.0, 16.0, 1234.5] {
306            let encoded = encode_ibm_f32(value).unwrap();
307            let decoded = decode_ibm_f32(encoded);
308            let tolerance = value.abs().max(1.0) * 1.0e-6;
309            assert!(
310                (decoded - value).abs() <= tolerance,
311                "value {value} encoded as {encoded:02x?} decoded as {decoded}"
312            );
313        }
314    }
315
316    #[test]
317    fn encodes_known_ibm_float_patterns() {
318        assert_eq!(encode_ibm_f32(0.0), Some([0x00, 0x00, 0x00, 0x00]));
319        assert_eq!(encode_ibm_f32(1.0), Some([0x41, 0x10, 0x00, 0x00]));
320        assert_eq!(encode_ibm_f32(-2.0), Some([0xc1, 0x20, 0x00, 0x00]));
321        assert_eq!(encode_ibm_f32(10.0), Some([0x41, 0xa0, 0x00, 0x00]));
322    }
323
324    #[test]
325    fn rejects_non_finite_ibm_float_values() {
326        assert_eq!(encode_ibm_f32(f32::NAN), None);
327        assert_eq!(encode_ibm_f32(f32::INFINITY), None);
328        assert_eq!(encode_ibm_f32(f32::NEG_INFINITY), None);
329    }
330}