use std::sync::Arc;
use std::time::{Duration, Instant};
#[cfg(feature = "rcgen")]
use dimpl::certificate::generate_self_signed_certificate;
use dimpl::{Config, Dtls, Output};
use crate::common::*;
fn dtls13_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
}
fn dtls13_ack_record(seq: u64) -> Vec<u8> {
let mut out = Vec::new();
out.push(26); 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(&[0xAA, 0xBB]);
out
}
fn dtls13_ack_record_for_records(seq: u64, records: &[(u64, u64)]) -> Vec<u8> {
let record_numbers_len = (records.len() * 16) as u16;
let mut fragment = Vec::with_capacity(2 + records.len() * 16);
fragment.extend_from_slice(&record_numbers_len.to_be_bytes());
for &(epoch, record_seq) in records {
fragment.extend_from_slice(&epoch.to_be_bytes());
fragment.extend_from_slice(&record_seq.to_be_bytes());
}
let mut out = Vec::new();
out.push(26); 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(&(fragment.len() as u16).to_be_bytes());
out.extend_from_slice(&fragment);
out
}
#[test]
#[cfg(feature = "rcgen")]
fn oversized_application_data_reports_buffer_too_small() {
let now = Instant::now();
let (mut client, mut server, _now) = setup_connected_13_pair(now);
let payload = vec![0xa5; 4000];
client
.send_application_data(&payload)
.expect("send application data");
let mut large_buf = vec![0u8; 8192];
let mut packets = Vec::new();
loop {
match client.poll_output(&mut large_buf) {
Output::Packet(packet) => packets.push(packet.to_vec()),
Output::Timeout(_) => break,
_ => {}
}
}
deliver_packets(&packets, &mut server);
let mut small_buf = vec![0u8; 2048];
match server.poll_output(&mut small_buf) {
Output::BufferTooSmall { needed } => assert_eq!(needed, payload.len()),
output => panic!("expected BufferTooSmall, got {output:?}"),
}
match server.poll_output(&mut large_buf) {
Output::ApplicationData(data) => assert_eq!(data, payload.as_slice()),
output => panic!("expected retained application data, got {output:?}"),
}
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_malformed_datagram_is_discarded_without_processing_alerts() {
let _ = env_logger::try_init();
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls13_config();
let now = Instant::now();
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
let mut packet = dtls13_alert_record(1, 2, 40);
packet.push(0xFF);
server
.handle_packet(&packet)
.expect("malformed datagram should be discarded");
let mut buf = [0; 1500];
assert!(!matches!(server.poll_output(&mut buf), Output::CloseNotify));
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_too_many_control_records_are_discarded() {
let _ = env_logger::try_init();
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls13_config();
let now = Instant::now();
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
let mut packet = Vec::new();
for seq in 1..=17 {
packet.extend_from_slice(&dtls13_ack_record(seq));
}
server
.handle_packet(&packet)
.expect("too many records should be discarded");
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_discards_too_short_ciphertext_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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
for len in 0..16u16 {
let mut bogus = Vec::with_capacity(5 + len as usize);
bogus.push(0x2F);
bogus.extend_from_slice(&(0x0100 + len).to_be_bytes()); bogus.extend_from_slice(&len.to_be_bytes());
bogus.resize(5 + len as usize, 0);
client
.handle_packet(&bogus)
.expect("short ciphertext record should be silently discarded");
}
client.send_application_data(b"ping").expect("send app");
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 bogus packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_discards_cid_bit_records() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
let bogus = vec![0x3F];
client
.handle_packet(&bogus)
.expect("CID-bit record should be discarded");
client.send_application_data(b"ping").expect("send app");
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 CID-bit bogus packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_discards_unauthenticated_ciphertext_without_length_field() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
let mut bogus = Vec::new();
bogus.push(0x2B);
bogus.extend_from_slice(&0x0001u16.to_be_bytes()); bogus.extend_from_slice(&[0u8; 16]);
client
.handle_packet(&bogus)
.expect("Unauthenticated ciphertext should be discarded");
client.send_application_data(b"ping").expect("send app");
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 unauthenticated bogus packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
let mut client_connected = false;
let mut server_connected = false;
let mut corrupted_once = false;
for i in 0..60 {
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;
}
for mut p in client_out.packets {
if !corrupted_once && p.len() > 20 {
p[15] ^= 0xFF;
p[16] ^= 0xFF;
corrupted_once = true;
}
let _ = server.handle_packet(&p);
}
deliver_packets(&server_out.packets, &mut client);
if client_connected && server_connected {
break;
}
if i % 5 == 4 {
now += Duration::from_secs(2);
} else {
now += Duration::from_millis(50);
}
}
assert!(
client_connected,
"Client should connect despite corrupted packet"
);
assert!(
server_connected,
"Server should connect despite corrupted packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_close_notify_graceful_shutdown() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_13_pair(now);
now = now_hs;
client.close().expect("client close");
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 packet"
);
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 observe CloseNotify from client"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_warning_user_canceled_alert_is_ignored() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_13_pair(now);
now = now_hs;
let warning_alert = dtls13_alert_record(100, 1, 90);
server
.handle_packet(&warning_alert)
.expect("warning alert should be ignored");
let server_out = drain_outputs(&mut server);
assert!(
!server_out.close_notify,
"warning alert must not be reported as close_notify"
);
client
.send_application_data(b"still-open")
.expect("connection should remain open after warning alert");
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 still emit application data after warning alert"
);
for packet in &client_out.packets {
server
.handle_packet(packet)
.expect("server should still accept packets after warning alert");
}
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out
.app_data
.iter()
.any(|data| data.as_slice() == b"still-open"),
"application data should still be delivered after warning alert"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_unknown_warning_level_alert_is_still_fatal() {
let _ = env_logger::try_init();
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls13_config();
let now = Instant::now();
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
let packet = dtls13_alert_record(1, 1, 40);
let err = server
.handle_packet(&packet)
.expect_err("non-whitelisted alert must be fatal regardless of level");
assert!(matches!(
err,
dimpl::Error::SecurityError(dimpl::SecurityError::FatalAlert { description: 40 })
));
}
fn queue_ack_with_peer_key_update(sender: &mut Dtls, receiver: &mut Dtls, now: &mut Instant) {
for i in 0..5 {
sender
.send_application_data(format!("msg{i}").as_bytes())
.expect("send app data");
}
*now += Duration::from_millis(10);
sender.handle_timeout(*now).expect("sender timeout");
let sender_out = drain_outputs(sender);
assert!(
!sender_out.packets.is_empty(),
"sender should emit app data and KeyUpdate"
);
for packet in &sender_out.packets {
receiver
.handle_packet(packet)
.expect("receiver should accept KeyUpdate batch");
}
}
fn drain_expected_app_data(endpoint: &mut Dtls, expected: usize) {
let mut buf = vec![0u8; 2048];
for i in 0..expected {
match endpoint.poll_output(&mut buf) {
Output::ApplicationData(data) => {
assert_eq!(
data,
format!("msg{i}").as_bytes(),
"unexpected queued application data before close()"
);
}
_ => panic!("expected queued application data"),
}
}
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_client_close_after_queued_ack_sends_close_notify() {
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 = Arc::new(
Config::builder()
.aead_encryption_limit(5)
.build()
.expect("build config"),
);
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
queue_ack_with_peer_key_update(&mut server, &mut client, &mut now);
drain_expected_app_data(&mut client, 5);
client
.close()
.expect("close should succeed with queued ACK pending");
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let mut buf = vec![0u8; 2048];
let first_packet = match client.poll_output(&mut buf) {
Output::Packet(packet) => packet.to_vec(),
_ => panic!("expected first close output packet"),
};
server
.handle_packet(&first_packet)
.expect("server should accept the first client close packet");
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
server_out.close_notify,
"server should observe close_notify from the first client close packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_server_close_after_queued_ack_sends_close_notify() {
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 = Arc::new(
Config::builder()
.aead_encryption_limit(5)
.build()
.expect("build config"),
);
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
queue_ack_with_peer_key_update(&mut client, &mut server, &mut now);
drain_expected_app_data(&mut server, 5);
server
.close()
.expect("close should succeed with queued ACK pending");
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let mut buf = vec![0u8; 2048];
let first_packet = match server.poll_output(&mut buf) {
Output::Packet(packet) => packet.to_vec(),
_ => panic!("expected first close output packet"),
};
client
.handle_packet(&first_packet)
.expect("client should accept the first server close packet");
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(
client_out.close_notify,
"client should observe close_notify from the first server close packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_discards_unknown_epoch_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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
let mut bogus = Vec::new();
bogus.push(0x2D); bogus.extend_from_slice(&0x0000u16.to_be_bytes()); bogus.extend_from_slice(&0x0020u16.to_be_bytes()); bogus.extend_from_slice(&[0xAA; 32]);
client
.handle_packet(&bogus)
.expect("unknown-epoch record should be discarded");
client.send_application_data(b"ping").expect("send app");
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 unknown-epoch bogus packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_discards_truncated_unified_header() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
let bogus = vec![0x2F];
let _ = client.handle_packet(&bogus);
client.send_application_data(b"ping").expect("send app");
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 truncated header packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_discards_plaintext_after_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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
let bogus = vec![
0x16, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, ];
client
.handle_packet(&bogus)
.expect("silently discard should not return error");
client
.send_application_data(b"after-plaintext")
.expect("send app");
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-plaintext"),
"Server should receive application data after plaintext bogus packet"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_post_encryption_plaintext_close_notify_is_ignored() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
server
.handle_packet(&dtls13_alert_record(0x100, 1, 0))
.expect("post-encryption plaintext close_notify should be ignored");
let after_plaintext_alert = drain_outputs(&mut server);
assert!(
!after_plaintext_alert.close_notify,
"plaintext close_notify after encryption must not close the connection"
);
client
.send_application_data(b"after-plaintext-close-notify")
.expect("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"after-plaintext-close-notify"),
"server should accept encrypted app data after plaintext close_notify"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_post_encryption_plaintext_fatal_alert_is_ignored() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
server
.handle_packet(&dtls13_alert_record(0x101, 2, 40))
.expect("post-encryption plaintext fatal alert should be ignored");
client
.send_application_data(b"after-plaintext-fatal-alert")
.expect("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"after-plaintext-fatal-alert"),
"server should accept encrypted app data after plaintext fatal alert"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_post_encryption_plaintext_ack_does_not_stop_retransmit() {
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 = Arc::new(
Config::builder()
.flight_start_rto(Duration::from_millis(100))
.build()
.expect("build config"),
);
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
client.handle_timeout(now).expect("client timeout");
let client_hello = collect_packets(&mut client);
assert!(!client_hello.is_empty(), "client should emit ClientHello");
deliver_packets(&client_hello, &mut server);
server.handle_timeout(now).expect("server timeout");
let first_server_flight = collect_packets(&mut server);
assert!(
!first_server_flight.is_empty(),
"server should emit first flight"
);
let acked_records: Vec<(u64, u64)> = (0..64).map(|seq| (2, seq)).collect();
server
.handle_packet(&dtls13_ack_record_for_records(0x200, &acked_records))
.expect("post-encryption plaintext ACK should be ignored");
now += Duration::from_millis(400);
server.handle_timeout(now).expect("server timeout");
let retransmit = collect_packets(&mut server);
assert!(
!retransmit.is_empty(),
"forged plaintext ACK must not stop server flight retransmission"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_duplicate_client_hello_still_triggers_retransmit_after_peer_encryption() {
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 = dtls13_config();
let now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
client.handle_timeout(now).expect("client timeout");
let client_hello = collect_packets(&mut client);
assert!(!client_hello.is_empty(), "client should emit ClientHello");
deliver_packets(&client_hello, &mut server);
server.handle_timeout(now).expect("server timeout");
let first_server_flight = collect_packets(&mut server);
assert!(
!first_server_flight.is_empty(),
"server should emit ServerHello plus encrypted handshake flight"
);
deliver_packets(&client_hello, &mut server);
let retransmit = collect_packets(&mut server);
assert!(
!retransmit.is_empty(),
"duplicate plaintext ClientHello should still trigger server flight retransmission"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_alert_bad_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 client_cert_der = client_cert.certificate.clone();
let server_cert_der = server_cert.certificate.clone();
let config = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
let mut client_connected = false;
let mut server_connected = false;
let mut client_peer_cert: Option<Vec<u8>> = None;
let mut server_peer_cert: Option<Vec<u8>> = None;
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);
client_connected |= client_out.connected;
server_connected |= server_out.connected;
if client_out.peer_cert.is_some() {
client_peer_cert = client_out.peer_cert;
}
if server_out.peer_cert.is_some() {
server_peer_cert = server_out.peer_cert;
}
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 be connected");
assert!(server_connected, "Server should be connected");
let client_saw_cert = client_peer_cert.expect("Client should receive PeerCert");
assert_eq!(
client_saw_cert, server_cert_der,
"Client's PeerCert should match the server's certificate"
);
let server_saw_cert = server_peer_cert.expect("Server should receive PeerCert");
assert_eq!(
server_saw_cert, client_cert_der,
"Server's PeerCert should match the client's certificate"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_only_functional_signature_schemes_advertised() {
let _ = env_logger::try_init();
let client_cert = generate_self_signed_certificate().expect("gen client cert");
let config = dtls13_config();
let now = Instant::now();
let mut client = Dtls::new_13(config, client_cert, now);
client.set_active(true);
client.handle_timeout(now).expect("client timeout");
let out = drain_outputs(&mut client);
assert!(!out.packets.is_empty(), "Client should send ClientHello");
let ch_packet = &out.packets[0];
let sig_alg_type: [u8; 2] = [0x00, 0x0D];
let pos = ch_packet
.windows(2)
.position(|w| w == sig_alg_type)
.expect("signature_algorithms extension should be present in ClientHello");
let ext_len_offset = pos + 2;
let list_len_offset = ext_len_offset + 2;
let list_len =
u16::from_be_bytes([ch_packet[list_len_offset], ch_packet[list_len_offset + 1]]) as usize;
let schemes_start = list_len_offset + 2;
let mut advertised: Vec<u16> = Vec::new();
let mut i = schemes_start;
while i < schemes_start + list_len {
let scheme = u16::from_be_bytes([ch_packet[i], ch_packet[i + 1]]);
advertised.push(scheme);
i += 2;
}
let non_functional: Vec<u16> = advertised
.iter()
.copied()
.filter(|s| *s != 0x0403 && *s != 0x0503)
.collect();
assert!(
non_functional.is_empty(),
"Non-functional signature schemes advertised: {:04X?}. \
Only ECDSA_SECP256R1_SHA256 (0x0403) and ECDSA_SECP384R1_SHA384 (0x0503) \
should be advertised.",
non_functional,
);
assert_eq!(
advertised,
vec![0x0403, 0x0503],
"Expected exactly ECDSA P-256 and P-384"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_bad_record_does_not_kill_datagram() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
server
.send_application_data(b"hello")
.expect("send app data");
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
!server_out.packets.is_empty(),
"Server should produce a packet"
);
let good_record = &server_out.packets[0];
let mut bogus_record = Vec::new();
bogus_record.push(0x2D); bogus_record.extend_from_slice(&0x0000u16.to_be_bytes()); bogus_record.extend_from_slice(&0x0020u16.to_be_bytes()); bogus_record.extend_from_slice(&[0xAA; 32]);
let mut combined = bogus_record;
combined.extend_from_slice(good_record);
client
.handle_packet(&combined)
.expect("multi-record datagram with bad first record should not error");
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(
client_out.app_data.iter().any(|d| d.as_slice() == b"hello"),
"Client should receive the valid app data record that followed the bogus record"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_old_epoch_record_accepted_after_key_update() {
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 = Arc::new(
Config::builder()
.aead_encryption_limit(5)
.build()
.expect("build config"),
);
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
client
.send_application_data(b"old-epoch-data")
.expect("send delayed msg");
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
let delayed_packets = client_out.packets.clone();
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
deliver_packets(&server_out.packets, &mut client);
let mut server_received = 0;
for i in 0..12 {
let msg = format!("msg-{}", i);
client
.send_application_data(msg.as_bytes())
.expect("send app data");
now += Duration::from_millis(10);
for _ in 0..3 {
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);
deliver_packets(&server_out.packets, &mut client);
server_received += server_out.app_data.len();
}
}
assert_eq!(
server_received, 12,
"All regular messages should be received"
);
deliver_packets(&delayed_packets, &mut server);
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
deliver_packets(&server_out.packets, &mut client);
assert!(
server_out
.app_data
.iter()
.any(|d| d.as_slice() == b"old-epoch-data"),
"Server must receive the delayed old-epoch packet (per-epoch replay windows)"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_client_hello_padded_to_mtu() {
let _ = env_logger::try_init();
let client_cert = generate_self_signed_certificate().expect("gen client cert");
let mtu = 1150;
let config = dtls13_config();
let now = Instant::now();
let mut client = Dtls::new_13(config, client_cert, now);
client.set_active(true);
client.handle_timeout(now).expect("client timeout");
let out = drain_outputs(&mut client);
assert!(!out.packets.is_empty(), "Client should send ClientHello");
let ch_packet = &out.packets[0];
assert_eq!(
ch_packet.len(),
mtu,
"ClientHello packet should be padded to MTU ({} bytes), got {} bytes",
mtu,
ch_packet.len()
);
let padding_type: [u8; 2] = [0x00, 0x15];
let has_padding = ch_packet.windows(2).any(|w| w == padding_type);
assert!(
has_padding,
"ClientHello should contain a padding extension (type 0x0015)"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_mixed_datagram_during_handshake_bogus_first() {
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 = dtls13_config();
let now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(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(), "Should have ClientHello");
let client_hello = &client_out.packets[0];
let bogus = vec![
0x17, 0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x62, 0x6F, 0x67, 0x75, 0x73, ];
let mut mixed = bogus;
mixed.extend_from_slice(client_hello);
server
.handle_packet(&mixed)
.expect("mixed datagram should not error");
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
assert!(
!server_out.packets.is_empty(),
"Server should send ServerHello flight despite bogus record"
);
deliver_packets(&server_out.packets, &mut client);
complete_dtls13_handshake(&mut client, &mut server, now);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_mixed_datagram_plaintext_first_then_valid() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, 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 dtls13_mixed_datagram_valid_first_then_bogus() {
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 = dtls13_config();
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, 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 dtls13_half_close_send_then_close() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_13_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);
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 CloseNotify");
server
.send_application_data(b"half-close-data")
.expect("send after close_notify should work");
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
deliver_packets(&server_out.packets, &mut client);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(
client_out
.app_data
.iter()
.any(|d| d.as_slice() == b"half-close-data"),
"Client should receive data sent during half-close"
);
server.close().unwrap();
assert!(
server.send_application_data(b"after-own-close").is_err(),
"Server should not accept sends after its own close()"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_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 = dtls13_config();
let now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(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 dtls13_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_13_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 dtls13_close_notify_out_of_order_app_data_accepted() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_13_pair(now);
now = now_hs;
server
.send_application_data(b"before-close-data")
.expect("send app data");
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let app_data_packets = drain_outputs(&mut server).packets;
server.close().unwrap();
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let close_packets = drain_outputs(&mut server).packets;
deliver_packets(&close_packets, &mut client);
deliver_packets(&app_data_packets, &mut client);
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(
client_out
.app_data
.iter()
.any(|d| d.as_slice() == b"before-close-data"),
"Out-of-order app data with earlier sequence should be accepted"
);
assert!(client_out.close_notify, "Client should emit CloseNotify");
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_half_closed_local_no_retransmit() {
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 = Arc::new(
Config::builder()
.aead_encryption_limit(3)
.build()
.expect("build config"),
);
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
for i in 0..3 {
client
.send_application_data(format!("msg{}", i).as_bytes())
.expect("send app data");
}
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);
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let _ = drain_outputs(&mut server);
client.close().unwrap();
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let _ = drain_outputs(&mut client);
for _ in 0..5 {
now += Duration::from_secs(5);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(
client_out.packets.is_empty(),
"No retransmission packets should be emitted after close()"
);
}
let result = client.send_application_data(b"should-fail");
assert!(
result.is_err(),
"send_application_data should fail in HalfClosedLocal"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_half_closed_local_transitions_to_closed() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_13_pair(now);
now = now_hs;
assert!(!client.is_closing());
assert!(!client.is_closed());
assert!(!server.is_closing());
assert!(!server.is_closed());
client.close().unwrap();
assert!(client.is_closing());
assert!(!client.is_closed());
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(client.is_closing());
assert!(!client.is_closed());
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
assert!(server.is_closing());
assert!(!server.is_closed());
let server_out = drain_outputs(&mut server);
assert!(server_out.close_notify, "Server should see CloseNotify");
assert!(!server.is_closing());
assert!(!server.is_closed());
server.close().unwrap();
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
assert!(server.is_closing());
assert!(!server.is_closed());
let server_out = drain_outputs(&mut server);
assert!(!server.is_closing());
assert!(server.is_closed());
deliver_packets(&server_out.packets, &mut client);
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
assert!(client.is_closing());
assert!(!client.is_closed());
let client_out = drain_outputs(&mut client);
assert!(
client_out.close_notify,
"Client should emit CloseNotify after receiving peer's close_notify"
);
assert!(!client.is_closing());
assert!(client.is_closed());
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_close_state_matrix() {
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_13_pair(now);
now = now_hs;
assert!(!client.is_closing());
assert!(!client.is_closed());
assert!(!server.is_closing());
assert!(!server.is_closed());
client.close().unwrap();
assert!(client.is_closing(), "local close pending");
assert!(!client.is_closed(), "local close not terminal");
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(), "local close_notify packet");
assert!(
client.is_closing(),
"local close remains pending until peer close_notify"
);
assert!(!client.is_closed(), "local half-close is not terminal");
deliver_packets(&client_out.packets, &mut server);
server.handle_timeout(now).expect("server timeout");
assert!(server.is_closing(), "remote close pending");
assert!(!server.is_closed(), "remote close not drained");
let server_out = drain_outputs(&mut server);
assert!(server_out.close_notify, "remote CloseNotify event");
assert!(!server.is_closing(), "remote close event drained");
assert!(
!server.is_closed(),
"remote half-close leaves write side open"
);
server.close().unwrap();
now += Duration::from_millis(10);
server
.handle_timeout(now)
.expect("server timeout after close");
assert!(server.is_closing(), "remote reciprocal close pending");
assert!(!server.is_closed(), "remote reciprocal close not drained");
let server_out = drain_outputs(&mut server);
assert!(
!server_out.packets.is_empty(),
"remote reciprocal close_notify packet"
);
assert!(!server.is_closing(), "remote reciprocal close drained");
assert!(server.is_closed(), "remote close terminal");
deliver_packets(&server_out.packets, &mut client);
now += Duration::from_millis(10);
client
.handle_timeout(now)
.expect("client timeout after reciprocal");
assert!(client.is_closing(), "local close peer response pending");
assert!(!client.is_closed(), "local close peer response not drained");
let client_out = drain_outputs(&mut client);
assert!(client_out.close_notify, "local CloseNotify event");
assert!(!client.is_closing(), "local close drained");
assert!(client.is_closed(), "local close terminal");
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_close_prohibits_further_sends() {
let _ = env_logger::try_init();
let mut now = Instant::now();
let (mut client, mut server, now_hs) = setup_connected_13_pair(now);
now = now_hs;
server.close().unwrap();
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let close_packets = drain_outputs(&mut server).packets;
deliver_packets(&close_packets, &mut client);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(client_out.close_notify, "Client should see CloseNotify");
let result = server.send_application_data(b"after-close");
assert!(
result.is_err(),
"send_application_data should fail after close()"
);
}
#[test]
#[cfg(feature = "rcgen")]
fn dtls13_half_closed_local_no_ack() {
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 = Arc::new(
Config::builder()
.aead_encryption_limit(5)
.build()
.expect("build config"),
);
let mut now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
now = complete_dtls13_handshake(&mut client, &mut server, now);
client.close().unwrap();
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let close_packets = drain_outputs(&mut client).packets;
for i in 0..5 {
server
.send_application_data(format!("msg{}", i).as_bytes())
.expect("send app data");
}
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let batch1 = drain_outputs(&mut server).packets;
server
.send_application_data(b"msg5")
.expect("send app data on new epoch");
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let batch2 = drain_outputs(&mut server).packets;
deliver_packets(&close_packets, &mut server);
now += Duration::from_millis(10);
server.handle_timeout(now).expect("server timeout");
let _ = drain_outputs(&mut server);
deliver_packets(&batch1, &mut client);
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out1 = drain_outputs(&mut client);
assert!(
client_out1.packets.is_empty(),
"Client in HalfClosedLocal should not send ACK for KeyUpdate"
);
deliver_packets(&batch2, &mut client);
now += Duration::from_millis(10);
client.handle_timeout(now).expect("client timeout");
let client_out2 = drain_outputs(&mut client);
let total = client_out1.app_data.len() + client_out2.app_data.len();
assert_eq!(
total, 6,
"Client must receive all 6 messages (6th on new epoch proves KeyUpdate was processed)"
);
assert!(
client_out2.packets.is_empty(),
"Client in HalfClosedLocal should not send any packets"
);
}