use std::sync::Arc;
use std::time::{Duration, Instant};
#[cfg(feature = "rcgen")]
use dimpl::certificate::generate_self_signed_certificate;
use dimpl::{Dtls, Output};
use crate::common::*;
fn dtls12_alert_record(seq: u64, level: u8, description: u8) -> Vec<u8> {
let mut out = Vec::new();
out.push(21); out.extend_from_slice(&[0xFE, 0xFD]); out.extend_from_slice(&0u16.to_be_bytes()); out.extend_from_slice(&seq.to_be_bytes()[2..]); out.extend_from_slice(&2u16.to_be_bytes()); out.extend_from_slice(&[level, description]);
out
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_malformed_datagram_does_not_process_alerts_before_parse_completes() {
let _ = env_logger::try_init();
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls12_config();
let now = Instant::now();
let mut server = Dtls::new_12(config, server_cert, now);
server.set_active(false);
let mut packet = dtls12_alert_record(1, 2, 40);
packet.push(0xFF);
let err = server
.handle_packet(&packet)
.expect_err("malformed datagram should fail atomically");
assert!(
matches!(err, dimpl::Error::ParseIncomplete),
"expected ParseIncomplete, got {err:?}"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_too_many_control_records_still_fail_before_filtering() {
let _ = env_logger::try_init();
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls12_config();
let now = Instant::now();
let mut server = Dtls::new_12(config, server_cert, now);
server.set_active(false);
let mut packet = Vec::new();
for seq in 1..=9 {
packet.extend_from_slice(&dtls12_alert_record(seq, 1, 0));
}
let err = server
.handle_packet(&packet)
.expect_err("control-only datagram should still trip TooManyRecords");
assert!(
matches!(err, dimpl::Error::TooManyRecords),
"expected TooManyRecords, got {err:?}"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_recovers_from_corrupted_packet() {
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();
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 f1 = collect_packets(&mut client);
assert!(!f1.is_empty(), "client should emit ClientHello");
for mut p in f1 {
if p.len() > 5 {
p[1] ^= 0xFF;
p[2] ^= 0xFF;
}
let _ = server.handle_packet(&p);
}
server.handle_timeout(now).expect("server arm");
let s_pkts = collect_packets(&mut server);
assert!(s_pkts.is_empty(), "server should have nothing to send yet");
trigger_timeout(&mut client, &mut now);
let f1_resend = collect_packets(&mut client);
assert!(
!f1_resend.is_empty(),
"client should retransmit ClientHello after timeout"
);
for p in &f1_resend {
server.handle_packet(p).expect("server recv clean CH");
}
server.handle_timeout(now).expect("server arm flight 2");
let f2 = collect_packets(&mut server);
assert!(!f2.is_empty(), "server should emit HelloVerifyRequest");
for p in &f2 {
client.handle_packet(p).expect("client recv HVR");
}
client.handle_timeout(now).expect("client arm flight 3");
let f3 = collect_packets(&mut client);
assert!(!f3.is_empty(), "client should emit ClientHello with cookie");
for p in &f3 {
server.handle_packet(p).expect("server recv CH+cookie");
}
server.handle_timeout(now).expect("server arm flight 4");
let f4 = collect_packets(&mut server);
assert!(!f4.is_empty(), "server should emit ServerHello flight");
for p in &f4 {
client.handle_packet(p).expect("client recv flight 4");
}
client.handle_timeout(now).expect("client arm flight 5");
let f5 = collect_packets(&mut client);
assert!(!f5.is_empty(), "client should emit flight 5");
for p in &f5 {
server.handle_packet(p).expect("server recv flight 5");
}
server.handle_timeout(now).expect("server arm flight 6");
let server_out = drain_outputs(&mut server);
assert!(
!server_out.packets.is_empty(),
"server should emit flight 6"
);
for p in &server_out.packets {
client.handle_packet(p).expect("client recv flight 6");
}
client.handle_timeout(now).expect("client final timeout");
let client_out = drain_outputs(&mut client);
assert!(
client_out.connected,
"Client should be connected after recovering from corrupted packet"
);
assert!(
server_out.connected,
"Server should be connected after recovering from corrupted packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_discards_wrong_epoch_record() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_12_pair(now);
now = now_hs;
let bogus = vec![
22, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, ];
client
.handle_packet(&bogus)
.expect("wrong epoch record should be silently discarded");
client
.send_application_data(b"ping")
.expect("client send app data");
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out.app_data.iter().any(|d| d.as_slice() == b"ping"),
"Server should receive application data after wrong-epoch bogus packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_discards_truncated_record() {
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();
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 truncated = vec![0x16, 0xFE, 0xFD]; let result = client.handle_packet(&truncated);
match result {
Ok(()) => {} Err(e) => {
eprintln!("Truncated packet returned error (non-fatal): {}", e);
}
}
now = complete_dtls12_handshake(&mut client, &mut server, now);
let truncated_post = vec![0x17, 0xFE, 0xFD]; let result = client.handle_packet(&truncated_post);
match result {
Ok(()) => {}
Err(e) => {
eprintln!(
"Post-handshake truncated packet returned error (non-fatal): {}",
e
);
}
}
client
.send_application_data(b"hello")
.expect("client send app data");
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out.app_data.iter().any(|d| d.as_slice() == b"hello"),
"Server should receive application data after truncated bogus packets"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_discards_unauthenticated_close_notify() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_12_pair(now);
now = now_hs;
let close_notify_epoch0 = vec![
21, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x01, 0x00, ];
server
.handle_packet(&close_notify_epoch0)
.expect("epoch 0 alert must be silently discarded post-handshake");
client
.send_application_data(b"after-alert")
.expect("client send after alert");
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out
.app_data
.iter()
.any(|d| d.as_slice() == b"after-alert"),
"Server should still receive app data after close_notify alert at epoch 0"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_rejects_renegotiation() {
let _ = env_logger::try_init();
let now = Instant::now();
let (_client, mut server, _now) = setup_connected_12_pair(now);
let renegotiation_hello = vec![
22, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x2F, 0x01, 0x00, 0x00, 0x23, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xFE, 0xFD, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
0x1F, 0x20, 0x00, 0x00, 0x00, ];
let result = server.handle_packet(&renegotiation_hello);
match result {
Ok(()) => {
}
Err(e) => {
eprintln!("Renegotiation attempt correctly rejected with error: {}", e);
}
}
let result = server.send_application_data(b"post-reneg");
assert!(
result.is_ok(),
"Server should still accept sends after renegotiation attempt was rejected"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_mixed_datagram_plaintext_first_then_valid() {
let _ = env_logger::try_init();
let now = Instant::now();
let (mut client, mut server, now) = setup_connected_12_pair(now);
client
.send_application_data(b"valid-data")
.expect("send valid data");
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(!client_out.packets.is_empty(), "Should have valid packet");
let valid_packet = &client_out.packets[0];
let bogus_record = vec![
0x17, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x06, 0x62, 0x6F, 0x67, 0x75, 0x73, 0x21, ];
let mut mixed_datagram = bogus_record;
mixed_datagram.extend_from_slice(valid_packet);
server
.handle_packet(&mixed_datagram)
.expect("mixed datagram should not error");
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out
.app_data
.iter()
.any(|d| d.as_slice() == b"valid-data"),
"Server should receive the valid encrypted ApplicationData even when bogus record comes first"
);
assert_eq!(
server_out.app_data.len(),
1,
"Should receive exactly 1 app data (the valid one), not the bogus plaintext"
);
assert!(
!server_out
.app_data
.iter()
.any(|d| d.as_slice() == b"bogus!"),
"Bogus plaintext ApplicationData must not be delivered"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_mixed_datagram_valid_first_then_bogus() {
let _ = env_logger::try_init();
let now = Instant::now();
let (mut client, mut server, now) = setup_connected_12_pair(now);
client
.send_application_data(b"valid-data")
.expect("send valid data");
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(!client_out.packets.is_empty(), "Should have valid packet");
let valid_packet = &client_out.packets[0];
let bogus_record = vec![
0x17, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x00, 0x06, 0x62, 0x6F, 0x67, 0x75, 0x73, 0x21, ];
let mut mixed_datagram = valid_packet.clone();
mixed_datagram.extend_from_slice(&bogus_record);
server
.handle_packet(&mixed_datagram)
.expect("mixed datagram should not error");
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out
.app_data
.iter()
.any(|d| d.as_slice() == b"valid-data"),
"Server should receive the valid encrypted ApplicationData even when bogus record follows"
);
assert_eq!(
server_out.app_data.len(),
1,
"Should receive exactly 1 app data (the valid one), not the bogus plaintext"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_app_data_after_close_notify_is_ignored() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_12_pair(now);
now = now_hs;
client
.send_application_data(b"before-close")
.expect("send app data");
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let app_data_out = drain_outputs(&mut client);
let app_data_packets = app_data_out.packets.clone();
assert!(!app_data_packets.is_empty(), "Should have app data packet");
client.close().unwrap();
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let close_out = drain_outputs(&mut client);
assert!(
!close_out.packets.is_empty(),
"Should have close_notify packet"
);
deliver_packets(&close_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(server_out.close_notify, "Server should emit CloseNotify");
deliver_packets(&app_data_packets, &mut server);
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out.app_data.is_empty(),
"ApplicationData arriving after close_notify must be discarded"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_close_during_handshake_emits_no_packets() {
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();
let 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");
let client_out = drain_outputs(&mut client);
assert!(
!client_out.packets.is_empty(),
"Client should emit ClientHello"
);
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let _server_out = drain_outputs(&mut server);
client.close().unwrap();
let client_out = drain_outputs(&mut client);
assert!(
client_out.packets.is_empty(),
"Client should not emit packets after close() during handshake"
);
let later = now + Duration::from_secs(5);
let _ = client.handle_timeout(later);
let client_out = drain_outputs(&mut client);
assert!(
client_out.packets.is_empty(),
"Client should not emit packets after timeout post-close()"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_reciprocal_close_notify_and_no_further_sends() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_12_pair(now);
now = now_hs;
client.close().unwrap();
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(
!client_out.packets.is_empty(),
"Client should emit close_notify alert"
);
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out.close_notify,
"Server should emit Output::CloseNotify"
);
assert!(
!server_out.packets.is_empty(),
"Server should emit a reciprocal close_notify packet"
);
deliver_packets(&server_out.packets, &mut client);
client
.handle_timeout(now)
.expect("client timeout after reciprocal");
let client_out2 = drain_outputs(&mut client);
assert!(
client_out2.close_notify,
"Client should emit Output::CloseNotify after receiving reciprocal close_notify"
);
assert!(
server.send_application_data(b"after-close").is_err(),
"send_application_data must fail after close_notify in DTLS 1.2"
);
assert!(
client.send_application_data(b"after-close").is_err(),
"send_application_data must fail after close() in DTLS 1.2"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_discard_pending_writes_on_close_notify() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_12_pair(now);
now = now_hs;
server
.send_application_data(b"pending-data")
.expect("server send pending data");
client.close().unwrap();
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(server_out.close_notify, "Server should see CloseNotify");
assert!(
!server_out.packets.is_empty(),
"Server should emit reciprocal close_notify"
);
deliver_packets(&server_out.packets, &mut client);
client
.handle_timeout(now)
.expect("client timeout after reciprocal");
let client_out2 = drain_outputs(&mut client);
assert!(
client_out2.close_notify,
"Client should emit Output::CloseNotify after receiving reciprocal close_notify"
);
assert!(
client_out2.app_data.is_empty(),
"Pending data must be discarded when close_notify is received"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_fatal_alert_during_handshake() {
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();
let 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);
client.handle_timeout(now).expect("client timeout");
let _client_out = drain_outputs(&mut client);
let fatal_alert = vec![
21, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x02, 0x28, ];
let result = client.handle_packet(&fatal_alert);
assert!(
result.is_err(),
"Fatal alert during handshake should return an error"
);
let err = result.unwrap_err();
assert!(
matches!(err, dimpl::Error::SecurityError(_)),
"Error should be SecurityError, got: {:?}",
err
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_app_data_delivered_before_close_notify() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_12_pair(now);
now = now_hs;
client
.send_application_data(b"before-close")
.expect("send app data");
client.close().unwrap();
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
let mut saw_app_data = false;
let mut saw_close_notify = false;
let mut close_after_data = false;
let mut buf = vec![0u8; 2048];
loop {
match server.poll_output(&mut buf) {
Output::ApplicationData(data) => {
assert!(
!saw_close_notify,
"ApplicationData must not appear after CloseNotify"
);
if data == b"before-close" {
saw_app_data = true;
}
}
Output::CloseNotify => {
saw_close_notify = true;
if saw_app_data {
close_after_data = true;
}
}
Output::Timeout(_) => break,
_ => {}
}
}
assert!(saw_app_data, "Server should receive the app data");
assert!(saw_close_notify, "Server should see CloseNotify");
assert!(
close_after_data,
"CloseNotify must come after ApplicationData"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls12_close_notify_not_retransmitted() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, _server, now_hs) = setup_connected_12_pair(now);
now = now_hs;
client.close().unwrap();
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(
!client_out.packets.is_empty(),
"Client should emit close_notify alert"
);
for _ in 0..5 {
now += Duration::from_secs(5);
let _ = client.handle_timeout(now);
let out = drain_outputs(&mut client);
assert!(
out.packets.is_empty(),
"close_notify must not be retransmitted (RFC 6347 §4.2.7)"
);
}
}