use std::os::unix::net::UnixDatagram;
use std::path::{Path, PathBuf};
pub const SOCK_MAGIC: i32 = 0x534f_434b;
pub const SAMPLE_LEN: usize = 40;
const TV_SEC_AT: usize = 0;
const TV_USEC_AT: usize = 8;
const OFFSET_AT: usize = 16;
const PULSE_AT: usize = 24;
const LEAP_AT: usize = 28;
const PAD_AT: usize = 32;
pub const MAGIC_AT: usize = 36;
pub fn encode_sample(
tv_sec: i64,
tv_usec: i64,
offset_s: f64,
pulse: i32,
leap: i32,
) -> [u8; SAMPLE_LEN] {
let mut b = [0u8; SAMPLE_LEN];
b[TV_SEC_AT..TV_USEC_AT].copy_from_slice(&tv_sec.to_ne_bytes());
b[TV_USEC_AT..OFFSET_AT].copy_from_slice(&tv_usec.to_ne_bytes());
b[OFFSET_AT..PULSE_AT].copy_from_slice(&offset_s.to_ne_bytes());
b[PULSE_AT..LEAP_AT].copy_from_slice(&pulse.to_ne_bytes());
b[LEAP_AT..PAD_AT].copy_from_slice(&leap.to_ne_bytes());
b[MAGIC_AT..SAMPLE_LEN].copy_from_slice(&SOCK_MAGIC.to_ne_bytes());
b
}
pub fn pulse_sample(host_ns: u64, utc_ns: i64, realtime_minus_mono_ns: i64) -> [u8; SAMPLE_LEN] {
const NS: i64 = 1_000_000_000;
let tv_ns = host_ns as i64 + realtime_minus_mono_ns;
let sec = tv_ns.div_euclid(NS);
let usec = tv_ns.rem_euclid(NS) / 1_000;
let sent_ns = sec * NS + usec * 1_000;
let offset_s = (utc_ns - sent_ns) as f64 / 1e9;
encode_sample(sec, usec, offset_s, 0, 0)
}
#[derive(Debug)]
pub struct ChronySink {
path: PathBuf,
sock: Option<UnixDatagram>,
sent: u64,
}
impl ChronySink {
pub fn new(path: impl Into<PathBuf>) -> Self {
ChronySink {
path: path.into(),
sock: None,
sent: 0,
}
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn sent(&self) -> u64 {
self.sent
}
pub fn send(&mut self, sample: &[u8; SAMPLE_LEN]) -> bool {
if self.sock.is_none() {
match UnixDatagram::unbound().and_then(|s| s.set_nonblocking(true).map(|()| s)) {
Ok(s) => self.sock = Some(s),
Err(e) => {
tracing::debug!(error = %e, "chrony socket create failed");
return false;
}
}
}
let Some(sock) = &self.sock else {
return false;
};
match sock.send_to(sample, &self.path) {
Ok(_) => {
self.sent += 1;
true
}
Err(e) => {
tracing::debug!(error = %e, path = %self.path.display(), "chrony sample not sent");
false
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn i32_at(b: &[u8], at: usize) -> i32 {
i32::from_ne_bytes(b[at..at + 4].try_into().unwrap())
}
fn tv(b: &[u8]) -> (i64, i64) {
(
i64::from_ne_bytes(b[0..8].try_into().unwrap()),
i64::from_ne_bytes(b[8..16].try_into().unwrap()),
)
}
fn offset(b: &[u8]) -> f64 {
f64::from_ne_bytes(b[16..24].try_into().unwrap())
}
#[test]
fn layout_is_chronys_64_bit_timeval_on_every_target() {
let b = encode_sample(1_760_860_000, 123_456, -0.25, 1, 2);
let mut want = Vec::with_capacity(40);
want.extend(1_760_860_000i64.to_ne_bytes());
want.extend(123_456i64.to_ne_bytes());
want.extend((-0.25f64).to_ne_bytes());
want.extend(1i32.to_ne_bytes());
want.extend(2i32.to_ne_bytes());
want.extend(0i32.to_ne_bytes());
want.extend(0x534f_434bi32.to_ne_bytes());
assert_eq!(b.as_slice(), want.as_slice());
assert_eq!(SAMPLE_LEN, 40);
assert_eq!(MAGIC_AT, 36);
#[repr(C)]
struct Sample64 {
tv_sec: i64,
tv_usec: i64,
offset: f64,
pulse: i32,
leap: i32,
pad: i32,
magic: i32,
}
assert_eq!(std::mem::size_of::<Sample64>(), SAMPLE_LEN);
assert_eq!(std::mem::offset_of!(Sample64, magic), MAGIC_AT);
let s = Sample64 {
tv_sec: 0,
tv_usec: 0,
offset: 0.0,
pulse: 0,
leap: 0,
pad: 0,
magic: 0,
};
let _ = (
s.tv_sec, s.tv_usec, s.offset, s.pulse, s.leap, s.pad, s.magic,
);
}
#[test]
fn pulse_maps_onto_the_system_clock() {
let host = 100_000_000_500u64;
let delta = 1_760_000_000_000_000_000i64;
let utc = host as i64 + delta + 2_000_000;
let b = pulse_sample(host, utc, delta);
assert_eq!(tv(&b), (1_760_000_100, 0));
assert!((offset(&b) - 0.002_000_5).abs() < 1e-9, "{}", offset(&b));
assert_eq!(i32_at(&b, PULSE_AT), 0);
assert_eq!(i32_at(&b, LEAP_AT), 0);
}
#[test]
fn delivers_to_a_bound_socket_and_skips_an_absent_one() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("chrony.sock");
let mut sink = ChronySink::new(&path);
assert!(!sink.send(&encode_sample(1, 2, 0.0, 0, 0)));
let rx = UnixDatagram::bind(&path).unwrap();
assert!(sink.send(&encode_sample(1, 2, 0.5, 0, 0)));
let mut buf = [0u8; 64];
let n = rx.recv(&mut buf).unwrap();
assert_eq!(n, SAMPLE_LEN);
assert_eq!(i32_at(&buf, MAGIC_AT), SOCK_MAGIC);
assert_eq!(sink.sent(), 1);
}
}