Skip to main content

moqtap_codec/
varint.rs

1use bytes::{Buf, BufMut};
2
3/// Maximum varint value: 2^62 - 1 (RFC 9000 Section 16)
4pub const MAX_VARINT: u64 = 4_611_686_018_427_387_903;
5
6/// A QUIC variable-length integer (RFC 9000 Section 16).
7///
8/// Uses 2-bit prefix encoding:
9/// - 00: 1 byte, values 0-63
10/// - 01: 2 bytes, values 0-16383
11/// - 10: 4 bytes, values 0-1073741823
12/// - 11: 8 bytes, values 0-4611686018427387903
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct VarInt(u64);
15
16/// Errors produced when encoding or decoding a variable-length integer.
17#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
18pub enum VarIntError {
19    /// Value exceeds the maximum varint value (2^62 - 1).
20    #[error("value {0} exceeds maximum varint value (2^62 - 1)")]
21    Overflow(u64),
22    /// Not enough bytes in the buffer to decode a varint.
23    #[error("insufficient bytes for varint decoding")]
24    UnexpectedEnd,
25    /// A 7-byte encoding was received on a draft-17 session, where the length
26    /// is undefined. Draft-17 Section 1.4.1 requires closing the session with
27    /// PROTOCOL_VIOLATION.
28    #[error("7-byte varint is not a defined encoding length in draft-17")]
29    InvalidCodePoint,
30}
31
32impl VarInt {
33    /// Create a VarInt from a u64, returning an error if it exceeds the maximum.
34    #[inline]
35    pub fn from_u64(v: u64) -> Result<Self, VarIntError> {
36        if v > MAX_VARINT {
37            Err(VarIntError::Overflow(v))
38        } else {
39            Ok(VarInt(v))
40        }
41    }
42
43    /// Get the inner u64 value.
44    #[inline]
45    pub fn into_inner(self) -> u64 {
46        self.0
47    }
48
49    /// Return the number of bytes needed to encode this varint.
50    #[inline]
51    pub fn encoded_len(&self) -> usize {
52        if self.0 <= 63 {
53            1
54        } else if self.0 <= 16383 {
55            2
56        } else if self.0 <= 1073741823 {
57            4
58        } else {
59            8
60        }
61    }
62
63    /// Encode this varint into the given buffer.
64    #[inline]
65    pub fn encode(&self, buf: &mut impl BufMut) {
66        match self.encoded_len() {
67            1 => {
68                buf.put_u8(self.0 as u8);
69            }
70            2 => {
71                buf.put_u16((self.0 as u16) | 0x4000);
72            }
73            4 => {
74                buf.put_u32((self.0 as u32) | 0x80000000);
75            }
76            8 => {
77                buf.put_u64(self.0 | 0xC000000000000000);
78            }
79            _ => unreachable!(),
80        }
81    }
82
83    /// Decode a varint from the given buffer.
84    #[inline]
85    pub fn decode(buf: &mut impl Buf) -> Result<Self, VarIntError> {
86        if buf.remaining() < 1 {
87            return Err(VarIntError::UnexpectedEnd);
88        }
89        let first = buf.chunk()[0];
90        let prefix = first >> 6;
91        let len = 1usize << prefix;
92        if buf.remaining() < len {
93            return Err(VarIntError::UnexpectedEnd);
94        }
95        let val = match len {
96            1 => {
97                buf.advance(1);
98                (first & 0x3F) as u64
99            }
100            2 => {
101                let v = buf.get_u16();
102                (v & 0x3FFF) as u64
103            }
104            4 => {
105                let v = buf.get_u32();
106                (v & 0x3FFFFFFF) as u64
107            }
108            8 => {
109                let v = buf.get_u64();
110                v & 0x3FFFFFFFFFFFFFFF
111            }
112            _ => unreachable!(),
113        };
114        Ok(VarInt(val))
115    }
116}
117
118/// Maximum MoQT varint value (draft-17 Section 1.4.1): 2^64 - 1.
119pub const MAX_MOQT_VARINT: u64 = u64::MAX;
120
121impl VarInt {
122    /// Create a VarInt from a u64 under the MoQT encoding, which reaches the
123    /// full 64-bit range and so cannot fail.
124    #[inline]
125    pub fn from_u64_moqt(v: u64) -> Self {
126        VarInt(v)
127    }
128
129    /// Number of bytes needed to encode this varint under the MoQT encoding.
130    ///
131    /// `seven_byte` is false for draft-17, which omits that length, so values
132    /// that take seven bytes elsewhere take eight there.
133    #[inline]
134    fn encoded_len_moqt(&self, seven_byte: bool) -> usize {
135        for len in 1..=8 {
136            if (len != 7 || seven_byte) && self.0 < 1u64 << (7 * len) {
137                return len;
138            }
139        }
140        9
141    }
142
143    #[inline]
144    fn encode_moqt_inner(&self, buf: &mut impl BufMut, seven_byte: bool) {
145        let len = self.encoded_len_moqt(seven_byte);
146        if len == 9 {
147            buf.put_u8(0xFF);
148            buf.put_u64(self.0);
149            return;
150        }
151        // (len - 1) leading 1 bits, then a 0, in the top `len` bits.
152        let prefix = (((1u16 << (len - 1)) - 1) << (9 - len)) as u8;
153        let combined = ((prefix as u64) << (8 * (len - 1))) | self.0;
154        for i in (0..len).rev() {
155            buf.put_u8((combined >> (8 * i)) as u8);
156        }
157    }
158
159    #[inline]
160    fn decode_moqt_inner(buf: &mut impl Buf, seven_byte: bool) -> Result<Self, VarIntError> {
161        if buf.remaining() < 1 {
162            return Err(VarIntError::UnexpectedEnd);
163        }
164        let first = buf.chunk()[0];
165
166        if first == 0xFF {
167            if buf.remaining() < 9 {
168                return Err(VarIntError::UnexpectedEnd);
169            }
170            buf.advance(1);
171            return Ok(VarInt(buf.get_u64()));
172        }
173
174        let len = first.leading_ones() as usize + 1;
175        if len == 7 && !seven_byte {
176            return Err(VarIntError::InvalidCodePoint);
177        }
178        if buf.remaining() < len {
179            return Err(VarIntError::UnexpectedEnd);
180        }
181        let mut val = (first & ((1u16 << (8 - len)) - 1) as u8) as u64;
182        buf.advance(1);
183        for _ in 1..len {
184            val = (val << 8) | buf.get_u8() as u64;
185        }
186        Ok(VarInt(val))
187    }
188
189    /// Encode using the MoQT variable-length integer (drafts 17 and later).
190    ///
191    /// Draft-17 replaced the RFC 9000 encoding with one whose length comes from
192    /// the number of leading 1 bits in the first byte: one byte carries 0-127,
193    /// and nine bytes carry the full 64-bit range. The shortest form that holds
194    /// the value is always used.
195    #[inline]
196    pub fn encode_moqt<P: MoqtProfile>(&self, buf: &mut impl BufMut) {
197        self.encode_moqt_inner(buf, P::SEVEN_BYTE);
198    }
199
200    /// Decode a MoQT variable-length integer (drafts 17 and later).
201    ///
202    /// Non-minimal encodings are accepted, as draft-19 Section 1.4.1 requires:
203    /// 0 may arrive as 0x00, 0x8000, 0xC00000 or any longer form. On a
204    /// [`Moqt17`] session a 7-byte encoding is [`VarIntError::InvalidCodePoint`].
205    #[inline]
206    pub fn decode_moqt<P: MoqtProfile>(buf: &mut impl Buf) -> Result<Self, VarIntError> {
207        Self::decode_moqt_inner(buf, P::SEVEN_BYTE)
208    }
209}
210
211mod sealed {
212    pub trait Sealed {}
213    impl Sealed for super::Moqt17 {}
214    impl Sealed for super::Moqt18 {}
215}
216
217/// One revision of MoQT's variable-length integer.
218///
219/// Named for the draft that introduced the revision, not the range of drafts
220/// using it — [`crate::version::DraftVersion::varint_encoding`] is the one
221/// place that says which draft uses which.
222pub trait MoqtProfile: sealed::Sealed {
223    /// Whether the 7-byte length is defined.
224    const SEVEN_BYTE: bool;
225}
226
227/// The encoding as introduced in draft-17, whose Table 1 omits the 7-byte
228/// length and which calls 11111100 an invalid code point. Values needing seven
229/// bytes under [`Moqt18`] take eight here.
230pub struct Moqt17;
231
232/// The encoding as revised in draft-18, which restored the 7-byte length so
233/// that all nine are defined.
234pub struct Moqt18;
235
236impl MoqtProfile for Moqt17 {
237    const SEVEN_BYTE: bool = false;
238}
239
240impl MoqtProfile for Moqt18 {
241    const SEVEN_BYTE: bool = true;
242}
243
244impl TryFrom<u64> for VarInt {
245    type Error = VarIntError;
246    #[inline]
247    fn try_from(v: u64) -> Result<Self, Self::Error> {
248        Self::from_u64(v)
249    }
250}
251
252impl From<VarInt> for u64 {
253    #[inline]
254    fn from(v: VarInt) -> u64 {
255        v.0
256    }
257}
258
259impl VarInt {
260    /// Create a VarInt from a usize. Infallible because practical memory sizes
261    /// are always well below the 2^62 varint maximum.
262    #[inline]
263    pub fn from_usize(v: usize) -> Self {
264        VarInt(v as u64)
265    }
266}
267
268impl From<u32> for VarInt {
269    #[inline]
270    fn from(v: u32) -> Self {
271        VarInt(v as u64)
272    }
273}