Skip to main content

brynja_protocol/tls/
dtls12_ciphertext.rs

1//! DTLS 1.2 protected record framing.
2
3use brynja_core::{ProtocolVersion, ReadCursor, WriteCursor};
4
5use super::{
6    ContentType, ContentTypeCode, LegacyRecordVersion, MAX_TLS12_CIPHERTEXT_LENGTH, RecordError,
7    WirePolicy,
8};
9
10const HEADER_LENGTH: usize = 13;
11
12/// One borrowed protected DTLS 1.2 record.
13#[derive(Clone, Copy, Eq, PartialEq)]
14pub struct Dtls12Ciphertext<'input> {
15    content_type: ContentType,
16    legacy_record_version: LegacyRecordVersion,
17    epoch: u16,
18    sequence_number: [u8; 6],
19    fragment: &'input [u8],
20}
21
22impl<'input> Dtls12Ciphertext<'input> {
23    /// Constructs a checked protected DTLS 1.2 envelope for encoding.
24    pub fn new(
25        policy: WirePolicy,
26        content_type: ContentTypeCode,
27        legacy_record_version: LegacyRecordVersion,
28        epoch: u16,
29        sequence_number: [u8; 6],
30        fragment: &'input [u8],
31    ) -> Result<Self, RecordError> {
32        if !matches!(policy.version(), ProtocolVersion::Dtls12) {
33            return Err(RecordError::ProfileMismatch);
34        }
35        let content_type = policy.admit_ciphertext(content_type)?;
36        validate_length(fragment.len())?;
37        Ok(Self {
38            content_type,
39            legacy_record_version,
40            epoch,
41            sequence_number,
42            fragment,
43        })
44    }
45
46    /// Parses one protected DTLS 1.2 record and returns the datagram suffix.
47    pub fn parse(
48        policy: WirePolicy,
49        input: &'input [u8],
50    ) -> Result<(Self, &'input [u8]), RecordError> {
51        if !matches!(policy.version(), ProtocolVersion::Dtls12) {
52            return Err(RecordError::ProfileMismatch);
53        }
54        let mut cursor = ReadCursor::new(input);
55        let code = read_byte(&mut cursor)?;
56        let content_type = policy.admit_ciphertext(ContentTypeCode::classify(code))?;
57        let version = read_version(&mut cursor)?;
58        let epoch = read_u16(&mut cursor)?;
59        let sequence_number = *cursor
60            .take_array::<6>()
61            .map_err(|_| RecordError::Truncated)?;
62        let length = usize::from(read_u16(&mut cursor)?);
63        validate_length(length)?;
64        let fragment = cursor.take(length).map_err(|_| RecordError::Truncated)?;
65        let remaining = cursor.remaining();
66        Ok((
67            Self {
68                content_type,
69                legacy_record_version: version,
70                epoch,
71                sequence_number,
72                fragment,
73            },
74            remaining,
75        ))
76    }
77
78    /// Returns the admitted outer content type.
79    #[must_use]
80    pub const fn content_type(&self) -> ContentType {
81        self.content_type
82    }
83
84    /// Returns the preserved record-version bytes.
85    #[must_use]
86    pub const fn legacy_record_version(&self) -> LegacyRecordVersion {
87        self.legacy_record_version
88    }
89
90    /// Returns the exact encoded epoch.
91    #[must_use]
92    pub const fn epoch(&self) -> u16 {
93        self.epoch
94    }
95
96    /// Returns the exact encoded 48-bit sequence number.
97    #[must_use]
98    pub const fn sequence_number(&self) -> [u8; 6] {
99        self.sequence_number
100    }
101
102    /// Returns the exact borrowed protected fragment.
103    #[must_use]
104    pub const fn fragment(&self) -> &'input [u8] {
105        self.fragment
106    }
107
108    /// Returns the complete encoded length.
109    #[must_use]
110    pub const fn encoded_len(&self) -> usize {
111        HEADER_LENGTH.saturating_add(self.fragment.len())
112    }
113
114    /// Writes the complete parsed record transactionally.
115    pub fn encode(&self, output: &mut [u8]) -> Result<usize, RecordError> {
116        let total = HEADER_LENGTH
117            .checked_add(self.fragment.len())
118            .ok_or(RecordError::LengthOverflow)?;
119        if output.len() < total {
120            return Err(RecordError::InsufficientOutput);
121        }
122        let length = u16::try_from(self.fragment.len())
123            .map_err(|_| RecordError::RecordOverflow)?
124            .to_be_bytes();
125        let content_type = [self.content_type.code()];
126        let version = self.legacy_record_version.bytes();
127        let epoch = self.epoch.to_be_bytes();
128        let mut cursor = WriteCursor::new(output);
129        cursor
130            .write_parts(&[
131                &content_type,
132                &version,
133                &epoch,
134                &self.sequence_number,
135                &length,
136                self.fragment,
137            ])
138            .map_err(|_| RecordError::InsufficientOutput)?;
139        Ok(total)
140    }
141}
142
143fn read_byte(cursor: &mut ReadCursor<'_>) -> Result<u8, RecordError> {
144    cursor
145        .take(1)
146        .map_err(|_| RecordError::Truncated)?
147        .first()
148        .copied()
149        .ok_or(RecordError::Truncated)
150}
151
152fn read_u16(cursor: &mut ReadCursor<'_>) -> Result<u16, RecordError> {
153    let bytes = cursor
154        .take_array::<2>()
155        .map_err(|_| RecordError::Truncated)?;
156    Ok(u16::from_be_bytes(*bytes))
157}
158
159fn read_version(cursor: &mut ReadCursor<'_>) -> Result<LegacyRecordVersion, RecordError> {
160    let bytes = cursor
161        .take_array::<2>()
162        .map_err(|_| RecordError::Truncated)?;
163    Ok(LegacyRecordVersion::from_bytes(*bytes))
164}
165
166fn validate_length(length: usize) -> Result<(), RecordError> {
167    if length > MAX_TLS12_CIPHERTEXT_LENGTH {
168        Err(RecordError::RecordOverflow)
169    } else {
170        Ok(())
171    }
172}