1use brynja_core::{ProtocolFamily, ProtocolVersion, ReadCursor, WriteCursor};
4
5use super::{
6 ContentType, ContentTypeCode, MAX_PLAINTEXT_LENGTH, MAX_TLS12_CIPHERTEXT_LENGTH,
7 MAX_TLS13_CIPHERTEXT_LENGTH, RecordError, WirePolicy,
8};
9
10const HEADER_LENGTH: usize = 5;
11
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
17pub struct LegacyRecordVersion([u8; 2]);
18
19#[derive(Clone, Copy, Eq, PartialEq)]
32pub struct TlsPlaintext<'input> {
33 content_type: ContentType,
34 legacy_record_version: LegacyRecordVersion,
35 fragment: &'input [u8],
36}
37
38#[derive(Clone, Copy, Eq, PartialEq)]
40pub struct TlsCiphertext<'input> {
41 content_type: ContentType,
42 legacy_record_version: LegacyRecordVersion,
43 fragment: &'input [u8],
44}
45
46impl LegacyRecordVersion {
47 #[must_use]
49 pub const fn from_bytes(bytes: [u8; 2]) -> Self {
50 Self(bytes)
51 }
52
53 #[must_use]
55 pub const fn bytes(self) -> [u8; 2] {
56 self.0
57 }
58
59 #[must_use]
61 pub const fn tls13_default() -> Self {
62 Self([3, 3])
63 }
64
65 #[must_use]
67 pub const fn tls13_initial_client_hello() -> Self {
68 Self([3, 1])
69 }
70
71 #[must_use]
73 pub const fn dtls13_default() -> Self {
74 Self([254, 253])
75 }
76
77 #[must_use]
79 pub const fn dtls13_initial_client_hello() -> Self {
80 Self([254, 255])
81 }
82}
83
84impl<'input> TlsPlaintext<'input> {
85 pub fn parse(
90 policy: WirePolicy,
91 input: &'input [u8],
92 ) -> Result<(Self, &'input [u8]), RecordError> {
93 require_tls(policy)?;
94 let mut cursor = ReadCursor::new(input);
95 let content_type = read_content_type(&mut cursor, policy, false)?;
96 let version = read_version(&mut cursor)?;
97 let length = read_u16(&mut cursor)?;
98 validate_plaintext_length(content_type, length)?;
99 let fragment = take(&mut cursor, length)?;
100 let remaining = cursor.remaining();
101 Ok((
102 Self {
103 content_type,
104 legacy_record_version: version,
105 fragment,
106 },
107 remaining,
108 ))
109 }
110
111 pub fn new(
113 policy: WirePolicy,
114 content_type: ContentTypeCode,
115 legacy_record_version: LegacyRecordVersion,
116 fragment: &'input [u8],
117 ) -> Result<Self, RecordError> {
118 require_tls(policy)?;
119 let content_type = policy.admit_plaintext(content_type)?;
120 validate_plaintext_length(content_type, fragment.len())?;
121 if matches!(policy.version(), ProtocolVersion::Tls13)
122 && !matches!(legacy_record_version.bytes(), [3, 3] | [3, 1])
123 {
124 return Err(RecordError::InvalidPlaintextVersion);
125 }
126 Ok(Self {
127 content_type,
128 legacy_record_version,
129 fragment,
130 })
131 }
132
133 #[must_use]
135 pub const fn content_type(&self) -> ContentType {
136 self.content_type
137 }
138
139 #[must_use]
141 pub const fn legacy_record_version(&self) -> LegacyRecordVersion {
142 self.legacy_record_version
143 }
144
145 #[must_use]
147 pub const fn fragment(&self) -> &'input [u8] {
148 self.fragment
149 }
150
151 #[must_use]
153 pub const fn encoded_len(&self) -> usize {
154 HEADER_LENGTH.saturating_add(self.fragment.len())
155 }
156
157 pub fn encode(&self, output: &mut [u8]) -> Result<usize, RecordError> {
159 encode_record(
160 self.content_type,
161 self.legacy_record_version,
162 self.fragment,
163 output,
164 )
165 }
166}
167
168impl<'input> TlsCiphertext<'input> {
169 pub fn parse(
171 policy: WirePolicy,
172 input: &'input [u8],
173 ) -> Result<(Self, &'input [u8]), RecordError> {
174 require_tls(policy)?;
175 let mut cursor = ReadCursor::new(input);
176 let content_type = read_content_type(&mut cursor, policy, true)?;
177 let version = read_version(&mut cursor)?;
178 validate_ciphertext_version(policy, version)?;
179 let length = read_u16(&mut cursor)?;
180 validate_ciphertext_length(policy, length)?;
181 let fragment = take(&mut cursor, length)?;
182 let remaining = cursor.remaining();
183 Ok((
184 Self {
185 content_type,
186 legacy_record_version: version,
187 fragment,
188 },
189 remaining,
190 ))
191 }
192
193 pub fn new(
195 policy: WirePolicy,
196 content_type: ContentTypeCode,
197 legacy_record_version: LegacyRecordVersion,
198 fragment: &'input [u8],
199 ) -> Result<Self, RecordError> {
200 require_tls(policy)?;
201 let content_type = policy.admit_ciphertext(content_type)?;
202 validate_ciphertext_version(policy, legacy_record_version)?;
203 validate_ciphertext_length(policy, fragment.len())?;
204 Ok(Self {
205 content_type,
206 legacy_record_version,
207 fragment,
208 })
209 }
210
211 #[must_use]
213 pub const fn content_type(&self) -> ContentType {
214 self.content_type
215 }
216
217 #[must_use]
219 pub const fn legacy_record_version(&self) -> LegacyRecordVersion {
220 self.legacy_record_version
221 }
222
223 #[must_use]
225 pub const fn fragment(&self) -> &'input [u8] {
226 self.fragment
227 }
228
229 #[must_use]
231 pub const fn encoded_len(&self) -> usize {
232 HEADER_LENGTH.saturating_add(self.fragment.len())
233 }
234
235 pub fn encode(&self, output: &mut [u8]) -> Result<usize, RecordError> {
237 encode_record(
238 self.content_type,
239 self.legacy_record_version,
240 self.fragment,
241 output,
242 )
243 }
244}
245
246fn require_tls(policy: WirePolicy) -> Result<(), RecordError> {
247 if matches!(policy.version().family(), ProtocolFamily::Tls) {
248 Ok(())
249 } else {
250 Err(RecordError::ProfileMismatch)
251 }
252}
253
254fn read_content_type(
255 cursor: &mut ReadCursor<'_>,
256 policy: WirePolicy,
257 ciphertext: bool,
258) -> Result<ContentType, RecordError> {
259 let bytes = take(cursor, 1)?;
260 let code = match bytes.first() {
261 Some(code) => ContentTypeCode::classify(*code),
262 None => return Err(RecordError::Truncated),
263 };
264 if ciphertext {
265 policy.admit_ciphertext(code)
266 } else {
267 policy.admit_plaintext(code)
268 }
269}
270
271fn read_version(cursor: &mut ReadCursor<'_>) -> Result<LegacyRecordVersion, RecordError> {
272 let bytes = cursor
273 .take_array::<2>()
274 .map_err(|_| RecordError::Truncated)?;
275 Ok(LegacyRecordVersion::from_bytes(*bytes))
276}
277
278fn read_u16(cursor: &mut ReadCursor<'_>) -> Result<usize, RecordError> {
279 let bytes = cursor
280 .take_array::<2>()
281 .map_err(|_| RecordError::Truncated)?;
282 Ok(usize::from(u16::from_be_bytes(*bytes)))
283}
284
285fn take<'input>(
286 cursor: &mut ReadCursor<'input>,
287 length: usize,
288) -> Result<&'input [u8], RecordError> {
289 cursor.take(length).map_err(|_| RecordError::Truncated)
290}
291
292fn validate_plaintext_length(content_type: ContentType, length: usize) -> Result<(), RecordError> {
293 if length > MAX_PLAINTEXT_LENGTH {
294 return Err(RecordError::RecordOverflow);
295 }
296 if length == 0 && !matches!(content_type, ContentType::ApplicationData) {
297 return Err(RecordError::EmptyFragment);
298 }
299 Ok(())
300}
301
302fn validate_ciphertext_version(
303 policy: WirePolicy,
304 version: LegacyRecordVersion,
305) -> Result<(), RecordError> {
306 if matches!(policy.version(), ProtocolVersion::Tls13) && version.bytes() != [3, 3] {
307 Err(RecordError::InvalidCiphertextVersion)
308 } else {
309 Ok(())
310 }
311}
312
313fn validate_ciphertext_length(policy: WirePolicy, length: usize) -> Result<(), RecordError> {
314 let maximum = if matches!(policy.version(), ProtocolVersion::Tls13) {
315 MAX_TLS13_CIPHERTEXT_LENGTH
316 } else {
317 MAX_TLS12_CIPHERTEXT_LENGTH
318 };
319 if length > maximum {
320 Err(RecordError::RecordOverflow)
321 } else if length == 0 && matches!(policy.version(), ProtocolVersion::Tls13) {
322 Err(RecordError::EmptyFragment)
323 } else {
324 Ok(())
325 }
326}
327
328fn encode_record(
329 content_type: ContentType,
330 version: LegacyRecordVersion,
331 fragment: &[u8],
332 output: &mut [u8],
333) -> Result<usize, RecordError> {
334 let total = HEADER_LENGTH
335 .checked_add(fragment.len())
336 .ok_or(RecordError::LengthOverflow)?;
337 if output.len() < total {
338 return Err(RecordError::InsufficientOutput);
339 }
340 let length = u16::try_from(fragment.len()).map_err(|_| RecordError::RecordOverflow)?;
341 let type_bytes = [content_type.code()];
342 let version_bytes = version.bytes();
343 let length_bytes = length.to_be_bytes();
344 let mut cursor = WriteCursor::new(output);
345 cursor
346 .write_parts(&[&type_bytes, &version_bytes, &length_bytes, fragment])
347 .map_err(|_| RecordError::InsufficientOutput)?;
348 Ok(total)
349}