1use 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}