use rusty_time_core::ntp::{
self, HEADER_LEN, LeapIndicator, Mode, NtpPacket, NtpShort, NtpTimestamp, Trailer,
};
fn splitmix(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
const PACKETS: usize = 256;
const PASSES: usize = 2000;
fn main() {
let mut seed = 0x0123_4567_89AB_CDEFu64;
let mut corpus: Vec<Vec<u8>> = Vec::with_capacity(PACKETS);
for i in 0..PACKETS {
let mut p = vec![0u8; HEADER_LEN];
for chunk in p.chunks_mut(8) {
let w = splitmix(&mut seed).to_be_bytes();
chunk.copy_from_slice(&w[..chunk.len()]);
}
let v = if i % 2 == 0 { 4u8 } else { 3u8 };
p[0] = (p[0] & !0b0011_1000) | (v << 3);
match i % 8 {
1 => {
p.extend_from_slice(&[0x01, 0x04, 0x00, 0x14]);
for _ in 0..16 {
p.push(splitmix(&mut seed) as u8);
}
}
5 => {
for _ in 0..4 {
p.push(splitmix(&mut seed) as u8);
}
}
_ => {}
}
corpus.push(p);
}
let mut checksum: u64 = 0xcbf2_9ce4_8422_2325;
let mut parsed: u64 = 0;
let mut rejected: u64 = 0;
let mut fields: u64 = 0;
let mut opaque: u64 = 0;
let mut written: u64 = 0;
#[inline(always)]
fn mix(acc: &mut u64, v: u64) {
*acc = (*acc).rotate_left(7) ^ v;
}
let fold = |checksum: &mut u64, v: u64| {
*checksum = checksum.wrapping_mul(0x0100_0000_01b3).wrapping_add(v);
};
for _ in 0..PASSES {
for pkt in &corpus {
match NtpPacket::parse(pkt) {
Ok(h) => {
parsed += 1;
let mut acc: u64 = 0;
mix(&mut acc, h.leap.bits_for_gate() as u64);
mix(&mut acc, h.version as u64);
mix(&mut acc, h.mode.bits_for_gate() as u64);
let mut out = [0u8; HEADER_LEN];
h.write(&mut out);
written += 1;
let (words, _) = out.as_chunks::<8>();
for w in words {
mix(&mut acc, u64::from_le_bytes(*w));
}
fold(&mut checksum, acc);
}
Err(_) => rejected += 1,
}
for t in ntp::extension_fields(pkt) {
match t {
Trailer::Extension(f) => {
fields += 1;
let mut acc: u64 = 0;
mix(&mut acc, f.field_type as u64);
mix(&mut acc, f.value.len() as u64);
for b in f.value {
mix(&mut acc, *b as u64);
}
fold(&mut checksum, acc);
}
Trailer::Opaque(b) => {
opaque += 1;
fold(&mut checksum, b.len() as u64);
}
}
}
}
}
let mut ts_acc: u64 = 0;
for i in 0..PACKETS as i64 {
let t = NtpTimestamp::from_unix(1_787_856_000 + i, (i as u32).wrapping_mul(7_919));
ts_acc ^= t.0;
ts_acc = ts_acc.wrapping_add(NtpShort::from_seconds(i as f64 / 1024.0).0 as u64);
}
fold(&mut checksum, ts_acc);
println!("packets {PACKETS}");
println!("passes {PASSES}");
println!("parsed {parsed}");
println!("rejected {rejected}");
println!("written {written}");
println!("fields {fields}");
println!("opaque {opaque}");
println!("CHECKSUM {checksum:#018x}");
}
trait GateBits {
fn bits_for_gate(self) -> u8;
}
impl GateBits for LeapIndicator {
fn bits_for_gate(self) -> u8 {
match self {
LeapIndicator::NoWarning => 0,
LeapIndicator::LastMinute61 => 1,
LeapIndicator::LastMinute59 => 2,
LeapIndicator::Unsynchronized => 3,
}
}
}
impl GateBits for Mode {
fn bits_for_gate(self) -> u8 {
match self {
Mode::Reserved => 0,
Mode::SymmetricActive => 1,
Mode::SymmetricPassive => 2,
Mode::Client => 3,
Mode::Server => 4,
Mode::Broadcast => 5,
Mode::Control => 6,
Mode::Private => 7,
}
}
}