1#![cfg_attr(not(feature = "std"), no_std)]
24#![deny(unsafe_code)]
25#![warn(missing_docs)]
26#![warn(unreachable_pub)]
27
28#[cfg(feature = "alloc")]
29extern crate alloc;
30
31pub mod error;
33pub mod extension;
38pub mod protocol;
39pub mod unix_time;
44
45#[cfg(any(feature = "tokio", feature = "smol-runtime"))]
50pub mod filter;
51
52#[cfg(any(feature = "tokio", feature = "smol-runtime"))]
54pub mod client_common;
55
56#[cfg(any(feature = "nts", feature = "nts-smol"))]
58pub(crate) mod nts_common;
59
60#[cfg(feature = "tokio")]
69pub mod client;
70
71#[cfg(feature = "nts")]
81pub mod nts;
82
83#[cfg(feature = "clock")]
95pub mod clock;
96
97#[cfg(feature = "tokio")]
108pub mod async_ntp;
109
110#[cfg(feature = "smol-runtime")]
121pub mod smol_ntp;
122
123#[cfg(feature = "smol-runtime")]
132pub mod smol_client;
133
134#[cfg(feature = "nts-smol")]
146pub mod smol_nts;
147
148#[cfg(feature = "std")]
153use log::debug;
154#[cfg(feature = "std")]
155use protocol::{ConstPackedSizeBytes, ReadBytes, WriteBytes};
156#[cfg(feature = "std")]
157use std::io;
158#[cfg(feature = "std")]
159use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
160#[cfg(feature = "std")]
161use std::ops::Deref;
162#[cfg(feature = "std")]
163use std::time::Duration;
164
165#[cfg(feature = "std")]
169pub(crate) fn bind_addr_for(target: &SocketAddr) -> &'static str {
170 match target {
171 SocketAddr::V4(_) => "0.0.0.0:0",
172 SocketAddr::V6(_) => "[::]:0",
173 }
174}
175
176#[cfg(feature = "std")]
205#[derive(Clone, Copy, Debug)]
206pub struct KissOfDeathError {
207 pub code: protocol::KissOfDeath,
209}
210
211#[cfg(feature = "std")]
212impl std::fmt::Display for KissOfDeathError {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 match self.code {
215 protocol::KissOfDeath::Deny => {
216 write!(
217 f,
218 "server sent Kiss-o'-Death DENY: access denied, stop querying this server"
219 )
220 }
221 protocol::KissOfDeath::Rstr => {
222 write!(
223 f,
224 "server sent Kiss-o'-Death RSTR: access restricted, stop querying this server"
225 )
226 }
227 protocol::KissOfDeath::Rate => {
228 write!(f, "server sent Kiss-o'-Death RATE: reduce polling interval")
229 }
230 }
231 }
232}
233
234#[cfg(feature = "std")]
235impl std::error::Error for KissOfDeathError {}
236
237#[cfg(feature = "std")]
243#[derive(Clone, Copy, Debug)]
244pub struct NtpResult {
245 pub packet: protocol::Packet,
247 pub destination_timestamp: protocol::TimestampFormat,
251 pub offset_seconds: f64,
262 pub delay_seconds: f64,
266}
267
268#[cfg(feature = "std")]
269impl Deref for NtpResult {
270 type Target = protocol::Packet;
271 fn deref(&self) -> &Self::Target {
272 &self.packet
273 }
274}
275
276#[cfg(feature = "std")]
278fn instant_to_f64(instant: &unix_time::Instant) -> f64 {
279 instant.secs() as f64 + (instant.subsec_nanos() as f64 / 1e9)
280}
281
282#[cfg(feature = "std")]
285pub(crate) fn compute_offset_delay(
286 t1: &unix_time::Instant,
287 t2: &unix_time::Instant,
288 t3: &unix_time::Instant,
289 t4: &unix_time::Instant,
290) -> (f64, f64) {
291 let t1 = instant_to_f64(t1);
292 let t2 = instant_to_f64(t2);
293 let t3 = instant_to_f64(t3);
294 let t4 = instant_to_f64(t4);
295 let offset = ((t2 - t1) + (t3 - t4)) / 2.0;
296 let delay = (t4 - t1) - (t3 - t2);
297 (offset, delay)
298}
299
300#[cfg(feature = "std")]
304pub(crate) fn build_request_packet() -> io::Result<(
305 [u8; protocol::Packet::PACKED_SIZE_BYTES],
306 protocol::TimestampFormat,
307)> {
308 let packet = protocol::Packet {
309 leap_indicator: protocol::LeapIndicator::default(),
310 version: protocol::Version::V4,
311 mode: protocol::Mode::Client,
312 stratum: protocol::Stratum::UNSPECIFIED,
313 poll: 0,
314 precision: 0,
315 root_delay: protocol::ShortFormat::default(),
316 root_dispersion: protocol::ShortFormat::default(),
317 reference_id: protocol::ReferenceIdentifier::PrimarySource(protocol::PrimarySource::Null),
318 reference_timestamp: protocol::TimestampFormat::default(),
319 origin_timestamp: protocol::TimestampFormat::default(),
320 receive_timestamp: protocol::TimestampFormat::default(),
321 transmit_timestamp: unix_time::Instant::now().into(),
322 };
323 let t1 = packet.transmit_timestamp;
324 let mut send_buf = [0u8; protocol::Packet::PACKED_SIZE_BYTES];
325 (&mut send_buf[..]).write_bytes(packet)?;
326 Ok((send_buf, t1))
327}
328
329#[cfg(feature = "std")]
340pub(crate) fn parse_and_validate_response(
341 recv_buf: &[u8],
342 recv_len: usize,
343 src_addr: SocketAddr,
344 resolved_addrs: &[SocketAddr],
345) -> io::Result<(protocol::Packet, protocol::TimestampFormat)> {
346 let t4_instant = unix_time::Instant::now();
348 let t4: protocol::TimestampFormat = t4_instant.into();
349
350 if !resolved_addrs.iter().any(|a| a.ip() == src_addr.ip()) {
352 return Err(io::Error::new(
353 io::ErrorKind::InvalidData,
354 "response from unexpected source address",
355 ));
356 }
357
358 if recv_len < protocol::Packet::PACKED_SIZE_BYTES {
360 return Err(io::Error::new(
361 io::ErrorKind::InvalidData,
362 "NTP response too short",
363 ));
364 }
365
366 let response: protocol::Packet =
368 (&recv_buf[..protocol::Packet::PACKED_SIZE_BYTES]).read_bytes()?;
369
370 if response.mode != protocol::Mode::Server {
372 return Err(io::Error::new(
373 io::ErrorKind::InvalidData,
374 "unexpected response mode (expected Server)",
375 ));
376 }
377
378 if let protocol::ReferenceIdentifier::KissOfDeath(kod) = response.reference_id {
380 return Err(io::Error::new(
381 io::ErrorKind::ConnectionRefused,
382 KissOfDeathError { code: kod },
383 ));
384 }
385
386 if response.transmit_timestamp.seconds == 0 && response.transmit_timestamp.fraction == 0 {
388 return Err(io::Error::new(
389 io::ErrorKind::InvalidData,
390 "server transmit timestamp is zero",
391 ));
392 }
393
394 if response.leap_indicator == protocol::LeapIndicator::Unknown
396 && response.stratum != protocol::Stratum::UNSPECIFIED
397 {
398 return Err(io::Error::new(
399 io::ErrorKind::InvalidData,
400 "server reports unsynchronized clock",
401 ));
402 }
403
404 Ok((response, t4))
405}
406
407#[cfg(feature = "std")]
413pub(crate) fn validate_response(
414 recv_buf: &[u8],
415 recv_len: usize,
416 src_addr: SocketAddr,
417 resolved_addrs: &[SocketAddr],
418 t1: &protocol::TimestampFormat,
419) -> io::Result<NtpResult> {
420 let (response, t4) = parse_and_validate_response(recv_buf, recv_len, src_addr, resolved_addrs)?;
421
422 if response.origin_timestamp != *t1 {
424 return Err(io::Error::new(
425 io::ErrorKind::InvalidData,
426 "origin timestamp mismatch: response does not match our request",
427 ));
428 }
429
430 let t4_instant = unix_time::Instant::from(t4);
432 let t1_instant = unix_time::timestamp_to_instant(*t1, &t4_instant);
433 let t2_instant = unix_time::timestamp_to_instant(response.receive_timestamp, &t4_instant);
434 let t3_instant = unix_time::timestamp_to_instant(response.transmit_timestamp, &t4_instant);
435
436 let (offset_seconds, delay_seconds) =
437 compute_offset_delay(&t1_instant, &t2_instant, &t3_instant, &t4_instant);
438
439 Ok(NtpResult {
440 packet: response,
441 destination_timestamp: t4,
442 offset_seconds,
443 delay_seconds,
444 })
445}
446
447#[cfg(feature = "std")]
489pub fn request<A: ToSocketAddrs>(addr: A) -> io::Result<NtpResult> {
490 request_with_timeout(addr, Duration::from_secs(5))
491}
492
493#[cfg(feature = "std")]
536pub fn request_with_timeout<A: ToSocketAddrs>(addr: A, timeout: Duration) -> io::Result<NtpResult> {
537 let resolved_addrs: Vec<SocketAddr> = addr.to_socket_addrs()?.collect();
539 if resolved_addrs.is_empty() {
540 return Err(io::Error::new(
541 io::ErrorKind::InvalidInput,
542 "address resolved to no socket addresses",
543 ));
544 }
545 let target_addr = resolved_addrs[0];
546
547 let (send_buf, t1) = build_request_packet()?;
549
550 let sock = UdpSocket::bind(bind_addr_for(&target_addr))?;
552 sock.set_read_timeout(Some(timeout))?;
553 sock.set_write_timeout(Some(timeout))?;
554
555 let sz = sock.send_to(&send_buf, target_addr)?;
557 debug!("{:?}", sock.local_addr());
558 debug!("sent: {}", sz);
559
560 let mut recv_buf = [0u8; 1024];
562 let (recv_len, src_addr) = sock.recv_from(&mut recv_buf[..])?;
563 debug!("recv: {} bytes from {:?}", recv_len, src_addr);
564
565 validate_response(&recv_buf, recv_len, src_addr, &resolved_addrs, &t1)
567}
568
569#[cfg(all(test, feature = "std"))]
570#[test]
571fn test_request_nist() {
572 match request_with_timeout("time.nist.gov:123", Duration::from_secs(10)) {
573 Ok(_) => {}
574 Err(e) if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut => {
575 eprintln!("skipping test_request_nist: NTP port unreachable ({e})");
576 }
577 Err(e) => panic!("unexpected error from time.nist.gov: {e}"),
578 }
579}
580
581#[cfg(all(test, feature = "std"))]
582#[test]
583fn test_request_nist_alt() {
584 match request_with_timeout("time-a-g.nist.gov:123", Duration::from_secs(10)) {
585 Ok(_) => {}
586 Err(e) if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut => {
587 eprintln!("skipping test_request_nist_alt: NTP port unreachable ({e})");
588 }
589 Err(e) => panic!("unexpected error from time-a-g.nist.gov: {e}"),
590 }
591}
592
593#[cfg(all(test, feature = "std"))]
594mod tests {
595 use super::*;
596
597 #[test]
600 fn test_offset_delay_symmetric() {
601 let t1 = unix_time::Instant::new(0, 0);
605 let t2 = unix_time::Instant::new(0, 500_000_000);
606 let t3 = unix_time::Instant::new(0, 500_000_000);
607 let t4 = unix_time::Instant::new(1, 0);
608 let (offset, delay) = compute_offset_delay(&t1, &t2, &t3, &t4);
609 assert!(offset.abs() < 1e-9, "expected ~0 offset, got {offset}");
610 assert!(
611 (delay - 1.0).abs() < 1e-9,
612 "expected 1.0 delay, got {delay}"
613 );
614 }
615
616 #[test]
617 fn test_offset_delay_local_behind() {
618 let t1 = unix_time::Instant::new(0, 0);
622 let t2 = unix_time::Instant::new(1, 500_000_000);
623 let t3 = unix_time::Instant::new(1, 500_000_000);
624 let t4 = unix_time::Instant::new(1, 0);
625 let (offset, delay) = compute_offset_delay(&t1, &t2, &t3, &t4);
626 assert!(
627 (offset - 1.0).abs() < 1e-9,
628 "expected 1.0 offset, got {offset}"
629 );
630 assert!(
631 (delay - 1.0).abs() < 1e-9,
632 "expected 1.0 delay, got {delay}"
633 );
634 }
635
636 #[test]
637 fn test_offset_delay_local_ahead() {
638 let t1 = unix_time::Instant::new(10, 0);
642 let t2 = unix_time::Instant::new(9, 250_000_000);
643 let t3 = unix_time::Instant::new(9, 750_000_000);
644 let t4 = unix_time::Instant::new(11, 0);
645 let (offset, delay) = compute_offset_delay(&t1, &t2, &t3, &t4);
646 assert!(
647 (offset - (-1.0)).abs() < 1e-9,
648 "expected -1.0 offset, got {offset}"
649 );
650 assert!(
651 (delay - 0.5).abs() < 1e-9,
652 "expected 0.5 delay, got {delay}"
653 );
654 }
655
656 #[test]
657 fn test_offset_delay_zero_processing_time() {
658 let t1 = unix_time::Instant::new(0, 0);
662 let t2 = unix_time::Instant::new(0, 50_000_000);
663 let t3 = unix_time::Instant::new(0, 50_000_000);
664 let t4 = unix_time::Instant::new(0, 100_000_000);
665 let (offset, delay) = compute_offset_delay(&t1, &t2, &t3, &t4);
666 assert!(offset.abs() < 1e-9, "expected ~0 offset, got {offset}");
667 assert!(
668 (delay - 0.1).abs() < 1e-9,
669 "expected 0.1 delay, got {delay}"
670 );
671 }
672
673 #[test]
676 fn test_build_request_packet_structure() {
677 let (buf, t1) = build_request_packet().unwrap();
678
679 let pkt: protocol::Packet = (&buf[..protocol::Packet::PACKED_SIZE_BYTES])
681 .read_bytes()
682 .unwrap();
683 assert_eq!(pkt.version, protocol::Version::V4);
684 assert_eq!(pkt.mode, protocol::Mode::Client);
685 assert_eq!(pkt.stratum, protocol::Stratum::UNSPECIFIED);
686 assert_eq!(pkt.transmit_timestamp, t1);
687 assert!(t1.seconds != 0 || t1.fraction != 0);
689 }
690
691 #[test]
692 fn test_build_request_packet_size() {
693 let (buf, _) = build_request_packet().unwrap();
694 assert_eq!(buf.len(), protocol::Packet::PACKED_SIZE_BYTES);
695 assert_eq!(buf.len(), 48);
696 }
697
698 fn make_server_response(
702 mode: protocol::Mode,
703 li: protocol::LeapIndicator,
704 stratum: protocol::Stratum,
705 ref_id: protocol::ReferenceIdentifier,
706 transmit_secs: u32,
707 ) -> [u8; 48] {
708 let pkt = protocol::Packet {
709 leap_indicator: li,
710 version: protocol::Version::V4,
711 mode,
712 stratum,
713 poll: 6,
714 precision: -20,
715 root_delay: protocol::ShortFormat::default(),
716 root_dispersion: protocol::ShortFormat::default(),
717 reference_id: ref_id,
718 reference_timestamp: protocol::TimestampFormat::default(),
719 origin_timestamp: protocol::TimestampFormat {
720 seconds: 100,
721 fraction: 0,
722 },
723 receive_timestamp: protocol::TimestampFormat {
724 seconds: 3_913_056_000,
725 fraction: 0,
726 },
727 transmit_timestamp: protocol::TimestampFormat {
728 seconds: transmit_secs,
729 fraction: 1,
730 },
731 };
732 let mut buf = [0u8; 48];
733 (&mut buf[..]).write_bytes(pkt).unwrap();
734 buf
735 }
736
737 fn valid_server_buf() -> [u8; 48] {
738 make_server_response(
739 protocol::Mode::Server,
740 protocol::LeapIndicator::NoWarning,
741 protocol::Stratum(2),
742 protocol::ReferenceIdentifier::SecondaryOrClient([127, 0, 0, 1]),
743 3_913_056_001,
744 )
745 }
746
747 fn src_addr() -> SocketAddr {
748 "127.0.0.1:123".parse().unwrap()
749 }
750
751 #[test]
752 fn test_validate_accepts_valid_response() {
753 let buf = valid_server_buf();
754 let addrs = vec![src_addr()];
755 let result = parse_and_validate_response(&buf, 48, src_addr(), &addrs);
756 assert!(result.is_ok());
757 let (pkt, _t4) = result.unwrap();
758 assert_eq!(pkt.mode, protocol::Mode::Server);
759 }
760
761 #[test]
762 fn test_validate_rejects_wrong_source_ip() {
763 let buf = valid_server_buf();
764 let addrs = vec!["10.0.0.1:123".parse().unwrap()];
765 let result = parse_and_validate_response(&buf, 48, src_addr(), &addrs);
766 assert!(result.is_err());
767 assert!(
768 result
769 .unwrap_err()
770 .to_string()
771 .contains("unexpected source")
772 );
773 }
774
775 #[test]
776 fn test_validate_rejects_short_packet() {
777 let buf = valid_server_buf();
778 let addrs = vec![src_addr()];
779 let result = parse_and_validate_response(&buf, 47, src_addr(), &addrs);
780 assert!(result.is_err());
781 assert!(result.unwrap_err().to_string().contains("too short"));
782 }
783
784 #[test]
785 fn test_validate_rejects_client_mode() {
786 let buf = make_server_response(
787 protocol::Mode::Client,
788 protocol::LeapIndicator::NoWarning,
789 protocol::Stratum(2),
790 protocol::ReferenceIdentifier::SecondaryOrClient([127, 0, 0, 1]),
791 3_913_056_001,
792 );
793 let addrs = vec![src_addr()];
794 let result = parse_and_validate_response(&buf, 48, src_addr(), &addrs);
795 assert!(result.is_err());
796 assert!(
797 result
798 .unwrap_err()
799 .to_string()
800 .contains("unexpected response mode")
801 );
802 }
803
804 #[test]
805 fn test_validate_rejects_kiss_of_death() {
806 let buf = make_server_response(
807 protocol::Mode::Server,
808 protocol::LeapIndicator::NoWarning,
809 protocol::Stratum::UNSPECIFIED,
810 protocol::ReferenceIdentifier::KissOfDeath(protocol::KissOfDeath::Deny),
811 3_913_056_001,
812 );
813 let addrs = vec![src_addr()];
814 let result = parse_and_validate_response(&buf, 48, src_addr(), &addrs);
815 let err = result.unwrap_err();
816 assert_eq!(err.kind(), io::ErrorKind::ConnectionRefused);
817 let kod = err
818 .get_ref()
819 .unwrap()
820 .downcast_ref::<KissOfDeathError>()
821 .unwrap();
822 assert!(matches!(kod.code, protocol::KissOfDeath::Deny));
823 }
824
825 #[test]
826 fn test_validate_rejects_zero_transmit() {
827 let pkt = protocol::Packet {
829 leap_indicator: protocol::LeapIndicator::NoWarning,
830 version: protocol::Version::V4,
831 mode: protocol::Mode::Server,
832 stratum: protocol::Stratum(2),
833 poll: 6,
834 precision: -20,
835 root_delay: protocol::ShortFormat::default(),
836 root_dispersion: protocol::ShortFormat::default(),
837 reference_id: protocol::ReferenceIdentifier::SecondaryOrClient([127, 0, 0, 1]),
838 reference_timestamp: protocol::TimestampFormat::default(),
839 origin_timestamp: protocol::TimestampFormat::default(),
840 receive_timestamp: protocol::TimestampFormat::default(),
841 transmit_timestamp: protocol::TimestampFormat {
842 seconds: 0,
843 fraction: 0,
844 },
845 };
846 let mut raw = [0u8; 48];
847 (&mut raw[..]).write_bytes(pkt).unwrap();
848 let addrs = vec![src_addr()];
849 let result = parse_and_validate_response(&raw, 48, src_addr(), &addrs);
850 assert!(result.is_err());
851 assert!(
852 result
853 .unwrap_err()
854 .to_string()
855 .contains("transmit timestamp is zero")
856 );
857 }
858
859 #[test]
860 fn test_validate_rejects_unsynchronized() {
861 let buf = make_server_response(
862 protocol::Mode::Server,
863 protocol::LeapIndicator::Unknown,
864 protocol::Stratum(2), protocol::ReferenceIdentifier::SecondaryOrClient([127, 0, 0, 1]),
866 3_913_056_001,
867 );
868 let addrs = vec![src_addr()];
869 let result = parse_and_validate_response(&buf, 48, src_addr(), &addrs);
870 assert!(result.is_err());
871 assert!(result.unwrap_err().to_string().contains("unsynchronized"));
872 }
873
874 #[test]
875 fn test_validate_allows_li_unknown_stratum_zero() {
876 let buf = make_server_response(
879 protocol::Mode::Server,
880 protocol::LeapIndicator::Unknown,
881 protocol::Stratum::UNSPECIFIED,
882 protocol::ReferenceIdentifier::PrimarySource(protocol::PrimarySource::Gps),
883 3_913_056_001,
884 );
885 let addrs = vec![src_addr()];
886 let result = parse_and_validate_response(&buf, 48, src_addr(), &addrs);
887 assert!(result.is_ok());
888 }
889
890 #[test]
891 fn test_validate_accepts_different_port() {
892 let buf = valid_server_buf();
894 let addrs = vec!["127.0.0.1:456".parse().unwrap()];
895 let result = parse_and_validate_response(&buf, 48, src_addr(), &addrs);
896 assert!(result.is_ok());
897 }
898
899 #[test]
902 fn test_kod_display_deny() {
903 let kod = KissOfDeathError {
904 code: protocol::KissOfDeath::Deny,
905 };
906 assert!(kod.to_string().contains("DENY"));
907 }
908
909 #[test]
910 fn test_kod_display_rstr() {
911 let kod = KissOfDeathError {
912 code: protocol::KissOfDeath::Rstr,
913 };
914 assert!(kod.to_string().contains("RSTR"));
915 }
916
917 #[test]
918 fn test_kod_display_rate() {
919 let kod = KissOfDeathError {
920 code: protocol::KissOfDeath::Rate,
921 };
922 assert!(kod.to_string().contains("RATE"));
923 }
924}