1use brynja_core::{ProtocolFamily, ProtocolVersion, ReadCursor, WriteCursor};
4
5use super::{
6 ContentType, ContentTypeCode, LegacyRecordVersion, MAX_PLAINTEXT_LENGTH,
7 MAX_TLS13_CIPHERTEXT_LENGTH, RecordError, WirePolicy,
8};
9
10const PLAINTEXT_HEADER_LENGTH: usize = 13;
11const UNIFIED_FIXED_BITS: u8 = 0x20;
12const UNIFIED_FIXED_MASK: u8 = 0xe0;
13const CID_BIT: u8 = 0x10;
14const LONG_SEQUENCE_BIT: u8 = 0x08;
15const LENGTH_BIT: u8 = 0x04;
16
17#[derive(Clone, Copy, Eq, PartialEq)]
19pub struct DtlsPlaintext<'input> {
20 content_type: ContentType,
21 legacy_record_version: LegacyRecordVersion,
22 epoch: u16,
23 sequence_number: [u8; 6],
24 fragment: &'input [u8],
25}
26
27#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
29pub struct Dtls13CiphertextConfig {
30 connection_id_length: u8,
31}
32
33#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
35pub enum Dtls13Sequence {
36 Short(u8),
38 Long(u16),
40}
41
42#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
44pub struct Dtls13CiphertextHeader<'cid> {
45 epoch_bits: u8,
46 connection_id: &'cid [u8],
47 sequence: Dtls13Sequence,
48 length_present: bool,
49}
50
51#[derive(Clone, Copy, Eq, PartialEq)]
53pub struct Dtls13Ciphertext<'input> {
54 unified_header: &'input [u8],
55 connection_id: &'input [u8],
56 sequence: Dtls13Sequence,
57 epoch_bits: u8,
58 length_present: bool,
59 encrypted_record: &'input [u8],
60}
61
62impl<'input> DtlsPlaintext<'input> {
63 pub fn new(
65 policy: WirePolicy,
66 content_type: ContentTypeCode,
67 legacy_record_version: LegacyRecordVersion,
68 epoch: u16,
69 sequence_number: [u8; 6],
70 fragment: &'input [u8],
71 ) -> Result<Self, RecordError> {
72 require_dtls(policy)?;
73 let content_type = policy.admit_plaintext(content_type)?;
74 if matches!(policy.version(), ProtocolVersion::Dtls13) && epoch != 0 {
75 return Err(RecordError::InvalidPlaintextEpoch);
76 }
77 if matches!(policy.version(), ProtocolVersion::Dtls13)
78 && !matches!(legacy_record_version.bytes(), [254, 253] | [254, 255])
79 {
80 return Err(RecordError::InvalidPlaintextVersion);
81 }
82 validate_plaintext_length(content_type, fragment.len())?;
83 Ok(Self {
84 content_type,
85 legacy_record_version,
86 epoch,
87 sequence_number,
88 fragment,
89 })
90 }
91
92 pub fn parse(
94 policy: WirePolicy,
95 input: &'input [u8],
96 ) -> Result<(Self, &'input [u8]), RecordError> {
97 require_dtls(policy)?;
98 let mut cursor = ReadCursor::new(input);
99 let code = read_byte(&mut cursor)?;
100 let content_type = policy.admit_plaintext(ContentTypeCode::classify(code))?;
101 let version = read_version(&mut cursor)?;
102 let epoch = read_u16(&mut cursor)?;
103 if matches!(policy.version(), ProtocolVersion::Dtls13) && epoch != 0 {
104 return Err(RecordError::InvalidPlaintextEpoch);
105 }
106 let sequence_number = *cursor
107 .take_array::<6>()
108 .map_err(|_| RecordError::Truncated)?;
109 let length = usize::from(read_u16(&mut cursor)?);
110 validate_plaintext_length(content_type, length)?;
111 let fragment = cursor.take(length).map_err(|_| RecordError::Truncated)?;
112 let remaining = cursor.remaining();
113 Ok((
114 Self {
115 content_type,
116 legacy_record_version: version,
117 epoch,
118 sequence_number,
119 fragment,
120 },
121 remaining,
122 ))
123 }
124
125 #[must_use]
127 pub const fn content_type(&self) -> ContentType {
128 self.content_type
129 }
130
131 #[must_use]
133 pub const fn legacy_record_version(&self) -> LegacyRecordVersion {
134 self.legacy_record_version
135 }
136
137 #[must_use]
139 pub const fn epoch(&self) -> u16 {
140 self.epoch
141 }
142
143 #[must_use]
145 pub const fn sequence_number(&self) -> [u8; 6] {
146 self.sequence_number
147 }
148
149 #[must_use]
151 pub const fn fragment(&self) -> &'input [u8] {
152 self.fragment
153 }
154
155 pub fn encode(&self, output: &mut [u8]) -> Result<usize, RecordError> {
157 let total = PLAINTEXT_HEADER_LENGTH
158 .checked_add(self.fragment.len())
159 .ok_or(RecordError::LengthOverflow)?;
160 if output.len() < total {
161 return Err(RecordError::InsufficientOutput);
162 }
163 let length = u16::try_from(self.fragment.len())
164 .map_err(|_| RecordError::RecordOverflow)?
165 .to_be_bytes();
166 let type_bytes = [self.content_type.code()];
167 let version = self.legacy_record_version.bytes();
168 let epoch = self.epoch.to_be_bytes();
169 let mut cursor = WriteCursor::new(output);
170 cursor
171 .write_parts(&[
172 &type_bytes,
173 &version,
174 &epoch,
175 &self.sequence_number,
176 &length,
177 self.fragment,
178 ])
179 .map_err(|_| RecordError::InsufficientOutput)?;
180 Ok(total)
181 }
182}
183
184impl Dtls13CiphertextConfig {
185 pub fn new(connection_id_length: usize) -> Result<Self, RecordError> {
187 let connection_id_length =
188 u8::try_from(connection_id_length).map_err(|_| RecordError::ConnectionIdTooLong)?;
189 Ok(Self {
190 connection_id_length,
191 })
192 }
193
194 #[must_use]
196 pub const fn connection_id_length(self) -> usize {
197 self.connection_id_length as usize
198 }
199}
200
201impl<'cid> Dtls13CiphertextHeader<'cid> {
202 pub fn new(
204 epoch_bits: u8,
205 connection_id: &'cid [u8],
206 sequence: Dtls13Sequence,
207 length_present: bool,
208 ) -> Result<Self, RecordError> {
209 if epoch_bits > 3 {
210 return Err(RecordError::InvalidUnifiedHeader);
211 }
212 let _ = u8::try_from(connection_id.len()).map_err(|_| RecordError::ConnectionIdTooLong)?;
213 Ok(Self {
214 epoch_bits,
215 connection_id,
216 sequence,
217 length_present,
218 })
219 }
220
221 #[must_use]
223 pub const fn epoch_bits(self) -> u8 {
224 self.epoch_bits
225 }
226
227 #[must_use]
229 pub const fn connection_id(self) -> &'cid [u8] {
230 self.connection_id
231 }
232
233 #[must_use]
235 pub const fn sequence(self) -> Dtls13Sequence {
236 self.sequence
237 }
238
239 #[must_use]
241 pub const fn length_present(self) -> bool {
242 self.length_present
243 }
244}
245
246impl<'input> Dtls13Ciphertext<'input> {
247 pub fn parse(
251 policy: WirePolicy,
252 config: Dtls13CiphertextConfig,
253 input: &'input [u8],
254 ) -> Result<(Self, &'input [u8]), RecordError> {
255 if !matches!(policy.version(), ProtocolVersion::Dtls13) {
256 return Err(RecordError::ProfileMismatch);
257 }
258 let mut cursor = ReadCursor::new(input);
259 let first = read_byte(&mut cursor)?;
260 if first & UNIFIED_FIXED_MASK != UNIFIED_FIXED_BITS {
261 return Err(RecordError::InvalidUnifiedHeader);
262 }
263 let cid_present = first & CID_BIT != 0;
264 if cid_present != (config.connection_id_length != 0) {
265 return Err(RecordError::ConnectionIdMismatch);
266 }
267 let connection_id = cursor
268 .take(config.connection_id_length())
269 .map_err(|_| RecordError::Truncated)?;
270 let sequence = if first & LONG_SEQUENCE_BIT == 0 {
271 Dtls13Sequence::Short(read_byte(&mut cursor)?)
272 } else {
273 Dtls13Sequence::Long(read_u16(&mut cursor)?)
274 };
275 let length_present = first & LENGTH_BIT != 0;
276 let encrypted_length = if length_present {
277 usize::from(read_u16(&mut cursor)?)
278 } else {
279 cursor.remaining_len()
280 };
281 validate_ciphertext_length(encrypted_length)?;
282 let encrypted_record = cursor
283 .take(encrypted_length)
284 .map_err(|_| RecordError::Truncated)?;
285 let remaining = cursor.remaining();
286 let header_length = input
287 .len()
288 .checked_sub(encrypted_record.len())
289 .and_then(|length| length.checked_sub(remaining.len()))
290 .ok_or(RecordError::LengthOverflow)?;
291 let unified_header = input.get(..header_length).ok_or(RecordError::Truncated)?;
292 Ok((
293 Self {
294 unified_header,
295 connection_id,
296 sequence,
297 epoch_bits: first & 3,
298 length_present,
299 encrypted_record,
300 },
301 remaining,
302 ))
303 }
304
305 #[must_use]
307 pub const fn unified_header(&self) -> &'input [u8] {
308 self.unified_header
309 }
310
311 #[must_use]
313 pub const fn connection_id(&self) -> &'input [u8] {
314 self.connection_id
315 }
316
317 #[must_use]
319 pub const fn sequence(&self) -> Dtls13Sequence {
320 self.sequence
321 }
322
323 #[must_use]
325 pub const fn epoch_bits(&self) -> u8 {
326 self.epoch_bits
327 }
328
329 #[must_use]
331 pub const fn length_present(&self) -> bool {
332 self.length_present
333 }
334
335 #[must_use]
337 pub const fn encrypted_record(&self) -> &'input [u8] {
338 self.encrypted_record
339 }
340
341 pub fn encode(&self, output: &mut [u8]) -> Result<usize, RecordError> {
343 let total = self
344 .unified_header
345 .len()
346 .checked_add(self.encrypted_record.len())
347 .ok_or(RecordError::LengthOverflow)?;
348 if output.len() < total {
349 return Err(RecordError::InsufficientOutput);
350 }
351 let mut cursor = WriteCursor::new(output);
352 cursor
353 .write_parts(&[self.unified_header, self.encrypted_record])
354 .map_err(|_| RecordError::InsufficientOutput)?;
355 Ok(total)
356 }
357}
358
359pub fn encode_dtls13_ciphertext(
361 header: Dtls13CiphertextHeader<'_>,
362 encrypted_record: &[u8],
363 output: &mut [u8],
364) -> Result<usize, RecordError> {
365 validate_ciphertext_length(encrypted_record.len())?;
366 let sequence_length = match header.sequence {
367 Dtls13Sequence::Short(_) => 1_usize,
368 Dtls13Sequence::Long(_) => 2_usize,
369 };
370 let length_length = if header.length_present {
371 2_usize
372 } else {
373 0_usize
374 };
375 let total = 1_usize
376 .checked_add(header.connection_id.len())
377 .and_then(|value| value.checked_add(sequence_length))
378 .and_then(|value| value.checked_add(length_length))
379 .and_then(|value| value.checked_add(encrypted_record.len()))
380 .ok_or(RecordError::LengthOverflow)?;
381 if output.len() < total {
382 return Err(RecordError::InsufficientOutput);
383 }
384 let mut first = UNIFIED_FIXED_BITS | header.epoch_bits;
385 if !header.connection_id.is_empty() {
386 first |= CID_BIT;
387 }
388 if matches!(header.sequence, Dtls13Sequence::Long(_)) {
389 first |= LONG_SEQUENCE_BIT;
390 }
391 if header.length_present {
392 first |= LENGTH_BIT;
393 }
394 let first_bytes = [first];
395 let sequence_bytes = match header.sequence {
396 Dtls13Sequence::Short(value) => [0, value],
397 Dtls13Sequence::Long(value) => value.to_be_bytes(),
398 };
399 let sequence = if matches!(header.sequence, Dtls13Sequence::Short(_)) {
400 sequence_bytes.get(1..).ok_or(RecordError::LengthOverflow)?
401 } else {
402 sequence_bytes.as_slice()
403 };
404 let length_value = u16::try_from(encrypted_record.len())
405 .map_err(|_| RecordError::RecordOverflow)?
406 .to_be_bytes();
407 let length = if header.length_present {
408 length_value.as_slice()
409 } else {
410 &[]
411 };
412 let mut cursor = WriteCursor::new(output);
413 cursor
414 .write_parts(&[
415 &first_bytes,
416 header.connection_id,
417 sequence,
418 length,
419 encrypted_record,
420 ])
421 .map_err(|_| RecordError::InsufficientOutput)?;
422 Ok(total)
423}
424
425fn require_dtls(policy: WirePolicy) -> Result<(), RecordError> {
426 if matches!(policy.version().family(), ProtocolFamily::Dtls) {
427 Ok(())
428 } else {
429 Err(RecordError::ProfileMismatch)
430 }
431}
432
433fn read_byte(cursor: &mut ReadCursor<'_>) -> Result<u8, RecordError> {
434 cursor
435 .take(1)
436 .map_err(|_| RecordError::Truncated)?
437 .first()
438 .copied()
439 .ok_or(RecordError::Truncated)
440}
441
442fn read_u16(cursor: &mut ReadCursor<'_>) -> Result<u16, RecordError> {
443 let bytes = cursor
444 .take_array::<2>()
445 .map_err(|_| RecordError::Truncated)?;
446 Ok(u16::from_be_bytes(*bytes))
447}
448
449fn read_version(cursor: &mut ReadCursor<'_>) -> Result<LegacyRecordVersion, RecordError> {
450 let bytes = cursor
451 .take_array::<2>()
452 .map_err(|_| RecordError::Truncated)?;
453 Ok(LegacyRecordVersion::from_bytes(*bytes))
454}
455
456fn validate_plaintext_length(content_type: ContentType, length: usize) -> Result<(), RecordError> {
457 if length > MAX_PLAINTEXT_LENGTH {
458 return Err(RecordError::RecordOverflow);
459 }
460 if length == 0 && !matches!(content_type, ContentType::ApplicationData) {
461 return Err(RecordError::EmptyFragment);
462 }
463 Ok(())
464}
465
466fn validate_ciphertext_length(length: usize) -> Result<(), RecordError> {
467 if length > MAX_TLS13_CIPHERTEXT_LENGTH {
468 Err(RecordError::RecordOverflow)
469 } else if length == 0 {
470 Err(RecordError::EmptyFragment)
471 } else {
472 Ok(())
473 }
474}