Skip to content

Commit 6a5f3d2

Browse files
committed
combine_dtls_fragments helper
1 parent 2f32181 commit 6a5f3d2

File tree

7 files changed

+227
-1
lines changed

7 files changed

+227
-1
lines changed

assets/dtls_cert_frag01.bin

161 Bytes
Binary file not shown.

assets/dtls_cert_frag02.bin

256 Bytes
Binary file not shown.

assets/dtls_cert_frag03.bin

256 Bytes
Binary file not shown.

assets/dtls_cert_frag04.bin

104 Bytes
Binary file not shown.

src/dtls.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ pub struct DTLSMessageHandshake<'a> {
9595
pub body: DTLSMessageHandshakeBody<'a>,
9696
}
9797

98+
impl<'a> DTLSMessageHandshake<'a> {
99+
pub fn is_fragment(&self) -> bool {
100+
matches!(self.body, DTLSMessageHandshakeBody::Fragment(_))
101+
}
102+
}
103+
98104
/// DTLS Generic handshake message
99105
#[derive(Debug, PartialEq)]
100106
pub enum DTLSMessageHandshakeBody<'a> {
@@ -133,7 +139,7 @@ impl<'a> DTLSMessage<'a> {
133139
/// fragments to be a complete message.
134140
pub fn is_fragment(&self) -> bool {
135141
match self {
136-
DTLSMessage::Handshake(h) => matches!(h.body, DTLSMessageHandshakeBody::Fragment(_)),
142+
DTLSMessage::Handshake(h) => h.is_fragment(),
137143
_ => false,
138144
}
139145
}

src/dtls_combine.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
use core::array;
2+
3+
use nom::error::{make_error, ErrorKind};
4+
use nom::{Err, IResult};
5+
6+
use crate::{
7+
parse_dtls_message_handshake, DTLSMessage, DTLSMessageHandshake, DTLSMessageHandshakeBody,
8+
};
9+
10+
const MAX_FRAGMENTS: usize = 50;
11+
12+
/// Combine the given fragments into one. Returns true if the fragments made a complete output.
13+
/// The fragments are combined in such a way that the output constitutes a complete DTLSMessage.
14+
///
15+
/// Returns `None` if the fragments are not complete.
16+
///
17+
/// Errors if:
18+
///
19+
/// 1. The output is not big enough to hold the reconstituted messages
20+
/// 2. Fragments are not of the same type (for example ClientHello mixed with Certificate)
21+
/// 3. (Total) length field differs between the fragments
22+
/// 4. Fragment offset/length are not consistent with total length
23+
/// 5. The DTLSMessageHandshakeBody in the message is not a Fragment
24+
/// 6. The message_seq differs between the fragments.
25+
///
26+
/// Panics if there are more than 50 fragments.
27+
pub fn combine_dtls_fragments<'a>(
28+
fragments: &[DTLSMessageHandshake],
29+
out: &'a mut [u8],
30+
) -> IResult<&'a [u8], Option<DTLSMessage<'a>>> {
31+
if fragments.is_empty() {
32+
return Ok((&[], None));
33+
}
34+
35+
if fragments.len() > MAX_FRAGMENTS {
36+
panic!("More than max fragments");
37+
}
38+
39+
const MESSAGE_HEADER_OFFSET: usize = 12;
40+
41+
// The header all of the fragments share the same DTLSMessage start, apart from the
42+
// fragment information. This goes into the first 12 bytes.
43+
if out.len() < MESSAGE_HEADER_OFFSET {
44+
// Error case 1
45+
return Err(Err::Error(make_error(&*out, ErrorKind::Fail)));
46+
}
47+
48+
// Helper to iterate the fragments in order.
49+
let ordered = Ordered::new(fragments);
50+
51+
// Investigate each fragment_offset + fragment_length to figure out
52+
// the max contiguous range over the fragments.
53+
let max = ordered.max_contiguous();
54+
55+
// Unwrap is OK, because we have at least one item (checked above).
56+
let first_handshake = ordered.iter().next().unwrap();
57+
58+
if first_handshake.fragment_offset != 0 {
59+
// The first fragment must start at 0, or we might have
60+
// missing packets or arriving out of order.
61+
return Ok((&[], None));
62+
}
63+
64+
let msg_type = first_handshake.msg_type;
65+
let message_seq = first_handshake.message_seq;
66+
let length = first_handshake.length;
67+
68+
if max > length {
69+
// Error case 4
70+
return Err(Err::Error(make_error(&*out, ErrorKind::Fail)));
71+
} else if max < length {
72+
// We do not have all fragments yet
73+
return Ok((&[], None));
74+
}
75+
76+
// Write the header into output.
77+
{
78+
out[0] = msg_type.into(); // The type.
79+
(&mut out[1..4]).copy_from_slice(&length.to_be_bytes()[1..]); // 24 bit length
80+
(&mut out[4..6]).copy_from_slice(&message_seq.to_be_bytes()); // 16 bit message sequence
81+
(&mut out[6..9]).copy_from_slice(&[0, 0, 0]); // 24 bit fragment_offset, which is 0 for the entire message.
82+
(&mut out[9..12]).copy_from_slice(&length.to_be_bytes()[1..]); // 24 bit fragment_length, which is entire length.
83+
}
84+
85+
let data = &mut out[MESSAGE_HEADER_OFFSET..];
86+
87+
if data.len() < length as usize {
88+
// Error case 1
89+
return Err(Err::Error(make_error(&*out, ErrorKind::Fail)));
90+
}
91+
92+
// Loop the fragments, in order and output the data.
93+
for handshake in ordered.iter() {
94+
if msg_type != handshake.msg_type {
95+
// Error case 2
96+
return Err(Err::Error(make_error(&*out, ErrorKind::Fail)));
97+
}
98+
99+
if handshake.length != length {
100+
// Error case 3
101+
return Err(Err::Error(make_error(&*out, ErrorKind::Fail)));
102+
}
103+
104+
if handshake.message_seq != message_seq {
105+
// Error case 6
106+
return Err(Err::Error(make_error(&*out, ErrorKind::Fail)));
107+
}
108+
109+
let from = handshake.fragment_offset as usize;
110+
let to = from + handshake.fragment_length as usize;
111+
112+
let body = match &handshake.body {
113+
DTLSMessageHandshakeBody::Fragment(v) => v,
114+
_ => {
115+
// Error case 5
116+
return Err(Err::Error(make_error(&*out, ErrorKind::Fail)));
117+
}
118+
};
119+
120+
// Copy into output.
121+
(&mut data[from..to]).copy_from_slice(&body[..]);
122+
}
123+
124+
// This parse should succeed now and produce a complete message.
125+
let (rest, message) = parse_dtls_message_handshake(out)?;
126+
127+
Ok((rest, Some(message)))
128+
}
129+
130+
struct Ordered<'a, 'b>([usize; MAX_FRAGMENTS], &'a [DTLSMessageHandshake<'b>]);
131+
132+
impl<'a, 'b> Ordered<'a, 'b> {
133+
fn new(fragments: &'a [DTLSMessageHandshake<'b>]) -> Self {
134+
// Indexes that will point into handshakes
135+
let mut order: [usize; MAX_FRAGMENTS] = array::from_fn(|i| i);
136+
137+
// Sort the index for fragment_offset starts.
138+
order.sort_by_key(|idx| {
139+
fragments
140+
.get(*idx)
141+
.map(|h| h.fragment_offset)
142+
// Somewhere outside the fragments length.
143+
.unwrap_or(*idx as u32 + 1000)
144+
});
145+
146+
Self(order, fragments)
147+
}
148+
149+
fn iter(&self) -> impl Iterator<Item = &'a DTLSMessageHandshake<'b>> + '_ {
150+
let len = self.1.len();
151+
self.0
152+
.iter()
153+
.take_while(move |idx| **idx < len)
154+
.map(move |idx| &self.1[*idx])
155+
}
156+
157+
// Find the max contiguous fragment_offset/fragment_length.
158+
fn max_contiguous(&self) -> u32 {
159+
let mut max = 0;
160+
161+
for h in self.iter() {
162+
// DTLS fragments can overlap, which means the offset might not start at the previous end.
163+
if h.fragment_offset <= max {
164+
let start = h.fragment_offset;
165+
max = start + h.fragment_length;
166+
} else {
167+
// Not contiguous.
168+
return 0;
169+
}
170+
}
171+
172+
max
173+
}
174+
}
175+
176+
#[cfg(test)]
177+
mod test {
178+
use crate::parse_dtls_plaintext_record;
179+
180+
use super::*;
181+
182+
#[test]
183+
fn read_dtls_certifiate_fragments() {
184+
// These are complete packets dumped with wireshark.
185+
const DTLS_CERT01: &[u8] = include_bytes!("../assets/dtls_cert_frag01.bin");
186+
const DTLS_CERT02: &[u8] = include_bytes!("../assets/dtls_cert_frag02.bin");
187+
const DTLS_CERT03: &[u8] = include_bytes!("../assets/dtls_cert_frag03.bin");
188+
const DTLS_CERT04: &[u8] = include_bytes!("../assets/dtls_cert_frag04.bin");
189+
190+
let mut fragments = vec![];
191+
192+
for c in &[DTLS_CERT01, DTLS_CERT02, DTLS_CERT03, DTLS_CERT04] {
193+
let (_, record) = parse_dtls_plaintext_record(c).expect("parsing failed");
194+
195+
for message in record.messages {
196+
// All of these should be fragments.
197+
assert!(message.is_fragment());
198+
199+
let handshake = match message {
200+
DTLSMessage::Handshake(v) => v,
201+
_ => panic!("Expected Handshake"),
202+
};
203+
204+
assert!(handshake.is_fragment());
205+
fragments.push(handshake);
206+
}
207+
}
208+
209+
// Temporary output to combine the fragments into.
210+
let mut out = vec![0_u8; 4192];
211+
let (_, message) = combine_dtls_fragments(&fragments, &mut out).expect("combine fragments");
212+
213+
// This optional should hold Some(DTLSMessage) indicating a complete parse.
214+
let message = message.expect("Combined fragments");
215+
216+
println!("{:02x?}", message);
217+
}
218+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ extern crate alloc;
142142

143143
mod certificate_transparency;
144144
mod dtls;
145+
mod dtls_combine;
145146
mod tls;
146147
mod tls_alert;
147148
mod tls_ciphers;
@@ -154,6 +155,7 @@ mod tls_states;
154155

155156
pub use certificate_transparency::*;
156157
pub use dtls::*;
158+
pub use dtls_combine::*;
157159
pub use tls::*;
158160
pub use tls_alert::*;
159161
pub use tls_ciphers::*;

0 commit comments

Comments
 (0)