use std::time::Duration;
use rand::{Rng, SeedableRng};
use tokio::net::UdpSocket;
use reconcile::{replicated_map::Config, ReplicatedMap};
fn malformed_payloads(seed: u64) -> Vec<Vec<u8>> {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut payloads = Vec::new();
for _ in 0..200 {
let len = rng.gen_range(0..4096);
let mut buf = vec![0u8; len];
rng.fill(&mut buf[..]);
payloads.push(buf);
}
for len in [0usize, 1, 7, 64, 1024, 4096] {
payloads.push(vec![0u8; len]);
payloads.push(vec![0xFFu8; len]);
}
for tag in 0u8..=4 {
payloads.push(vec![tag]);
payloads.push(vec![tag, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
let mut huge_len = vec![tag];
huge_len.extend_from_slice(&u64::MAX.to_le_bytes());
payloads.push(huge_len);
}
payloads
}
#[tokio::test(flavor = "multi_thread")]
async fn malformed_datagrams_do_not_panic_or_corrupt_state() {
let port = 8085;
let victim_addr = "127.0.0.70";
let config = Config::default()
.with_port(port)
.with_listen_addr(victim_addr.parse().unwrap())
.with_insecure_no_key();
let store = ReplicatedMap::<i32, String>::new(config)
.await
.expect("bind failed");
store.load_bulk(&[(0, "legit".to_string())]);
let task = tokio::spawn(store.clone().run());
let attacker = UdpSocket::bind("127.0.0.71:0").await.unwrap();
let target = format!("{victim_addr}:{port}");
for payload in malformed_payloads(0xDEAD_BEEF) {
let _ = attacker.send_to(&payload, &target).await;
tokio::task::yield_now().await;
}
tokio::time::sleep(Duration::from_millis(200)).await;
assert!(
!task.is_finished(),
"receive task died on a malformed datagram"
);
assert_eq!(store.get(&0).as_deref(), Some(&"legit".to_string()));
task.abort();
}