umsh_ulcp/
sint.rs

1//! Minimal-length signed integers.
2//!
3//! Two's complement, little-endian, in the fewest octets that hold the
4//! value: one octet up to ±128, two up to ±32768, and so on to four. An
5//! altitude in meters is the motivating case — most of the world is
6//! within a byte of sea level, and the property that carries it is read
7//! over LoRa.
8//!
9//! Decoding accepts any width from one to four octets, so a sender that
10//! pads to a fixed width is understood; encoding always produces the
11//! minimal form.
12
13/// Largest encoded size of a minimal signed integer.
14pub const MAX_LEN: usize = 4;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum Error {
18    /// The output buffer cannot hold the encoded value.
19    BufferTooSmall,
20    /// The input was empty or longer than [`MAX_LEN`].
21    Malformed,
22}
23
24/// Return the encoded size of `value` in bytes.
25pub const fn encoded_len(value: i32) -> usize {
26    if value >= i8::MIN as i32 && value <= i8::MAX as i32 {
27        1
28    } else if value >= i16::MIN as i32 && value <= i16::MAX as i32 {
29        2
30    } else if value >= -(1 << 23) && value < (1 << 23) {
31        3
32    } else {
33        4
34    }
35}
36
37/// Encode `value` into `out`, returning the number of bytes written.
38pub fn encode(value: i32, out: &mut [u8]) -> Result<usize, Error> {
39    let len = encoded_len(value);
40    let dst = out.get_mut(..len).ok_or(Error::BufferTooSmall)?;
41    dst.copy_from_slice(&value.to_le_bytes()[..len]);
42    Ok(len)
43}
44
45/// Decode a one- to four-octet two's-complement value.
46///
47/// The high bit of the last octet is the sign, and the value is sign-
48/// extended from whatever width arrived.
49pub fn decode(input: &[u8]) -> Result<i32, Error> {
50    let (&last, rest) = input.split_last().ok_or(Error::Malformed)?;
51    if input.len() > MAX_LEN {
52        return Err(Error::Malformed);
53    }
54    let fill = if last & 0x80 != 0 { 0xFF } else { 0x00 };
55    let mut bytes = [fill; MAX_LEN];
56    bytes[..rest.len()].copy_from_slice(rest);
57    bytes[rest.len()] = last;
58    Ok(i32::from_le_bytes(bytes))
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    fn roundtrip(value: i32, expected_len: usize) {
66        let mut buf = [0u8; MAX_LEN];
67        let len = encode(value, &mut buf).expect("encoding fits");
68        assert_eq!(len, expected_len, "width of {value}");
69        assert_eq!(decode(&buf[..len]), Ok(value));
70    }
71
72    #[test]
73    fn widths_follow_the_value() {
74        roundtrip(0, 1);
75        roundtrip(100, 1);
76        roundtrip(127, 1);
77        roundtrip(-128, 1);
78        roundtrip(128, 2);
79        roundtrip(-129, 2);
80        roundtrip(200, 2);
81        roundtrip(32767, 2);
82        roundtrip(-32768, 2);
83        roundtrip(32768, 3);
84        roundtrip(-32769, 3);
85        roundtrip(8_388_607, 3);
86        roundtrip(-8_388_608, 3);
87        roundtrip(8_388_608, 4);
88        roundtrip(-8_388_609, 4);
89        roundtrip(i32::MAX, 4);
90        roundtrip(i32::MIN, 4);
91    }
92
93    #[test]
94    fn a_padded_encoding_decodes_to_the_same_value() {
95        // A sender is free to pad; what comes back is minimal either way.
96        assert_eq!(decode(&[0x64]), Ok(100));
97        assert_eq!(decode(&[0x64, 0x00]), Ok(100));
98        assert_eq!(decode(&[0x64, 0x00, 0x00, 0x00]), Ok(100));
99        assert_eq!(decode(&[0x9C, 0xFF]), Ok(-100));
100        assert_eq!(decode(&[0x9C, 0xFF, 0xFF, 0xFF]), Ok(-100));
101    }
102
103    #[test]
104    fn lengths_outside_one_through_four_are_malformed() {
105        assert_eq!(decode(&[]), Err(Error::Malformed));
106        assert_eq!(decode(&[0; 5]), Err(Error::Malformed));
107    }
108
109    #[test]
110    fn a_buffer_shorter_than_the_value_is_refused() {
111        let mut buf = [0u8; 1];
112        assert_eq!(encode(1000, &mut buf), Err(Error::BufferTooSmall));
113    }
114}