#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "utils")]
pub mod utils;
mod log;
mod types;
pub use crate::types::*;
#[cfg(any(feature = "log", feature = "defmt"))]
use crate::log::debug;
use cfg_if::cfg_if;
use core::net;
pub async fn get_time<U, T>(addr: net::SocketAddr, socket: &U, context: NtpContext<T>) -> Result<NtpResult>
where
U: NtpUdpSocket,
T: NtpTimestampGenerator + Copy,
{
let result = sntp_send_request(addr, socket, context).await?;
sntp_process_response(addr, socket, context, result).await
}
pub async fn sntp_send_request<U, T>(
dest: net::SocketAddr,
socket: &U,
context: NtpContext<T>,
) -> Result<SendRequestResult>
where
U: NtpUdpSocket,
T: NtpTimestampGenerator,
{
#[cfg(any(feature = "log", feature = "defmt"))]
debug!("send request - Address: {:?}", dest);
let request = NtpPacket::new(context.timestamp_gen);
send_request(dest, &request, socket).await?;
Ok(SendRequestResult::from(request))
}
pub async fn sntp_process_response<U, T>(
dest: net::SocketAddr,
socket: &U,
mut context: NtpContext<T>,
send_req_result: SendRequestResult,
) -> Result<NtpResult>
where
U: NtpUdpSocket,
T: NtpTimestampGenerator,
{
let mut response_buf = RawNtpPacket::default();
let (response, src) = socket.recv_from(response_buf.0.as_mut()).await?;
context.timestamp_gen.init();
let recv_timestamp = get_ntp_timestamp(&context.timestamp_gen);
#[cfg(any(feature = "log", feature = "defmt"))]
debug!("Response: {}", response);
if dest != src {
return Err(Error::ResponseAddressMismatch);
}
if response != size_of::<NtpPacket>() {
return Err(Error::IncorrectPayload);
}
let result = process_response(send_req_result, response_buf, recv_timestamp);
#[cfg(any(feature = "log", feature = "defmt"))]
if let Ok(r) = &result {
debug!("{:?}", r);
}
result
}
async fn send_request<U>(dest: net::SocketAddr, req: &NtpPacket, socket: &U) -> Result<()>
where
U: NtpUdpSocket,
{
let buf = RawNtpPacket::from(req);
match socket.send_to(&buf.0, dest).await {
Ok(size) => {
if size == buf.0.len() {
Ok(())
} else {
Err(Error::Network)
}
}
Err(_) => Err(Error::Network),
}
}
#[cfg(feature = "sync")]
pub mod sync {
#[cfg(any(feature = "log", feature = "defmt"))]
use crate::log::debug;
use crate::net;
use crate::types::{NtpContext, NtpResult, NtpTimestampGenerator, NtpUdpSocket, Result, SendRequestResult};
pub(crate) const SYNC_EXECUTOR_NUMBER_OF_TASKS: usize = 1;
use miniloop::executor::Executor;
pub fn get_time<U, T>(addr: net::SocketAddr, socket: &U, context: NtpContext<T>) -> Result<NtpResult>
where
U: NtpUdpSocket,
T: NtpTimestampGenerator + Copy,
{
let result = sntp_send_request(addr, socket, context)?;
#[cfg(any(feature = "log", feature = "defmt"))]
debug!("{:?}", result);
sntp_process_response(addr, socket, context, result)
}
pub fn sntp_send_request<U, T>(
dest: net::SocketAddr,
socket: &U,
context: NtpContext<T>,
) -> Result<SendRequestResult>
where
U: NtpUdpSocket,
T: NtpTimestampGenerator + Copy,
{
Executor::<1>::new().block_on(crate::sntp_send_request(dest, socket, context))
}
pub fn sntp_process_response<U, T>(
dest: net::SocketAddr,
socket: &U,
context: NtpContext<T>,
send_req_result: SendRequestResult,
) -> Result<NtpResult>
where
U: NtpUdpSocket,
T: NtpTimestampGenerator + Copy,
{
Executor::<SYNC_EXECUTOR_NUMBER_OF_TASKS>::new().block_on(crate::sntp_process_response(
dest,
socket,
context,
send_req_result,
))
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)]
fn process_response(send_req_result: SendRequestResult, resp: RawNtpPacket, recv_timestamp: u64) -> Result<NtpResult> {
const SNTP_UNICAST: u8 = 4;
const SNTP_BROADCAST: u8 = 5;
const LI_MAX_VALUE: u8 = 3;
let mut packet = NtpPacket::from(resp);
convert_from_network(&mut packet);
cfg_if!(
if #[cfg(any(feature = "log", feature = "defmt"))] {
let debug_packet = DebugNtpPacket::new(&packet, recv_timestamp);
debug!("{:#?}", debug_packet);
}
);
if send_req_result.originate_timestamp != packet.origin_timestamp {
return Err(Error::IncorrectOriginTimestamp);
}
let mode = shifter(packet.li_vn_mode, MODE_MASK, MODE_SHIFT);
let li = shifter(packet.li_vn_mode, LI_MASK, LI_SHIFT);
let resp_version = shifter(packet.li_vn_mode, VERSION_MASK, VERSION_SHIFT);
let req_version = shifter(send_req_result.version, VERSION_MASK, VERSION_SHIFT);
if mode != SNTP_UNICAST && mode != SNTP_BROADCAST {
return Err(Error::IncorrectMode);
}
if li > LI_MAX_VALUE {
return Err(Error::IncorrectLeapIndicator);
}
if req_version != resp_version {
return Err(Error::IncorrectResponseVersion);
}
if packet.stratum == 0 {
return Err(Error::KissOfDeath(KissOfDeathCode::new(packet.ref_id.to_be_bytes())));
}
if packet.stratum >= 16 {
return Err(Error::IncorrectStratumHeaders);
}
let t1 = packet.origin_timestamp;
let t2 = packet.recv_timestamp;
let t3 = packet.tx_timestamp;
let t4 = recv_timestamp;
let units = Units::Microseconds;
let roundtrip = roundtrip_calculate(t1, t2, t3, t4, units);
let offset = offset_calculate(t1, t2, t3, t4, units);
let timestamp = NtpTimestamp::from(packet.tx_timestamp);
#[cfg(any(feature = "log", feature = "defmt"))]
debug!("Roundtrip delay: {} {}. Offset: {} {}", roundtrip, units, offset, units);
Ok(NtpResult::new(
timestamp.seconds as u32,
timestamp.seconds_fraction as u32,
roundtrip,
offset,
packet.stratum,
packet.precision,
))
}
fn shifter(val: u8, mask: u8, shift: u8) -> u8 {
(val & mask) >> shift
}
fn convert_from_network(packet: &mut NtpPacket) {
fn ntohl<T: NtpNum>(val: &T) -> T::Type {
val.ntohl()
}
packet.root_delay = ntohl(&packet.root_delay);
packet.root_dispersion = ntohl(&packet.root_dispersion);
packet.ref_id = ntohl(&packet.ref_id);
packet.ref_timestamp = ntohl(&packet.ref_timestamp);
packet.origin_timestamp = ntohl(&packet.origin_timestamp);
packet.recv_timestamp = ntohl(&packet.recv_timestamp);
packet.tx_timestamp = ntohl(&packet.tx_timestamp);
}
fn convert_delays(sec: u64, fraction: u64, units: u64) -> u64 {
sec * units + fraction * units / u64::from(u32::MAX)
}
fn roundtrip_calculate(t1: u64, t2: u64, t3: u64, t4: u64, units: Units) -> u64 {
let delta = t4.wrapping_sub(t1).saturating_sub(t3.wrapping_sub(t2));
let delta_sec = (delta & SECONDS_MASK) >> 32;
let delta_sec_fraction = delta & SECONDS_FRAC_MASK;
match units {
Units::Milliseconds => convert_delays(delta_sec, delta_sec_fraction, u64::from(MSEC_IN_SEC)),
Units::Microseconds => convert_delays(delta_sec, delta_sec_fraction, u64::from(USEC_IN_SEC)),
}
}
#[allow(clippy::cast_possible_wrap)]
fn offset_calculate(t1: u64, t2: u64, t3: u64, t4: u64, units: Units) -> i64 {
let theta = (t2.wrapping_sub(t1) as i64 / 2).saturating_add(t3.wrapping_sub(t4) as i64 / 2);
let theta_sec = (theta.unsigned_abs() & SECONDS_MASK) >> 32;
let theta_sec_fraction = theta.unsigned_abs() & SECONDS_FRAC_MASK;
match units {
Units::Milliseconds => {
convert_delays(theta_sec, theta_sec_fraction, u64::from(MSEC_IN_SEC)) as i64 * theta.signum()
}
Units::Microseconds => {
convert_delays(theta_sec, theta_sec_fraction, u64::from(USEC_IN_SEC)) as i64 * theta.signum()
}
}
}
fn get_ntp_timestamp<T: NtpTimestampGenerator>(timestamp_gen: &T) -> u64 {
((timestamp_gen.timestamp_sec() + (u64::from(NtpPacket::NTP_TIMESTAMP_DELTA))) << 32)
+ u64::from(timestamp_gen.timestamp_subsec_micros()) * u64::from(u32::MAX) / u64::from(USEC_IN_SEC)
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn fraction_to_milliseconds(sec_fraction: u32) -> u32 {
(u64::from(sec_fraction) * u64::from(MSEC_IN_SEC) / u64::from(u32::MAX)) as u32
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn fraction_to_microseconds(sec_fraction: u32) -> u32 {
(u64::from(sec_fraction) * u64::from(USEC_IN_SEC) / u64::from(u32::MAX)) as u32
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn fraction_to_nanoseconds(sec_fraction: u32) -> u32 {
(u64::from(sec_fraction) * u64::from(NSEC_IN_SEC) / u64::from(u32::MAX)) as u32
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn fraction_to_picoseconds(sec_fraction: u32) -> u64 {
(u128::from(sec_fraction) * u128::from(PSEC_IN_SEC) / u128::from(u32::MAX)) as u64
}
#[cfg(test)]
mod sntpc_ntp_result_tests {
use crate::offset_calculate;
use crate::types::Units;
struct Timestamps(u64, u64, u64, u64);
struct OffsetCalcTestCase {
timestamp: Timestamps,
expected: i64,
}
impl OffsetCalcTestCase {
fn new(t1: u64, t2: u64, t3: u64, t4: u64, expected: i64) -> Self {
OffsetCalcTestCase {
timestamp: Timestamps(t1, t2, t3, t4),
expected,
}
}
fn t1(&self) -> u64 {
self.timestamp.0
}
fn t2(&self) -> u64 {
self.timestamp.1
}
fn t3(&self) -> u64 {
self.timestamp.2
}
fn t4(&self) -> u64 {
self.timestamp.3
}
}
#[test]
fn test_offset_calculate_us() {
let tests = [
OffsetCalcTestCase::new(
16_893_142_954_672_769_962,
16_893_142_959_053_084_959,
16_893_142_959_053_112_968,
16_893_142_954_793_063_406,
1_005_870,
),
OffsetCalcTestCase::new(
16_893_362_966_131_575_843,
16_893_362_966_715_800_791,
16_893_362_966_715_869_584,
16_893_362_967_084_349_913,
25115,
),
OffsetCalcTestCase::new(
16_893_399_716_399_327_198,
16_893_399_716_453_045_029,
16_893_399_716_453_098_083,
16_893_399_716_961_924_964,
-52981,
),
OffsetCalcTestCase::new(
9_487_534_663_484_046_772u64,
16_882_120_099_581_835_046u64,
16_882_120_099_583_884_144u64,
9_487_534_663_651_464_597u64,
1_721_686_086_620_926,
),
];
for t in tests {
let offset = offset_calculate(t.t1(), t.t2(), t.t3(), t.t4(), Units::Microseconds);
let expected = t.expected;
assert_eq!(offset, expected);
}
}
#[test]
fn test_offset_calculate_ms() {
let tests = [
OffsetCalcTestCase::new(
16_893_142_954_672_769_962,
16_893_142_959_053_084_959,
16_893_142_959_053_112_968,
16_893_142_954_793_063_406,
1_005_870 / 1_000,
),
OffsetCalcTestCase::new(
16_893_362_966_131_575_843,
16_893_362_966_715_800_791,
16_893_362_966_715_869_584,
16_893_362_967_084_349_913,
25115 / 1_000,
),
OffsetCalcTestCase::new(
16_893_399_716_399_327_198,
16_893_399_716_453_045_029,
16_893_399_716_453_098_083,
16_893_399_716_961_924_964,
-52981 / 1_000,
),
OffsetCalcTestCase::new(
9_487_534_663_484_046_772u64,
16_882_120_099_581_835_046u64,
16_882_120_099_583_884_144u64,
9_487_534_663_651_464_597u64,
1_721_686_086_620_926 / 1_000,
),
];
for t in tests {
let offset = offset_calculate(t.t1(), t.t2(), t.t3(), t.t4(), Units::Milliseconds);
let expected = t.expected;
assert_eq!(offset, expected);
}
}
#[test]
fn test_units_str_representation() {
assert_eq!(format!("{}", Units::Milliseconds), "ms");
assert_eq!(format!("{}", Units::Microseconds), "us");
}
}