Skip to main content

moq_net/coding/
varint.rs

1// Based on quinn-proto
2// https://github.com/quinn-rs/quinn/blob/main/quinn-proto/src/varint.rs
3// Licensed via Apache 2.0 and MIT
4
5use std::convert::{TryFrom, TryInto};
6use std::fmt;
7
8use thiserror::Error;
9
10use super::{Decode, DecodeError, Encode, EncodeError};
11
12/// The number is too large to fit in a VarInt (62 bits).
13#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
14#[error("value out of range")]
15pub struct BoundsExceeded;
16
17/// An integer less than 2^62
18///
19/// Values of this type are suitable for encoding as QUIC variable-length integer.
20/// It would be neat if we could express to Rust that the top two bits are available for use as enum
21/// discriminants
22#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
23pub struct VarInt(u64);
24
25impl VarInt {
26	/// The largest possible value.
27	pub const MAX: Self = Self((1 << 62) - 1);
28
29	/// The smallest possible value.
30	pub const ZERO: Self = Self(0);
31
32	/// Construct a `VarInt` infallibly using the largest available type.
33	/// Larger values need to use `try_from` instead.
34	pub const fn from_u32(x: u32) -> Self {
35		Self(x as u64)
36	}
37
38	/// Construct from a `u64`, or `None` if it exceeds [`Self::MAX`].
39	pub const fn from_u64(x: u64) -> Option<Self> {
40		if x <= Self::MAX.0 { Some(Self(x)) } else { None }
41	}
42
43	/// Construct from a `u128`, or `None` if it exceeds [`Self::MAX`].
44	pub const fn from_u128(x: u128) -> Option<Self> {
45		if x <= Self::MAX.0 as u128 {
46			Some(Self(x as u64))
47		} else {
48			None
49		}
50	}
51
52	/// Extract the integer value
53	pub const fn into_inner(self) -> u64 {
54		self.0
55	}
56
57	/// Encode a signed `i64` as a zigzag-then-unsigned varint: `(n << 1) ^ (n >> 63)`.
58	///
59	/// Small negative numbers map to small unsigneds (-1 -> 1, 1 -> 2, -2 -> 3, ...).
60	/// Returns [`BoundsExceeded`] if `signed` is outside `[-2^61, 2^61 - 1]`, since the
61	/// zigzag-encoded result must fit in a 62-bit varint.
62	pub const fn from_zigzag(signed: i64) -> Result<Self, BoundsExceeded> {
63		const RANGE: i64 = 1 << 61;
64		if signed < -RANGE || signed >= RANGE {
65			return Err(BoundsExceeded);
66		}
67		Ok(Self(((signed << 1) ^ (signed >> 63)) as u64))
68	}
69
70	/// Decode this varint as a signed `i64` via the inverse zigzag transform.
71	pub const fn to_zigzag(self) -> i64 {
72		let v = self.0;
73		((v >> 1) as i64) ^ -((v & 1) as i64)
74	}
75}
76
77impl From<VarInt> for u64 {
78	fn from(x: VarInt) -> Self {
79		x.0
80	}
81}
82
83impl From<VarInt> for usize {
84	fn from(x: VarInt) -> Self {
85		x.0 as usize
86	}
87}
88
89impl From<VarInt> for u128 {
90	fn from(x: VarInt) -> Self {
91		x.0 as u128
92	}
93}
94
95impl From<u8> for VarInt {
96	fn from(x: u8) -> Self {
97		Self(x.into())
98	}
99}
100
101impl From<u16> for VarInt {
102	fn from(x: u16) -> Self {
103		Self(x.into())
104	}
105}
106
107impl From<u32> for VarInt {
108	fn from(x: u32) -> Self {
109		Self(x.into())
110	}
111}
112
113impl TryFrom<u64> for VarInt {
114	type Error = BoundsExceeded;
115
116	/// Succeeds iff `x` < 2^62
117	fn try_from(x: u64) -> Result<Self, BoundsExceeded> {
118		let x = Self(x);
119		if x <= Self::MAX { Ok(x) } else { Err(BoundsExceeded) }
120	}
121}
122
123impl TryFrom<u128> for VarInt {
124	type Error = BoundsExceeded;
125
126	/// Succeeds iff `x` < 2^62
127	fn try_from(x: u128) -> Result<Self, BoundsExceeded> {
128		if x <= Self::MAX.into() {
129			Ok(Self(x as u64))
130		} else {
131			Err(BoundsExceeded)
132		}
133	}
134}
135
136impl TryFrom<usize> for VarInt {
137	type Error = BoundsExceeded;
138
139	/// Succeeds iff `x` < 2^62
140	fn try_from(x: usize) -> Result<Self, BoundsExceeded> {
141		Self::try_from(x as u64)
142	}
143}
144
145impl TryFrom<VarInt> for u32 {
146	type Error = BoundsExceeded;
147
148	/// Succeeds iff `x` < 2^32
149	fn try_from(x: VarInt) -> Result<Self, BoundsExceeded> {
150		if x.0 <= u32::MAX.into() {
151			Ok(x.0 as u32)
152		} else {
153			Err(BoundsExceeded)
154		}
155	}
156}
157
158impl TryFrom<VarInt> for u16 {
159	type Error = BoundsExceeded;
160
161	/// Succeeds iff `x` < 2^16
162	fn try_from(x: VarInt) -> Result<Self, BoundsExceeded> {
163		if x.0 <= u16::MAX.into() {
164			Ok(x.0 as u16)
165		} else {
166			Err(BoundsExceeded)
167		}
168	}
169}
170
171impl TryFrom<VarInt> for u8 {
172	type Error = BoundsExceeded;
173
174	/// Succeeds iff `x` < 2^8
175	fn try_from(x: VarInt) -> Result<Self, BoundsExceeded> {
176		if x.0 <= u8::MAX.into() {
177			Ok(x.0 as u8)
178		} else {
179			Err(BoundsExceeded)
180		}
181	}
182}
183
184impl fmt::Display for VarInt {
185	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186		self.0.fmt(f)
187	}
188}
189
190impl VarInt {
191	/// Decode a QUIC-style varint (2-bit length tag in top bits).
192	pub fn decode_quic<R: bytes::Buf>(r: &mut R) -> Result<Self, DecodeError> {
193		if !r.has_remaining() {
194			return Err(DecodeError::Short);
195		}
196
197		let b = r.get_u8();
198		let tag = b >> 6;
199
200		let mut buf = [0u8; 8];
201		buf[0] = b & 0b0011_1111;
202
203		let x = match tag {
204			0b00 => u64::from(buf[0]),
205			0b01 => {
206				if !r.has_remaining() {
207					return Err(DecodeError::Short);
208				}
209				r.copy_to_slice(buf[1..2].as_mut());
210				u64::from(u16::from_be_bytes(buf[..2].try_into().unwrap()))
211			}
212			0b10 => {
213				if r.remaining() < 3 {
214					return Err(DecodeError::Short);
215				}
216				r.copy_to_slice(buf[1..4].as_mut());
217				u64::from(u32::from_be_bytes(buf[..4].try_into().unwrap()))
218			}
219			0b11 => {
220				if r.remaining() < 7 {
221					return Err(DecodeError::Short);
222				}
223				r.copy_to_slice(buf[1..8].as_mut());
224				u64::from_be_bytes(buf)
225			}
226			_ => unreachable!(),
227		};
228
229		Ok(Self(x))
230	}
231
232	/// Encode a QUIC-style varint (2-bit length tag in top bits).
233	pub fn encode_quic<W: bytes::BufMut>(&self, w: &mut W) -> Result<(), EncodeError> {
234		let remaining = w.remaining_mut();
235		if self.0 < (1u64 << 6) {
236			if remaining < 1 {
237				return Err(EncodeError::Short);
238			}
239			w.put_u8(self.0 as u8);
240		} else if self.0 < (1u64 << 14) {
241			if remaining < 2 {
242				return Err(EncodeError::Short);
243			}
244			w.put_u16((0b01 << 14) | self.0 as u16);
245		} else if self.0 < (1u64 << 30) {
246			if remaining < 4 {
247				return Err(EncodeError::Short);
248			}
249			w.put_u32((0b10 << 30) | self.0 as u32);
250		} else if self.0 < (1u64 << 62) {
251			if remaining < 8 {
252				return Err(EncodeError::Short);
253			}
254			w.put_u64((0b11 << 62) | self.0);
255		} else {
256			return Err(BoundsExceeded.into());
257		}
258		Ok(())
259	}
260
261	/// Decode a leading-1-bits varint (draft-17+ Section 1.4.1).
262	///
263	/// The number of leading 1-bits determines the byte length:
264	/// - `0xxxxxxx` → 1 byte, 7 usable bits
265	/// - `10xxxxxx` → 2 bytes, 14 usable bits
266	/// - `110xxxxx` → 3 bytes, 21 usable bits
267	/// - `1110xxxx` → 4 bytes, 28 usable bits
268	/// - `11110xxx` → 5 bytes, 35 usable bits
269	/// - `111110xx` → 6 bytes, 42 usable bits
270	/// - `1111110x` → 7 bytes, 49 usable bits (draft-18+, INVALID in draft-17 per #1595)
271	/// - `11111110` → 8 bytes, 56 usable bits
272	/// - `11111111` → 9 bytes, 64 usable bits
273	fn decode_leading_ones<R: bytes::Buf>(r: &mut R) -> Result<Self, DecodeError> {
274		if !r.has_remaining() {
275			return Err(DecodeError::Short);
276		}
277
278		let b = r.get_u8();
279		let ones = b.leading_ones() as usize;
280
281		match ones {
282			0 => {
283				// 0xxxxxxx: 7 bits
284				Ok(Self(u64::from(b)))
285			}
286			1 => {
287				// 10xxxxxx + 1 byte: 14 bits
288				if !r.has_remaining() {
289					return Err(DecodeError::Short);
290				}
291				let hi = u64::from(b & 0x3F);
292				let lo = u64::from(r.get_u8());
293				Ok(Self((hi << 8) | lo))
294			}
295			2 => {
296				// 110xxxxx + 2 bytes: 21 bits
297				if r.remaining() < 2 {
298					return Err(DecodeError::Short);
299				}
300				let hi = u64::from(b & 0x1F);
301				let mut buf = [0u8; 2];
302				r.copy_to_slice(&mut buf);
303				Ok(Self((hi << 16) | u64::from(u16::from_be_bytes(buf))))
304			}
305			3 => {
306				// 1110xxxx + 3 bytes: 28 bits
307				if r.remaining() < 3 {
308					return Err(DecodeError::Short);
309				}
310				let hi = u64::from(b & 0x0F);
311				let mut buf = [0u8; 3];
312				r.copy_to_slice(&mut buf);
313				Ok(Self(
314					(hi << 24) | u64::from(buf[0]) << 16 | u64::from(buf[1]) << 8 | u64::from(buf[2]),
315				))
316			}
317			4 => {
318				// 11110xxx + 4 bytes: 35 bits
319				if r.remaining() < 4 {
320					return Err(DecodeError::Short);
321				}
322				let hi = u64::from(b & 0x07);
323				let mut buf = [0u8; 4];
324				r.copy_to_slice(&mut buf);
325				Ok(Self((hi << 32) | u64::from(u32::from_be_bytes(buf))))
326			}
327			5 => {
328				// 111110xx + 5 bytes: 42 bits
329				if r.remaining() < 5 {
330					return Err(DecodeError::Short);
331				}
332				let hi = u64::from(b & 0x03);
333				let mut buf = [0u8; 5];
334				r.copy_to_slice(&mut buf);
335				let lo = u64::from(buf[0]) << 32
336					| u64::from(buf[1]) << 24
337					| u64::from(buf[2]) << 16
338					| u64::from(buf[3]) << 8
339					| u64::from(buf[4]);
340				Ok(Self((hi << 40) | lo))
341			}
342			6 => {
343				// 1111110x + 6 bytes, 49 bits (draft-18+, INVALID in draft-17 per #1595)
344				if r.remaining() < 6 {
345					return Err(DecodeError::Short);
346				}
347				let hi = u64::from(b & 0x01);
348				let mut buf = [0u8; 8];
349				r.copy_to_slice(&mut buf[2..]);
350				Ok(Self((hi << 48) | u64::from_be_bytes(buf)))
351			}
352			7 => {
353				// 11111110 + 7 bytes: 56 bits
354				if r.remaining() < 7 {
355					return Err(DecodeError::Short);
356				}
357				let mut buf = [0u8; 8];
358				buf[0] = 0;
359				r.copy_to_slice(&mut buf[1..]);
360				Ok(Self(u64::from_be_bytes(buf)))
361			}
362			8 => {
363				// 11111111 + 8 bytes: 64 bits
364				if r.remaining() < 8 {
365					return Err(DecodeError::Short);
366				}
367				let mut buf = [0u8; 8];
368				r.copy_to_slice(&mut buf);
369				Ok(Self(u64::from_be_bytes(buf)))
370			}
371			_ => unreachable!(),
372		}
373	}
374
375	/// Encode a leading-1-bits varint (draft-17+ Section 1.4.1).
376	///
377	/// Always emits the minimal canonical form. Draft-18 also accepts 7-byte form
378	/// (`1111110x`) on decode but we never emit it because the 8-byte form is one byte
379	/// larger but simpler and is universally valid.
380	fn encode_leading_ones<W: bytes::BufMut>(&self, w: &mut W) -> Result<(), EncodeError> {
381		let x = self.0;
382		let remaining = w.remaining_mut();
383
384		if x < (1 << 7) {
385			// 0xxxxxxx: 1 byte
386			if remaining < 1 {
387				return Err(EncodeError::Short);
388			}
389			w.put_u8(x as u8);
390		} else if x < (1 << 14) {
391			// 10xxxxxx: 2 bytes
392			if remaining < 2 {
393				return Err(EncodeError::Short);
394			}
395			w.put_u8(0x80 | (x >> 8) as u8);
396			w.put_u8(x as u8);
397		} else if x < (1 << 21) {
398			// 110xxxxx: 3 bytes
399			if remaining < 3 {
400				return Err(EncodeError::Short);
401			}
402			w.put_u8(0xC0 | (x >> 16) as u8);
403			w.put_u16(x as u16);
404		} else if x < (1 << 28) {
405			// 1110xxxx: 4 bytes
406			if remaining < 4 {
407				return Err(EncodeError::Short);
408			}
409			w.put_u8(0xE0 | (x >> 24) as u8);
410			w.put_u8((x >> 16) as u8);
411			w.put_u16(x as u16);
412		} else if x < (1 << 35) {
413			// 11110xxx: 5 bytes
414			if remaining < 5 {
415				return Err(EncodeError::Short);
416			}
417			w.put_u8(0xF0 | (x >> 32) as u8);
418			w.put_u32(x as u32);
419		} else if x < (1 << 42) {
420			// 111110xx: 6 bytes
421			if remaining < 6 {
422				return Err(EncodeError::Short);
423			}
424			w.put_u8(0xF8 | (x >> 40) as u8);
425			w.put_u8((x >> 32) as u8);
426			w.put_u32(x as u32);
427		} else if x < (1 << 56) {
428			// 11111110: 8 bytes (skips 7)
429			if remaining < 8 {
430				return Err(EncodeError::Short);
431			}
432			w.put_u8(0xFE);
433			// Write 7 bytes: high byte then low 6 bytes
434			w.put_u8((x >> 48) as u8);
435			w.put_u16((x >> 32) as u16);
436			w.put_u32(x as u32);
437		} else {
438			// 11111111: 9 bytes
439			if remaining < 9 {
440				return Err(EncodeError::Short);
441			}
442			w.put_u8(0xFF);
443			w.put_u64(x);
444		}
445
446		Ok(())
447	}
448}
449
450use crate::{Version, ietf, lite};
451
452// Lite01-06 use QUIC-style varints; lite-07+ uses leading-ones. Lite-07 is only reached
453// through its ALPN, so the codec is known before the first byte of any stream.
454impl Encode<lite::Version> for VarInt {
455	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: lite::Version) -> Result<(), EncodeError> {
456		match version {
457			lite::Version::Lite01
458			| lite::Version::Lite02
459			| lite::Version::Lite03
460			| lite::Version::Lite04
461			| lite::Version::Lite05
462			| lite::Version::Lite06 => self.encode_quic(w),
463			_ => self.encode_leading_ones(w),
464		}
465	}
466}
467
468impl Decode<lite::Version> for VarInt {
469	fn decode<R: bytes::Buf>(r: &mut R, version: lite::Version) -> Result<Self, DecodeError> {
470		match version {
471			lite::Version::Lite01
472			| lite::Version::Lite02
473			| lite::Version::Lite03
474			| lite::Version::Lite04
475			| lite::Version::Lite05
476			| lite::Version::Lite06 => Self::decode_quic(r),
477			// Lite-07 values span the full 64 bits, but `VarInt` is still 62-bit here, so a
478			// larger value is a loud decode error, never a truncation. Known limitation, lifted
479			// when the VarInt codec quest (quest/m1/rs2ts/varint-codec.md) widens `VarInt`.
480			_ => match Self::decode_leading_ones(r)? {
481				x if x > Self::MAX => Err(DecodeError::BoundsExceeded),
482				x => Ok(x),
483			},
484		}
485	}
486}
487
488// Draft14-16 use QUIC-style varints; draft-17+ uses leading-ones.
489impl Encode<ietf::Version> for VarInt {
490	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: ietf::Version) -> Result<(), EncodeError> {
491		match version {
492			ietf::Version::Draft14 | ietf::Version::Draft15 | ietf::Version::Draft16 => self.encode_quic(w),
493			_ => self.encode_leading_ones(w),
494		}
495	}
496}
497
498impl Decode<ietf::Version> for VarInt {
499	fn decode<R: bytes::Buf>(r: &mut R, version: ietf::Version) -> Result<Self, DecodeError> {
500		match version {
501			ietf::Version::Draft14 | ietf::Version::Draft15 | ietf::Version::Draft16 => Self::decode_quic(r),
502			// Draft-18 made 1111110x the 7-byte form; draft-17 reserves it (#1595).
503			ietf::Version::Draft17 if r.chunk().first().is_some_and(|b| b.leading_ones() == 6) => {
504				Err(DecodeError::InvalidValue)
505			}
506			_ => Self::decode_leading_ones(r),
507		}
508	}
509}
510
511// The top-level Version delegates to the sub-version impls.
512impl Encode<Version> for VarInt {
513	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
514		match version {
515			Version::Lite(v) => self.encode(w, v),
516			Version::Ietf(v) => self.encode(w, v),
517		}
518	}
519}
520
521impl Decode<Version> for VarInt {
522	fn decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
523		match version {
524			Version::Lite(v) => Self::decode(r, v),
525			Version::Ietf(v) => Self::decode(r, v),
526		}
527	}
528}
529
530// Blanket impls for integer types that delegate to VarInt.
531impl<V: Copy> Encode<V> for u64
532where
533	VarInt: Encode<V>,
534{
535	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
536		VarInt::try_from(*self)?.encode(w, version)
537	}
538}
539
540impl<V: Copy> Decode<V> for u64
541where
542	VarInt: Decode<V>,
543{
544	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
545		VarInt::decode(r, version).map(|v| v.into_inner())
546	}
547}
548
549impl<V: Copy> Encode<V> for usize
550where
551	VarInt: Encode<V>,
552{
553	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
554		VarInt::try_from(*self)?.encode(w, version)
555	}
556}
557
558impl<V: Copy> Decode<V> for usize
559where
560	VarInt: Decode<V>,
561{
562	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
563		// Truncating on 32-bit targets (wasm32) would let an oversized length pass a size cap.
564		let v = VarInt::decode(r, version)?;
565		v.into_inner().try_into().map_err(|_| DecodeError::BoundsExceeded)
566	}
567}
568
569impl<V: Copy> Encode<V> for u32
570where
571	VarInt: Encode<V>,
572{
573	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
574		VarInt::from(*self).encode(w, version)
575	}
576}
577
578impl<V: Copy> Decode<V> for u32
579where
580	VarInt: Decode<V>,
581{
582	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
583		let v = VarInt::decode(r, version)?;
584		let v = v.try_into().map_err(|_| DecodeError::BoundsExceeded)?;
585		Ok(v)
586	}
587}
588
589#[cfg(test)]
590mod tests {
591	use super::*;
592	use crate::{ietf, lite};
593	use bytes::Bytes;
594
595	/// Test vectors from the draft-17 spec (Table 2: Example Integer Encodings),
596	/// excluding the known-buggy example 4 (0xdd7f3e7d).
597	#[test]
598	fn leading_ones_spec_examples() {
599		let cases: &[(&[u8], u64)] = &[
600			(&[0x25], 37),
601			(&[0x80, 0x25], 37),
602			(&[0xbb, 0xbd], 15_293),
603			// Example 4 (0xdd7f3e7d = 494,878,333) is omitted. The spec has a bug.
604			// See https://github.com/moq-wg/moq-transport/pull/1521
605			(&[0xfa, 0xa1, 0xa0, 0xe4, 0x03, 0xd8], 2_893_212_287_960),
606			(
607				&[0xfe, 0xfa, 0x31, 0x8f, 0xa8, 0xe3, 0xca, 0x11],
608				70_423_237_261_249_041,
609			),
610			(
611				&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
612				18_446_744_073_709_551_615,
613			),
614		];
615
616		for (bytes, expected) in cases {
617			// Test decoding
618			let mut buf = Bytes::from(bytes.to_vec());
619			let decoded = VarInt::decode_leading_ones(&mut buf).expect("decode should succeed");
620			assert_eq!(
621				decoded.into_inner(),
622				*expected,
623				"decode mismatch for bytes {bytes:02x?}"
624			);
625			assert_eq!(buf.len(), 0, "all bytes should be consumed for {bytes:02x?}");
626
627			// Test round-trip encode:
628			// - Skip non-minimal encoding (0x8025 for 37)
629			// - Skip u64::MAX which exceeds VarInt::MAX (2^62-1) but is decodable
630			if let Some(varint) = VarInt::from_u64(*expected)
631				&& (bytes.len() == 1 || *expected != 37)
632			{
633				let mut encoded = Vec::new();
634				varint.encode_leading_ones(&mut encoded).expect("encode should succeed");
635				assert_eq!(&encoded, bytes, "encode mismatch for value {expected}");
636			}
637		}
638	}
639
640	/// 11111100 (0xFC) is an invalid code point on draft-17 (allowed as 7-byte form on draft-18+).
641	#[test]
642	fn leading_ones_invalid_0xfc() {
643		let mut buf = Bytes::from_static(&[0xFC]);
644		assert!(
645			matches!(
646				VarInt::decode(&mut buf, ietf::Version::Draft17),
647				Err(DecodeError::InvalidValue)
648			),
649			"0xFC should be rejected as invalid on draft-17"
650		);
651	}
652
653	#[test]
654	fn leading_ones_boundaries_round_trip() {
655		let cases = [
656			((1u64 << 7) - 1, 1usize),
657			(1u64 << 7, 2usize),
658			((1u64 << 14) - 1, 2usize),
659			(1u64 << 14, 3usize),
660			((1u64 << 56) - 1, 8usize),
661			(1u64 << 56, 9usize),
662		];
663
664		for (value, expected_len) in cases {
665			let varint = VarInt::from_u64(value).expect("value should be representable as VarInt");
666			let mut encoded = Vec::new();
667			varint
668				.encode_leading_ones(&mut encoded)
669				.expect("leading-ones encode should succeed");
670			assert_eq!(
671				encoded.len(),
672				expected_len,
673				"unexpected encoded length for value {value}"
674			);
675
676			let mut bytes = Bytes::from(encoded);
677			let decoded = VarInt::decode_leading_ones(&mut bytes).expect("leading-ones decode should succeed");
678			assert_eq!(decoded.into_inner(), value, "round-trip mismatch for value {value}");
679		}
680	}
681
682	#[test]
683	fn draft17_rejects_7_byte_varint() {
684		// 1111110x prefix: invalid on draft-17.
685		let bytes = Bytes::from(vec![0xFC, 0, 0, 0, 0, 0, 0]);
686		let mut buf = bytes.clone();
687		let err = VarInt::decode(&mut buf, ietf::Version::Draft17).unwrap_err();
688		assert!(matches!(err, DecodeError::InvalidValue));
689	}
690
691	#[test]
692	fn zigzag_roundtrip_small() {
693		for n in [-3i64, -2, -1, 0, 1, 2, 3, 100, -100] {
694			let v = VarInt::from_zigzag(n).unwrap();
695			assert_eq!(v.to_zigzag(), n, "roundtrip failed for {}", n);
696		}
697	}
698
699	#[test]
700	fn zigzag_small_values_compact() {
701		// First few values should fit in 1 byte (varint range 0..=63 = top-2-bits tag 00).
702		assert_eq!(VarInt::from_zigzag(0).unwrap().into_inner(), 0);
703		assert_eq!(VarInt::from_zigzag(-1).unwrap().into_inner(), 1);
704		assert_eq!(VarInt::from_zigzag(1).unwrap().into_inner(), 2);
705		assert_eq!(VarInt::from_zigzag(-2).unwrap().into_inner(), 3);
706		assert_eq!(VarInt::from_zigzag(2).unwrap().into_inner(), 4);
707	}
708
709	#[test]
710	fn zigzag_roundtrip_boundary() {
711		// Boundary values in the valid input range [-2^61, 2^61 - 1].
712		let max = (1i64 << 61) - 1;
713		let min = -(1i64 << 61);
714		let mid = (1i64 << 30) + 17;
715
716		for n in [max, min, mid, -mid] {
717			let v = VarInt::from_zigzag(n).unwrap();
718			assert_eq!(v.to_zigzag(), n);
719		}
720	}
721
722	#[test]
723	fn zigzag_out_of_range_rejected() {
724		// Values past the i61 boundary are out of varint range.
725		assert!(VarInt::from_zigzag(1i64 << 61).is_err());
726		assert!(VarInt::from_zigzag(-(1i64 << 61) - 1).is_err());
727		assert!(VarInt::from_zigzag(i64::MAX).is_err());
728		assert!(VarInt::from_zigzag(i64::MIN).is_err());
729	}
730
731	#[test]
732	fn zigzag_quic_varint_roundtrip() {
733		// Encode a zigzag value through the QUIC varint wire format.
734		for n in [-5000i64, 0, 100, -1, 1_000_000, -1_000_000] {
735			let v = VarInt::from_zigzag(n).unwrap();
736
737			let mut buf = bytes::BytesMut::new();
738			v.encode(&mut buf, lite::Version::Lite01).unwrap();
739			let mut bytes = buf.freeze();
740			let decoded = VarInt::decode(&mut bytes, lite::Version::Lite01).unwrap();
741			assert_eq!(decoded.to_zigzag(), n);
742		}
743	}
744
745	fn lite(value: u64, version: lite::Version) -> Vec<u8> {
746		let mut buf = Vec::new();
747		VarInt::try_from(value).unwrap().encode(&mut buf, version).unwrap();
748		buf
749	}
750
751	/// Lite-01 through lite-06 keep the QUIC form byte for byte.
752	#[test]
753	fn lite06_keeps_quic_varints() {
754		for version in [lite::Version::Lite01, lite::Version::Lite05, lite::Version::Lite06] {
755			assert_eq!(lite(63, version), [0x3F]);
756			assert_eq!(lite(64, version), [0x40, 0x40]);
757			assert_eq!(lite(16_384, version), [0x80, 0x00, 0x40, 0x00]);
758		}
759	}
760
761	/// Lite-07 switches to leading-ones, including at every length boundary.
762	#[test]
763	fn lite07_uses_leading_ones() {
764		let version = lite::Version::Lite07;
765		let cases: &[(u64, &[u8])] = &[
766			(0, &[0x00]),
767			(127, &[0x7F]),
768			(128, &[0x80, 0x80]),
769			((1 << 14) - 1, &[0xBF, 0xFF]),
770			(1 << 14, &[0xC0, 0x40, 0x00]),
771			((1 << 21) - 1, &[0xDF, 0xFF, 0xFF]),
772			(1 << 21, &[0xE0, 0x20, 0x00, 0x00]),
773			((1 << 56) - 1, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
774			(1 << 56, &[0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
775			(
776				VarInt::MAX.into_inner(),
777				&[0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
778			),
779		];
780		for (value, wire) in cases {
781			assert_eq!(lite(*value, version), *wire, "encode {value}");
782			let mut buf = *wire;
783			assert_eq!(VarInt::decode(&mut buf, version).unwrap().into_inner(), *value);
784			assert!(buf.is_empty());
785		}
786	}
787
788	/// The 7-byte `1111110x` form is valid on lite-07, as on draft-18+.
789	#[test]
790	fn lite07_accepts_7_byte_varint() {
791		let mut buf: &[u8] = &[0xFD, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD];
792		let decoded = VarInt::decode(&mut buf, lite::Version::Lite07).unwrap();
793		assert_eq!(decoded.into_inner(), 0x1_2345_6789_ABCD);
794	}
795
796	/// Lite-07 values above 2^62-1 are legal on the wire, but the 62-bit `VarInt` cannot
797	/// hold them yet, so they must fail loud rather than wrap or truncate.
798	#[test]
799	fn lite07_values_above_62_bits_fail_loud() {
800		for wire in [[0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0xFF; 9]] {
801			let err = VarInt::decode(&mut &wire[..], lite::Version::Lite07).unwrap_err();
802			assert!(matches!(err, DecodeError::BoundsExceeded), "{wire:02x?}: {err:?}");
803		}
804	}
805
806	#[test]
807	fn draft18_accepts_7_byte_varint() {
808		// Value 0x1234_5678_9ABC encoded as 7-byte leading-ones (1111110x | hi, +6 bytes).
809		let value: u64 = 0x1234_5678_9ABC;
810		let mut bytes = Vec::new();
811		// Prefix byte: 1111110_0 + (value >> 48) bit. Top 1 bit of 49 = bit 48.
812		// value fits in 49 bits, so the 0x01 LSB of prefix encodes bit 48 of value.
813		let hi_bit = ((value >> 48) & 0x01) as u8;
814		bytes.push(0xFC | hi_bit);
815		for shift in (0..48).step_by(8).rev() {
816			bytes.push(((value >> shift) & 0xFF) as u8);
817		}
818		let mut buf = Bytes::from(bytes);
819		let decoded = VarInt::decode(&mut buf, ietf::Version::Draft18).unwrap();
820		assert_eq!(decoded.into_inner(), value);
821	}
822}