Skip to main content

zerodds_hpack/
integer.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Variable-Length-Integer-Coding — RFC 7541 §5.1.
5//!
6//! Encoded auf einer N-Bit-Praefix-Position mit Continuation-Marker
7//! im MSB der Folge-Bytes.
8
9use alloc::vec::Vec;
10
11/// Integer-Coding-Fehler.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum IntegerError {
14    /// Eingabe-Buffer zu kurz.
15    Truncated,
16    /// Ueber 7 Continuation-Bytes — RFC 7541 erlaubt das nicht
17    /// (Spec §5.1, Implementations MAY enforce a limit).
18    TooLarge,
19}
20
21impl core::fmt::Display for IntegerError {
22    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23        match self {
24            Self::Truncated => f.write_str("integer truncated"),
25            Self::TooLarge => f.write_str("integer too large"),
26        }
27    }
28}
29
30#[cfg(feature = "std")]
31impl std::error::Error for IntegerError {}
32
33/// Encode `value` mit `prefix_bits` Bits Praefix-Slot. Spec §5.1.
34///
35/// `prefix_bits` muss in 1..=8 liegen. Das erste Output-Byte enthaelt
36/// die `(8 - prefix_bits)` Most-Significant-Bits aus
37/// `out_byte_prefix_bits` (Caller liefert die in Bits 7..prefix_bits).
38#[must_use]
39pub fn encode_integer(value: u64, prefix_bits: u8, out_byte_prefix_bits: u8) -> Vec<u8> {
40    let mask = (1u64 << prefix_bits) - 1;
41    let mut out = Vec::new();
42    if value < mask {
43        out.push(out_byte_prefix_bits | (value as u8));
44        return out;
45    }
46    out.push(out_byte_prefix_bits | mask as u8);
47    let mut v = value - mask;
48    while v >= 128 {
49        out.push((v & 0x7f) as u8 | 0x80);
50        v >>= 7;
51    }
52    out.push(v as u8);
53    out
54}
55
56/// Decode einen Integer aus einem Byte-Slice. Spec §5.1.
57///
58/// Liefert (decoded value, bytes consumed).
59///
60/// # Errors
61/// `Truncated` / `TooLarge`.
62pub fn decode_integer(input: &[u8], prefix_bits: u8) -> Result<(u64, usize), IntegerError> {
63    if input.is_empty() {
64        return Err(IntegerError::Truncated);
65    }
66    let mask = (1u64 << prefix_bits) - 1;
67    let first = u64::from(input[0]) & mask;
68    if first < mask {
69        return Ok((first, 1));
70    }
71    let mut value = mask;
72    let mut shift: u32 = 0;
73    let mut idx = 1;
74    while idx < input.len() {
75        let b = input[idx];
76        idx += 1;
77        if shift >= 56 {
78            return Err(IntegerError::TooLarge);
79        }
80        value = value
81            .checked_add(u64::from(b & 0x7f) << shift)
82            .ok_or(IntegerError::TooLarge)?;
83        shift += 7;
84        if (b & 0x80) == 0 {
85            return Ok((value, idx));
86        }
87    }
88    Err(IntegerError::Truncated)
89}
90
91#[cfg(test)]
92#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn rfc7541_c1_1_encoded_10_in_5bit_prefix() {
98        // Spec §C.1.1 — value 10 in 5-bit prefix → 0x0a (one byte)
99        let buf = encode_integer(10, 5, 0);
100        assert_eq!(buf, alloc::vec![0x0a]);
101        assert_eq!(decode_integer(&buf, 5).unwrap(), (10, 1));
102    }
103
104    #[test]
105    fn rfc7541_c1_2_encoded_1337_in_5bit_prefix() {
106        // Spec §C.1.2 — value 1337 in 5-bit prefix → 0x1f, 0x9a, 0x0a
107        let buf = encode_integer(1337, 5, 0);
108        assert_eq!(buf, alloc::vec![0x1f, 0x9a, 0x0a]);
109        assert_eq!(decode_integer(&buf, 5).unwrap(), (1337, 3));
110    }
111
112    #[test]
113    fn rfc7541_c1_3_encoded_42_in_8bit_prefix() {
114        // Spec §C.1.3 — value 42 in 8-bit prefix → 0x2a (one byte)
115        let buf = encode_integer(42, 8, 0);
116        assert_eq!(buf, alloc::vec![0x2a]);
117        assert_eq!(decode_integer(&buf, 8).unwrap(), (42, 1));
118    }
119
120    #[test]
121    fn round_trip_small_values() {
122        for v in 0..256u64 {
123            let buf = encode_integer(v, 7, 0);
124            let (decoded, _) = decode_integer(&buf, 7).unwrap();
125            assert_eq!(decoded, v);
126        }
127    }
128
129    #[test]
130    fn round_trip_large_values() {
131        for v in [u64::from(u32::MAX), 1_000_000, 1u64 << 32] {
132            let buf = encode_integer(v, 5, 0);
133            let (decoded, _) = decode_integer(&buf, 5).unwrap();
134            assert_eq!(decoded, v);
135        }
136    }
137
138    #[test]
139    fn truncated_continuation_rejected() {
140        // 5-bit prefix all-ones triggers continuation, but no 2nd byte.
141        let buf = alloc::vec![0x1f];
142        assert_eq!(decode_integer(&buf, 5), Err(IntegerError::Truncated));
143    }
144
145    #[test]
146    fn empty_input_truncated() {
147        assert_eq!(decode_integer(&[], 5), Err(IntegerError::Truncated));
148    }
149
150    #[test]
151    fn prefix_bits_left_other_bits_alone() {
152        let buf = encode_integer(5, 5, 0xc0); // top 3 bits set
153        assert_eq!(buf[0] & 0xe0, 0xc0);
154        assert_eq!(buf[0] & 0x1f, 5);
155    }
156}