s2n_quic_core/frame/
crypto.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    frame::{FitError, Tag},
6    varint::VarInt,
7};
8use core::{convert::TryFrom, mem::size_of};
9use s2n_codec::{
10    decoder_parameterized_value, DecoderBuffer, DecoderBufferMut, Encoder, EncoderValue,
11};
12
13//= https://www.rfc-editor.org/rfc/rfc9000#section-19.6
14//# A CRYPTO frame (type=0x06) is used to transmit cryptographic
15//# handshake messages.
16
17macro_rules! crypto_tag {
18    () => {
19        0x06u8
20    };
21}
22
23//= https://www.rfc-editor.org/rfc/rfc9000#section-19.6
24//# CRYPTO Frame {
25//#   Type (i) = 0x06,
26//#   Offset (i),
27//#   Length (i),
28//#   Crypto Data (..),
29//# }
30
31//= https://www.rfc-editor.org/rfc/rfc9000#section-19.6
32//# CRYPTO frames contain the following fields:
33//#
34//# Offset:  A variable-length integer specifying the byte offset in the
35//#    stream for the data in this CRYPTO frame.
36//#
37//# Length:  A variable-length integer specifying the length of the
38//#    Crypto Data field in this CRYPTO frame.
39//#
40//# Crypto Data:  The cryptographic message data.
41
42#[derive(Debug, PartialEq, Eq)]
43pub struct Crypto<Data> {
44    /// A variable-length integer specifying the byte offset in the stream
45    /// for the data in this CRYPTO frame.
46    pub offset: VarInt,
47
48    /// The cryptographic message data.
49    pub data: Data,
50}
51
52impl<Data> Crypto<Data> {
53    #[inline]
54    pub const fn tag(&self) -> u8 {
55        crypto_tag!()
56    }
57
58    /// Converts the crypto data from one type to another
59    #[inline]
60    pub fn map_data<F: FnOnce(Data) -> Out, Out>(self, map: F) -> Crypto<Out> {
61        Crypto {
62            offset: self.offset,
63            data: map(self.data),
64        }
65    }
66}
67
68impl<Data: EncoderValue> Crypto<Data> {
69    /// Tries to fit the frame into the provided capacity
70    ///
71    /// If ok, the new payload length is returned, otherwise the frame cannot
72    /// fit.
73    #[inline]
74    pub fn try_fit(&self, capacity: usize) -> Result<usize, FitError> {
75        let mut fixed_len = 0;
76        fixed_len += size_of::<Tag>();
77        fixed_len += self.offset.encoding_size();
78
79        let remaining_capacity = capacity.checked_sub(fixed_len).ok_or(FitError)?;
80
81        let data_len = self.data.encoding_size();
82        let max_data_len = remaining_capacity.min(data_len);
83
84        let len_prefix_size = VarInt::try_from(max_data_len)
85            .map_err(|_| FitError)?
86            .encoding_size();
87
88        let prefixed_data_len = remaining_capacity
89            .checked_sub(len_prefix_size)
90            .ok_or(FitError)?;
91        let data_len = prefixed_data_len.min(data_len);
92
93        Ok(data_len)
94    }
95}
96
97pub type CryptoRef<'a> = Crypto<&'a [u8]>;
98pub type CryptoMut<'a> = Crypto<&'a mut [u8]>;
99
100decoder_parameterized_value!(
101    impl<'a, Data> Crypto<Data> {
102        fn decode(_tag: Tag, buffer: Buffer) -> Result<Self> {
103            let (offset, buffer) = buffer.decode()?;
104            let (data, buffer) = buffer.decode_with_len_prefix::<VarInt, Data>()?;
105
106            let frame = Crypto { offset, data };
107
108            Ok((frame, buffer))
109        }
110    }
111);
112
113impl<Data: EncoderValue> EncoderValue for Crypto<Data> {
114    #[inline]
115    fn encode<E: Encoder>(&self, buffer: &mut E) {
116        buffer.encode(&self.tag());
117        buffer.encode(&self.offset);
118        buffer.encode_with_len_prefix::<VarInt, _>(&self.data);
119    }
120}
121
122impl<'a> From<Crypto<DecoderBuffer<'a>>> for CryptoRef<'a> {
123    #[inline]
124    fn from(s: Crypto<DecoderBuffer<'a>>) -> Self {
125        s.map_data(|data| data.into_less_safe_slice())
126    }
127}
128
129impl<'a> From<Crypto<DecoderBufferMut<'a>>> for CryptoRef<'a> {
130    #[inline]
131    fn from(s: Crypto<DecoderBufferMut<'a>>) -> Self {
132        s.map_data(|data| &*data.into_less_safe_slice())
133    }
134}
135
136impl<'a> From<Crypto<DecoderBufferMut<'a>>> for CryptoMut<'a> {
137    #[inline]
138    fn from(s: Crypto<DecoderBufferMut<'a>>) -> Self {
139        s.map_data(|data| data.into_less_safe_slice())
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::frame::Padding;
147    use bolero::check;
148    use core::convert::TryInto;
149
150    fn model(offset: VarInt, length: VarInt, capacity: usize) {
151        let length = if let Ok(length) = VarInt::try_into(length) {
152            length
153        } else {
154            // if the length cannot be represented by `usize` then bail
155            return;
156        };
157
158        let mut frame = Crypto {
159            offset,
160            data: Padding { length },
161        };
162
163        if let Ok(new_length) = frame.try_fit(capacity) {
164            frame.data = Padding { length: new_length };
165
166            assert!(
167                frame.encoding_size() <= capacity,
168                "the encoding_size should not exceed capacity {frame:#?}"
169            );
170
171            if new_length < length {
172                // Ideally `frame.encoding_size() == capacity` but in some cases, the payload
173                // needs to be decreased to fit `capacity` and by decreasing the payload size,
174                // the length prefix is also decreased.
175                //
176                // The tolerance is based on the length prefix encoding size.
177                // For example, if the length prefix requires 2 bytes to encode the length,
178                // the overall `frame.encoding_size()` can be within 2 bytes of `capacity`.
179                let tolerance = VarInt::try_from(new_length).unwrap().encoding_size();
180
181                assert!(
182                    capacity - frame.encoding_size() <= tolerance,
183                    "should fit capacity tolerance: expected {}, got {}; {:#?}",
184                    tolerance,
185                    capacity - frame.encoding_size(),
186                    frame,
187                );
188            }
189        } else {
190            assert!(
191                frame.encoding_size() > capacity,
192                "rejection should only occur when encoding size > capacity {frame:#?}"
193            );
194        }
195    }
196
197    #[test]
198    #[cfg_attr(kani, kani::proof, kani::unwind(1), kani::solver(kissat))]
199    fn try_fit_test() {
200        check!()
201            .with_type()
202            .cloned()
203            .for_each(|(offset, length, capacity)| {
204                model(offset, length, capacity);
205            });
206    }
207}