use std::collections::VecDeque;
use std::sync::Arc;
use std::time::{Duration, Instant};
use dimpl::{Config, Dtls, Output};
use crate::common::*;
use crate::ossl_helper::{DtlsCertOptions, DtlsEvent, OsslDtlsCert};
fn run_client_server_with_mtu(mtu: usize) -> (usize, usize) {
let _ = env_logger::try_init();
let client_cert_options = DtlsCertOptions::default();
let client_cert = OsslDtlsCert::new(client_cert_options);
let server_cert_options = DtlsCertOptions::default();
let server_cert = OsslDtlsCert::new(server_cert_options);
let mut server = server_cert
.new_dtls_impl()
.expect("Failed to create DTLS server");
server.set_active(false);
let config = Arc::new(
Config::builder()
.mtu(mtu)
.build()
.expect("Failed to build config"),
);
let client_x509_der = client_cert
.x509
.to_der()
.expect("Failed to get client cert DER");
let client_pkey_der = client_cert
.pkey
.private_key_to_der()
.expect("Failed to get client private key DER");
let now = Instant::now();
let mut client = Dtls::new_12(
config,
dimpl::DtlsCertificate {
certificate: client_x509_der,
private_key: client_pkey_der,
},
now,
);
client.set_active(true);
let mut server_events = VecDeque::new();
let mut client_connected = false;
let mut server_connected = false;
let mut client_to_server_packets: usize = 0;
let mut server_to_client_packets: usize = 0;
let mut max_c2s_len: usize = 0;
let client_test_data = b"Hello from client";
let server_test_data = b"Hello from server";
let mut client_received_data = Vec::new();
let mut server_received_data = Vec::new();
let mut out_buf = vec![0u8; mtu + 512];
for _ in 0..20 {
client.handle_timeout(Instant::now()).unwrap();
let mut continue_polling = true;
while continue_polling {
let output = client.poll_output(&mut out_buf);
match output {
Output::Packet(data) => {
client_to_server_packets += 1;
if data.len() > max_c2s_len {
max_c2s_len = data.len();
}
if let Err(e) = server.handle_receive(data, &mut server_events) {
panic!("Server failed to handle client packet: {:?}", e);
}
}
Output::Connected => {
client_connected = true;
}
Output::PeerCert(_cert) => {
}
Output::KeyingMaterial(_km, _profile) => {
client
.send_application_data(client_test_data)
.expect("Failed to send client data");
}
Output::ApplicationData(data) => {
client_received_data.extend_from_slice(data);
}
Output::Timeout(_) => {
continue_polling = false;
}
_ => {}
}
}
while let Some(event) = server_events.pop_front() {
match event {
DtlsEvent::Connected => {
server_connected = true;
}
DtlsEvent::RemoteFingerprint(_fp) => {}
DtlsEvent::SrtpKeyingMaterial(_km, _profile) => {
server
.handle_input(server_test_data)
.expect("Failed to send server data");
}
DtlsEvent::Data(data) => {
server_received_data.extend_from_slice(&data);
}
}
}
while let Some(datagram) = server.poll_datagram() {
server_to_client_packets += 1;
client
.handle_packet(&datagram)
.expect("Failed to handle server packet");
}
if client_connected
&& server_connected
&& !client_received_data.is_empty()
&& !server_received_data.is_empty()
{
break;
}
}
assert!(client_connected, "Client should be connected");
assert!(server_connected, "Server should be connected");
assert_eq!(server_received_data, client_test_data);
assert_eq!(client_received_data, server_test_data);
assert!(
max_c2s_len <= mtu,
"client->server datagram length {} exceeds MTU {}",
max_c2s_len,
mtu
);
(client_to_server_packets, server_to_client_packets)
}
#[test]
fn dtls12_fragmentation_increases_packet_count() {
let (large_c2s, large_s2c) = run_client_server_with_mtu(1400);
let (small_c2s, small_s2c) = run_client_server_with_mtu(100);
println!(
"packet counts: large(c2s={}, s2c={}), small(c2s={}, s2c={})",
large_c2s, large_s2c, small_c2s, small_s2c
);
assert!(
(3..=8).contains(&large_c2s),
"large MTU client->server packets: {}",
large_c2s
);
assert!(
(4..=20).contains(&small_c2s),
"small MTU client->server packets: {}",
small_c2s
);
assert!(
small_c2s > large_c2s,
"small MTU should produce more client->server packets"
);
assert!(
(3..=10).contains(&large_s2c),
"large MTU server->client packets: {}",
large_s2c
);
assert!(
(5..=20).contains(&small_s2c),
"small MTU server->client packets: {}",
small_s2c
);
assert!(
small_s2c >= large_s2c,
"small MTU should produce at least as many server->client packets"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_fragmentation_dimpl_to_dimpl() {
use dimpl::certificate::generate_self_signed_certificate;
let _ = env_logger::try_init();
let client_cert = generate_self_signed_certificate().expect("gen client cert");
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls12_config_with_mtu(200);
let mut now = Instant::now();
let mut client = Dtls::new_12(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_12(config, server_cert, now);
server.set_active(false);
let mut client_connected = false;
let mut server_connected = false;
let mut max_packet_size = 0usize;
for _ in 0..40 {
client.handle_timeout(now).expect("client timeout");
server.handle_timeout(now).expect("server timeout");
let client_out = drain_outputs(&mut client);
let server_out = drain_outputs(&mut server);
for p in &client_out.packets {
if p.len() > max_packet_size {
max_packet_size = p.len();
}
}
for p in &server_out.packets {
if p.len() > max_packet_size {
max_packet_size = p.len();
}
}
if client_out.connected {
client_connected = true;
}
if server_out.connected {
server_connected = true;
}
deliver_packets(&client_out.packets, &mut server);
deliver_packets(&server_out.packets, &mut client);
if client_connected && server_connected {
break;
}
now += Duration::from_millis(10);
}
assert!(
client_connected,
"Client should connect with small MTU (dimpl-to-dimpl)"
);
assert!(
server_connected,
"Server should connect with small MTU (dimpl-to-dimpl)"
);
assert!(
max_packet_size <= 200,
"All packets should respect 200-byte MTU: max was {}",
max_packet_size
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_fragmented_handshake_with_packet_loss() {
use dimpl::certificate::generate_self_signed_certificate;
let _ = env_logger::try_init();
let client_cert = generate_self_signed_certificate().expect("gen client cert");
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls12_config_with_mtu(200);
let mut now = Instant::now();
let mut client = Dtls::new_12(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_12(config, server_cert, now);
server.set_active(false);
client.handle_timeout(now).expect("client timeout start");
client.handle_timeout(now).expect("client arm flight 1");
let flight1 = collect_packets(&mut client);
assert!(
!flight1.is_empty(),
"Client should emit at least one packet for flight 1"
);
deliver_packets(&flight1[1..], &mut server);
trigger_timeout(&mut client, &mut now);
let retransmit1 = collect_packets(&mut client);
assert!(!retransmit1.is_empty(), "Client should retransmit flight 1");
deliver_packets(&retransmit1, &mut server);
let mut client_connected = false;
let mut server_connected = false;
for _ in 0..40 {
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
if server_out.connected {
server_connected = true;
}
deliver_packets(&server_out.packets, &mut client);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
if client_out.connected {
client_connected = true;
}
deliver_packets(&client_out.packets, &mut server);
if client_connected && server_connected {
break;
}
now += Duration::from_millis(10);
}
assert!(
client_connected,
"Client should connect after retransmit of dropped fragment"
);
assert!(
server_connected,
"Server should connect after retransmit of dropped fragment"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_out_of_order_fragments() {
use dimpl::certificate::generate_self_signed_certificate;
let _ = env_logger::try_init();
let client_cert = generate_self_signed_certificate().expect("gen client cert");
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls12_config_with_mtu(200);
let mut now = Instant::now();
let mut client = Dtls::new_12(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_12(config, server_cert, now);
server.set_active(false);
let mut client_connected = false;
let mut server_connected = false;
for _ in 0..40 {
client.handle_timeout(now).expect("client timeout");
server.handle_timeout(now).expect("server timeout");
let client_out = drain_outputs(&mut client);
let server_out = drain_outputs(&mut server);
if client_out.connected {
client_connected = true;
}
if server_out.connected {
server_connected = true;
}
let mut client_packets = client_out.packets;
let mut server_packets = server_out.packets;
client_packets.reverse();
server_packets.reverse();
deliver_packets(&client_packets, &mut server);
deliver_packets(&server_packets, &mut client);
if client_connected && server_connected {
break;
}
now += Duration::from_millis(10);
}
assert!(
client_connected,
"Client should connect with out-of-order fragments"
);
assert!(
server_connected,
"Server should connect with out-of-order fragments"
);
}