Skip to main content

s2n_quic_core/varint/
table.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::MAX_VARINT_VALUE;
5use core::fmt;
6use s2n_codec::{Encoder, EncoderValue};
7
8//# The QUIC variable-length integer encoding reserves the two most
9//# significant bits of the first byte to encode the base 2 logarithm of
10//# the integer encoding length in bytes.  The integer value is encoded
11//# on the remaining bits, in network byte order.
12
13//= https://www.rfc-editor.org/rfc/rfc9000#section-16
14//# This means that integers are encoded on 1, 2, 4, or 8 bytes and can
15//# encode 6-, 14-, 30-, or 62-bit values, respectively.  Table 4
16//# summarizes the encoding properties.
17//#
18//#        +======+========+=============+=======================+
19//#        | 2MSB | Length | Usable Bits | Range                 |
20//#        +======+========+=============+=======================+
21//#        | 00   | 1      | 6           | 0-63                  |
22//#        +------+--------+-------------+-----------------------+
23//#        | 01   | 2      | 14          | 0-16383               |
24//#        +------+--------+-------------+-----------------------+
25//#        | 10   | 4      | 30          | 0-1073741823          |
26//#        +------+--------+-------------+-----------------------+
27//#        | 11   | 8      | 62          | 0-4611686018427387903 |
28//#        +------+--------+-------------+-----------------------+
29
30macro_rules! call_table {
31    ($table:ident) => {
32        $table! {
33            (0b10, 4, 30, 1_073_741_823);
34            (0b01, 2, 14, 16_383);
35            (0b00, 1, 6 , 63);
36        }
37    };
38}
39
40#[derive(Clone, Copy, PartialEq, Eq)]
41pub struct Entry {
42    /// The two bits to use for this entry, encoding in native-endian
43    pub two_bit: u64,
44    /// The two bits to use for this entry, encoding in big-endian
45    pub two_bit_be: u64,
46    /// The number of bytes required to encode this entry
47    pub len: usize,
48    /// The number of usable bits for the actual value
49    pub usable_bits: u64,
50    /// The number of bits to shift for encoding/decoding
51    pub shift: u64,
52}
53
54impl fmt::Debug for Entry {
55    #[inline]
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        f.debug_struct("Entry")
58            .field("two_bit", &self.two_bit)
59            .field("two_bit_be", &self.two_bit_be.to_ne_bytes())
60            .field("len", &self.len)
61            .field("usable_bits", &self.usable_bits)
62            .field("shift", &self.shift)
63            .finish()
64    }
65}
66
67impl Entry {
68    /// Returns the entry in the table corresponding to the provided value
69    #[inline(always)]
70    pub fn read(x: u64) -> Self {
71        let optimized = Self::read_optimized(x);
72        debug_assert_eq!(optimized, Self::read_rfc(x));
73        optimized
74    }
75
76    /// A simple implementation of the table lookup, mostly based on the RFC table
77    #[inline(always)]
78    pub fn read_rfc(x: u64) -> Self {
79        // write the table from the RFC in a non-optimized format and make sure the
80        // actual results match
81        #[allow(clippy::match_overlapping_arm)]
82        match x {
83            0..=63 => Self {
84                two_bit: 0b00,
85                two_bit_be: 0b00,
86                len: 1,
87                usable_bits: 6,
88                shift: 56,
89            },
90            0..=16383 => Self {
91                two_bit: 0b01,
92                two_bit_be: (0b01u64 << 62).to_be(),
93                len: 2,
94                usable_bits: 14,
95                shift: 48,
96            },
97            0..=1073741823 => Self {
98                two_bit: 0b10,
99                two_bit_be: (0b10u64 << 62).to_be(),
100                len: 4,
101                usable_bits: 30,
102                shift: 32,
103            },
104            0..=4611686018427387903 => Self {
105                two_bit: 0b11,
106                two_bit_be: (0b11u64 << 62).to_be(),
107                len: 8,
108                usable_bits: 62,
109                shift: 0,
110            },
111            _ => unreachable!(),
112        }
113    }
114
115    // https://godbolt.org/z/9xrxd1osd
116    #[inline(always)]
117    pub fn read_optimized(x: u64) -> Self {
118        unsafe {
119            crate::assume!(x <= MAX_VARINT_VALUE);
120        }
121
122        macro_rules! table {
123            ($(($two_bit:expr, $length:expr, $usable_bits:expr, $max_value:expr);)*) => {{
124                let mut shift = 0;
125                let mut usable_bits = 62;
126                let mut two_bit = 0b11u64;
127                let mut two_bit_be = (two_bit << 62).to_be();
128                let mut len = 8;
129
130                $(
131                    if x <= $max_value {
132                        shift = 62 - $usable_bits;
133                        usable_bits = $usable_bits;
134                        two_bit -= 1u64;
135                        two_bit_be -= (1u64 << 62).to_be();
136                        len = $length;
137                    }
138                )*
139
140                Self { two_bit, two_bit_be, len, usable_bits, shift }
141            }};
142        }
143
144        call_table!(table)
145    }
146
147    #[inline(always)]
148    pub fn format(&self, value: u64) -> Formatted {
149        let encoded_be = (value << self.shift).to_be() | self.two_bit_be;
150        let len = self.len;
151        Formatted { encoded_be, len }
152    }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156pub struct Formatted {
157    pub(crate) encoded_be: u64,
158    pub(crate) len: usize,
159}
160
161impl Formatted {
162    #[inline(always)]
163    pub(super) fn new(x: u64) -> Self {
164        unsafe {
165            crate::assume!(x <= MAX_VARINT_VALUE);
166        }
167
168        macro_rules! table {
169            ($(($two_bit:expr, $length:expr, $usable_bits:expr, $max_value:expr);)*) => {{
170                let mut shift = 0;
171                let mut two_bit = (0b11u64 << 62).to_be();
172                let mut len = 8;
173
174                $(
175                    if x <= $max_value {
176                        shift = 62 - $usable_bits;
177                        two_bit -= (1u64 << 62).to_be();
178                        len = $length;
179                    }
180                )*
181
182                #[cfg(debug_assertions)]
183                {
184                    // make sure we end up with the same computed entry
185                    let entry = Entry::read_rfc(x);
186                    assert_eq!(entry.shift, shift);
187                    assert_eq!(entry.two_bit_be, two_bit);
188                    assert_eq!(entry.len, len);
189                }
190
191                let encoded_be = (x << shift).to_be() | two_bit;
192
193                let v = Self { encoded_be, len };
194
195                v.invariants();
196
197                v
198            }};
199        }
200
201        call_table!(table)
202    }
203
204    #[inline(always)]
205    pub(super) fn encode_oversized<E: Encoder>(&self, encoder: &mut E) {
206        debug_assert!(encoder.remaining_capacity() >= 8);
207        self.invariants();
208
209        encoder.write_sized(self.len, |dest| {
210            let dest = dest.as_mut_ptr() as *mut [u8; 8];
211            let bytes = self.encoded_be.to_ne_bytes();
212            unsafe {
213                core::ptr::write(dest, bytes);
214            }
215        });
216    }
217
218    #[inline(always)]
219    pub(super) fn encode_maybe_undersized<E: Encoder>(&self, encoder: &mut E) {
220        self.invariants();
221
222        let len = self.len;
223
224        encoder.write_sized(len, |dst| {
225            let src = self.encoded_be.to_ne_bytes();
226            unsafe {
227                use core::ptr::copy_nonoverlapping as copy;
228
229                crate::assume!(dst.len() == len);
230
231                let src = src.as_ptr();
232                let dst = dst.as_mut_ptr();
233
234                match len {
235                    1 => copy(src, dst, 1),
236                    2 => copy(src, dst, 2),
237                    4 => copy(src, dst, 4),
238                    8 => copy(src, dst, 8),
239                    _ => {
240                        assume!(false, "invalid len: {len}");
241                    }
242                }
243            }
244        });
245    }
246
247    #[inline(always)]
248    pub(super) fn invariants(&self) {
249        unsafe {
250            let len = self.len;
251            // avoid using `contains` since kani wants an unwind for that
252            assume!(len == 1 || len == 2 || len == 4 || len == 8);
253        }
254    }
255}
256
257impl EncoderValue for Formatted {
258    #[inline(always)]
259    fn encode<E: Encoder>(&self, encoder: &mut E) {
260        self.invariants();
261
262        // optimize for the case where we have at least 8 bytes left and just write a full u64 to
263        // the buffer, but incrementing the offset by `self.len`.
264        //
265        // ignore this optimization under miri, since we're technically reading beyond the slice
266        // that the encoder gives us, which miri complains about.
267        if encoder.remaining_capacity() >= 8 && !cfg!(miri) {
268            self.encode_oversized(encoder);
269        } else {
270            self.encode_maybe_undersized(encoder);
271        }
272    }
273
274    #[inline(always)]
275    fn encoding_size(&self) -> usize {
276        self.len
277    }
278
279    #[inline(always)]
280    fn encoding_size_for_encoder<E: Encoder>(&self, _encoder: &E) -> usize {
281        self.len
282    }
283}