From 4edc36f667992fca705cf3e2f1ee24b40889f5b0 Mon Sep 17 00:00:00 2001 From: FObersteiner Date: Sun, 16 Jun 2024 17:50:15 +0200 Subject: [PATCH] to v0.0.11 --- .gitignore | 1 + README.md | 5 +- build.zig | 2 +- build.zig.zon | 2 +- docs/NTP_notes.md | 152 ++++++++++++++++++++++++++-------------- docs/change.log | 15 ++++ src/main.zig | 53 ++++++++++---- src/ntp.zig | 164 ++++++++++++++++++++++++++++++-------------- src/prettyprint.zig | 31 ++++----- 9 files changed, 288 insertions(+), 137 deletions(-) diff --git a/.gitignore b/.gitignore index 4e2bd88..0b4ebfd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ zig-cache/ zig-out/ autodoc/ .old/ +perf.data diff --git a/README.md b/README.md index 4eebddd..a35c936 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ # NTP Client -Command line app to query an NTP server, to verify your OS clock setting. The original repository is hosted [on Codeberg](https://codeberg.org/FObersteiner/ntp_client). +Command line app to query an NTP server, to verify your OS clock setting. + +- [on Codeberg](https://codeberg.org/FObersteiner/ntp_client) +- [on github](https://github.com/FObersteiner/ntp-client) ```text Usage: ntp_client [options] diff --git a/build.zig b/build.zig index 282406e..748c57d 100755 --- a/build.zig +++ b/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); const log = std.log.scoped(.ntp_client_build); -const client_version = std.SemanticVersion{ .major = 0, .minor = 0, .patch = 9 }; +const client_version = std.SemanticVersion{ .major = 0, .minor = 0, .patch = 11 }; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); diff --git a/build.zig.zon b/build.zig.zon index aef9f38..a35fe2c 100755 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = "ntp_client", - .version = "0.0.9", + .version = "0.0.11", .dependencies = .{ .zdt = .{ .url = "https://codeberg.org/FObersteiner/zdt/archive/v0.1.3.tar.gz", diff --git a/docs/NTP_notes.md b/docs/NTP_notes.md index 28ea7fa..2f1ad2d 100644 --- a/docs/NTP_notes.md +++ b/docs/NTP_notes.md @@ -8,56 +8,6 @@ - - -NTP v4 data format, from : - -```text -0 1 2 3 -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -|LI | VN |Mode | Stratum | Poll | Precision | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| Root Delay | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| Root Dispersion | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| Reference ID | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -+ Reference Timestamp (64) + -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -+ Origin Timestamp (64) + -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -+ Receive Timestamp (64) + -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -+ Transmit Timestamp (64) + -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -. . -. Extension Field 1 (variable) . -. . -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -. . -. Extension Field 2 (variable) . -. . -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| Key Identifier | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -| dgst (128) | -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -``` - ## limits Only fields up to and including Transmit Timestamp are used further on. @@ -80,4 +30,104 @@ Extensions are not supported (yet). ## on Linux -On a Linux running timedatectl, check via `timedatectl timesync-status` +On a Linux running `timedatectl`, check via `timedatectl timesync-status` + +## Specs + +NTP v4 data format, + +### Packet + +```text + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |LI | VN |Mode | Stratum | Poll | Precision | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Root Delay | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Root Dispersion | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reference ID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Reference Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Origin Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Receive Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Transmit Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + . . + . Extension Field 1 (variable) . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + . . + . Extension Field 2 (variable) . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Key Identifier | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | dgst (128) | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +``` + +### Kiss Codes + +```text + +------+------------------------------------------------------------+ + | Code | Meaning | + +------+------------------------------------------------------------+ + | ACST | The association belongs to a unicast server. | + | AUTH | Server authentication failed. | + | AUTO | Autokey sequence failed. | + | BCST | The association belongs to a broadcast server. | + | CRYP | Cryptographic authentication or identification failed. | + | DENY | Access denied by remote server. | + | DROP | Lost peer in symmetric mode. | + | RSTR | Access denied due to local policy. | + | INIT | The association has not yet synchronized for the first | + | | time. | + | MCST | The association belongs to a dynamically discovered server.| + | NKEY | No key found. Either the key was never installed or is | + | | not trusted. | + | RATE | Rate exceeded. The server has temporarily denied access | + | | because the client exceeded the rate threshold. | + | RMOT | Alteration of association from a remote host running | + | | ntpdc. | + | STEP | A step change in system time has occurred, but the | + | | association has not yet resynchronized. | + +------+------------------------------------------------------------+ +``` + +### Globals / Boundaries + +```text + +-----------+-------+----------------------------------+ + | Name | Value | Description | + +-----------+-------+----------------------------------+ + | PORT | 123 | NTP port number | + | VERSION | 4 | NTP version number | + | TOLERANCE | 15e-6 | frequency tolerance PHI (s/s) | + | MINPOLL | 4 | minimum poll exponent (16 s) | + | MAXPOLL | 17 | maximum poll exponent (36 h) | + | MAXDISP | 16 | maximum dispersion (16 s) | + | MINDISP | .005 | minimum dispersion increment (s) | + | MAXDIST | 1 | distance threshold (1 s) | + | MAXSTRAT | 16 | maximum stratum number | + +-----------+-------+----------------------------------+ +``` diff --git a/docs/change.log b/docs/change.log index 1d0ff83..c4966d8 100644 --- a/docs/change.log +++ b/docs/change.log @@ -1,5 +1,20 @@ # Changelog +## 2024-06-16, v0.0.11 + +- keep client timestamps local to client +- send random number as client xmt + +## 2024-06-16, v0.0.10 + +- add 5 second timeout if server doesn't answer +- revise pprint to console +- more tests, some new functionality (correctTime, refIDprintable) + +## 2024-06-15, v0.0.9 + +- dependency update + ## 2024-06-14, v0.0.8 - add IPv6 support diff --git a/src/main.zig b/src/main.zig index 1eff391..442893f 100755 --- a/src/main.zig +++ b/src/main.zig @@ -18,9 +18,11 @@ test { _ = ntp; } -//----------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------ +const timeout_sec: isize = 5; // wait-for-reply timeout const mtu: usize = 1024; // buffer size for transmission -//----------------------------------------------------------------------------- +const poll_int: u8 = 0; // one-shot program, do not set poll interval +// ------------------------------------------------------------------------------------ pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); @@ -53,7 +55,7 @@ pub fn main() !void { } } - // ------------------------------------------------------------------------ + // --- prepare connection --------------------------------------------------------- // resolve hostname const addrlist = net.getAddressList(allocator, cli.flags.server, port) catch { @@ -67,8 +69,6 @@ pub fn main() !void { const sock = try posix.socket( addr_src.any.family, - // Notes on flags: - // NONBLOCK can be used to create timeout behavior. // CLOEXEC not strictly needed here; see open(2) man page. posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, posix.IPPROTO.UDP, @@ -76,18 +76,33 @@ pub fn main() !void { try posix.bind(sock, &addr_src.any, addr_src.getOsSockLen()); defer posix.close(sock); + if (timeout_sec != 0) { // make this configurable ? ...0 would mean no timeout + try posix.setsockopt( + sock, + posix.SOL.SOCKET, + posix.SO.RCVTIMEO, + &std.mem.toBytes(posix.timespec{ .tv_sec = timeout_sec, .tv_nsec = 0 }), + ); + } + + // --- query server(s) ------------------------------------------------------------ + var buf: [mtu]u8 = std.mem.zeroes([mtu]u8); - iter_addrs: for (addrlist.addrs) |dst| { + iter_addrs: for (addrlist.addrs, 0..) |dst, i| { var dst_addr_sock: posix.sockaddr = undefined; // must not use dst.any var dst_addr_len: posix.socklen_t = dst.getOsSockLen(); - ntp.Packet.toBytesBuffer(proto_vers, true, &buf); + ntp.Packet.toBytesBuffer(proto_vers, poll_int, &buf); + + // packet created! + const T1: ntp.Time = ntp.Time.fromUnixNanos(@as(u64, @intCast(std.time.nanoTimestamp()))); + _ = posix.sendto( sock, buf[0..ntp.packet_len], 0, - &dst.any, // &dst_addr_sock, + &dst.any, dst_addr_len, ) catch |err| switch (err) { error.AddressFamilyNotSupported => { @@ -100,23 +115,33 @@ pub fn main() !void { else => |e| return e, }; - const n_recv: usize = try posix.recvfrom( + const n_recv: usize = posix.recvfrom( sock, buf[0..], 0, &dst_addr_sock, &dst_addr_len, - ); + ) catch |err| switch (err) { + error.WouldBlock => { + println("Error: connection timed out", .{}); + if (i < addrlist.addrs.len - 1) println("Try next server.", .{}); + continue :iter_addrs; + }, + else => |e| return e, + }; - const ts_dst = std.time.nanoTimestamp(); + // reply received! + const T4: ntp.Time = ntp.Time.fromUnixNanos(@as(u64, @intCast(std.time.nanoTimestamp()))); if (n_recv != ntp.packet_len) { - println("invalid reply length, try next server.", .{}); + println("Error: invalid reply length", .{}); + if (i < addrlist.addrs.len - 1) println("Try next server.", .{}); continue :iter_addrs; } const p_result: ntp.Packet = ntp.Packet.parse(buf[0..ntp.packet_len].*); - const result: ntp.Result = ntp.Result.fromPacket(p_result, ts_dst); + const result: ntp.Result = ntp.Result.fromPacket(p_result, T1, T4); + println("Server name: {s}", .{cli.flags.server}); println("Server address: {any}", .{dst}); println("---", .{}); @@ -127,7 +152,7 @@ pub fn main() !void { } } -//----------------------------------------------------------------------------- +// --- helpers ------------------------------------------------------------------------ /// Print to stdout with trailing newline, unbuffered, and silently returning on failure. fn println(comptime fmt: []const u8, args: anytype) void { diff --git a/src/ntp.zig b/src/ntp.zig index ab118ac..4829e25 100755 --- a/src/ntp.zig +++ b/src/ntp.zig @@ -1,6 +1,7 @@ //! NTP client library const std = @import("std"); const mem = std.mem; +const rand = std.crypto.random; const print = std.debug.print; const testing = std.testing; const assert = std.debug.assert; @@ -39,7 +40,7 @@ pub fn precisionToNanos(prc: i8) u64 { /// In an NTP packet, this represents a duration since the NTP epoch. /// Era 0 starts at zero hours on 1900-01-01. /// -const Time = struct { +pub const Time = struct { t: u64 = 0, // upper 32 bits: seconds, lower 32 bits: fraction /// from value as received in NTP packet. @@ -62,7 +63,7 @@ const Time = struct { return sec * ns_per_s + nsec; } - // NOTE : addition is not permitted by NTP since might overflow. + // Addition is not permitted by NTP since might overflow. /// NTP time subtraction which works across era bounds; /// works as long as the absolute difference between A and B is < 2^(n-1) (~68 years for n=32). @@ -180,7 +181,7 @@ test "Time Unix" { } /// Duration with lower resolution and smaller range -const TimeShort = struct { +pub const TimeShort = struct { t: u32 = 0, // upper 16 bits: seconds, lower 16 bits: fraction /// from value as received in NTP packet. @@ -217,6 +218,9 @@ test "TimeShort" { t = TimeShort{ .t = 0x00018000 }; try testing.expectEqual(@as(u64, 1_500_000_000), t.decode()); + + t = TimeShort{ .t = 0xffff0000 }; + try testing.expectEqual(@as(u64, 65535 * ns_per_s), t.decode()); } /// Struct equivalent of the NPT packet definition. @@ -226,36 +230,39 @@ pub const Packet = packed struct { li_vers_mode: u8, // 2 bits leap second indicator, 3 bits protocol version, 3 bits mode stratum: u8 = 0, poll: u8 = 0, - precision: i8 = 0, + precision: i8 = 0x20, root_delay: u32 = 0, root_dispersion: u32 = 0, ref_id: u32 = 0, - ts_ref: u64 = 0, // combines seconds and fraction - ts_org: u64 = 0, - ts_rec: u64 = 0, - ts_xmt: u64 = 0, + ts_ref: u64 = 0, // combines seconds and fraction ---v + ts_org: u64 = 0, // + ts_rec: u64 = 0, // + ts_xmt: u64 = 0, // ---^ // extension field #1 // extension field #2 // key identifier // digest // Create a client mode NTP packet to query the time from a server. - // Can directly set the transmit timestamp (xmt), - // which will be returned as origin timestamp by the server. - fn _init(version: u8, set_xmt: bool) Packet { - const ts = if (set_xmt) - Time.fromUnixNanos(@as(u64, @intCast(std.time.nanoTimestamp()))) - else - Time{}; - return .{ .li_vers_mode = 0 << 6 | version << 3 | client_mode, .ts_xmt = ts.t }; + // Random bytes are used as client transmit timestamp (xmt), + // see . + // For a single query, the poll intervall should be 0. + fn _init(version: u8, poll_int: u8) Packet { + var b: [8]u8 = undefined; + rand.bytes(&b); + return .{ + .li_vers_mode = 0 << 6 | version << 3 | client_mode, + .poll = poll_int, + .ts_xmt = @bitCast(b), + }; } /// Create an NTP packet and fill it into a bytes buffer. /// 'buf' must be sufficiently large to store ntp.packet_len bytes. /// Considers endianess; fields > 1 byte are in big endian byte order. - pub fn toBytesBuffer(version: u8, set_xmt: bool, buf: []u8) void { + pub fn toBytesBuffer(version: u8, poll_int: u8, buf: []u8) void { assert(buf.len >= packet_len); - var p: Packet = Packet._init(version, set_xmt); + var p: Packet = Packet._init(version, poll_int); p.ts_xmt = mem.nativeToBig(u64, p.ts_xmt); const ntp_bytes: [packet_len]u8 = @bitCast(p); mem.copyForwards(u8, buf, ntp_bytes[0..]); @@ -277,7 +284,8 @@ pub const Packet = packed struct { }; test "Packet" { - const p = Packet._init(3, true); + const p = Packet._init(3, 0); + try testing.expectEqual(@as(i8, 32), p.precision); const b: [packet_len]u8 = @bitCast(p); try testing.expectEqual(@as(u8, 27), b[0]); } @@ -306,14 +314,16 @@ pub const Result = struct { /// T4, when the packet was received and processed T4: Time = .{}, - /// offset of the local machine relative to the server - theta: i64 = 0, - /// round-trip delay - delta: i64 = 0, - /// syncronization distance, i.e. error estimate of server clock - lambda: u64 = 0, + /// offset of the local machine vs. the server + offset: i64 = 0, + /// round-trip delay (network) + delay: i64 = 0, + /// dispersion / clock error estimate + disp: u64 = 0, - pub fn fromPacket(p: Packet, dst_time: i128) Result { + /// results from a server reply packet. + /// client org and rec times must be provided by the caller. + pub fn fromPacket(p: Packet, T1: Time, T4: Time) Result { var result = Result{}; result.leap_indicator = @truncate((p.li_vers_mode >> 6) & 3); result.version = @truncate((p.li_vers_mode >> 3) & 0x7); @@ -326,60 +336,108 @@ pub const Result = struct { result.ref_id = p.ref_id; result.Tref = Time.fromRaw(p.ts_ref); - result.T1 = Time.fromRaw(p.ts_org); + result.T1 = T1; result.T2 = Time.fromRaw(p.ts_rec); result.T3 = Time.fromRaw(p.ts_xmt); - result.T4 = Time.fromUnixNanos(@intCast(dst_time)); + result.T4 = T4; - // offset / theta = T(B) - T(A) = 1/2 * [(T2-T1) + (T3-T4)] - result.theta = @divFloor((result.T2.sub(result.T1) + result.T3.sub(result.T4)), 2); + // offset = T(B) - T(A) = 1/2 * [(T2-T1) + (T3-T4)] + result.offset = @divFloor((result.T2.sub(result.T1) + result.T3.sub(result.T4)), 2); - // roundtrip duration / delta = T(ABA) = (T4-T1) - (T3-T2) - result.delta = result.T4.sub(result.T1) - result.T3.sub(result.T2); + // roundtrip delay = T(ABA) = (T4-T1) - (T3-T2) + result.delay = result.T4.sub(result.T1) - result.T3.sub(result.T2); - // sync distance; error estimate of server clock - result.lambda = result.root_dispersion +| result.root_delay / 2; + // TODO: dispersion return result; } + /// current time in nanoseconds since the Unix epoch corrected by offset reported + /// by NTP server. + pub fn correctTime(self: Result, uncorrected: i128) i128 { + return uncorrected + self.offset; + } + + /// RefID might be a 4-letter ASCII string. + pub fn refIDprintable(self: Result) bool { + const data: [4]u8 = @bitCast(self.ref_id); + for (data) |c| { + if (c < ' ' or c > '~') return false; + } + return true; + } + // TODO : add validate() - ref time fresh enough, stratum <= 16 etc. + // stratum 0 --> Kiss of Death --> check code + // stratum <= 16 ? + // freshness of ts_ref ? + // sync distance; (result.root_dispersion +| result.root_delay / 2) > max_disp ? + // server ts_rec must be ts_xmt (cannot send before receive) + // leap == 3? unsynchronized leap second! }; test "Result" { // random bytes can be parsed and analyzed - const rand = std.crypto.random; var b: [packet_len]u8 = undefined; var i: usize = 0; while (i < 1_000_000) : (i += 1) { rand.bytes(&b); - const r: Result = Result.fromPacket( - Packet.parse(b), - std.time.nanoTimestamp(), - ); + const r: Result = Result.fromPacket(Packet.parse(b), Time{}, Time{}); std.mem.doNotOptimizeAway(r); } - var p = Packet._init(3, true); + const now: u64 = @intCast(std.time.nanoTimestamp()); + var p = Packet._init(3, 0); + // client | server | client // T1 ->T2 ->T3 ->T4 // 1 0 0 3 // => offset -2, roundtrip 2 - const now: u64 = @intCast(std.time.nanoTimestamp()); - p.ts_org = Time.fromUnixNanos(now - 2 * ns_per_s).t; - p.ts_rec = Time.fromUnixNanos(now - 3 * ns_per_s).t; - p.ts_xmt = Time.fromUnixNanos(now - 3 * ns_per_s).t; - var res = Result.fromPacket(p, now); - try testing.expectEqual(@as(i64, 2 * ns_per_s), res.delta); - try testing.expectEqual(-@as(i64, 2 * ns_per_s), res.theta); + var T1 = Time.fromUnixNanos(now + 1 * ns_per_s); + p.ts_rec = Time.fromUnixNanos(now).t; + p.ts_xmt = Time.fromUnixNanos(now).t; + var T4 = Time.fromUnixNanos(now + 3 * ns_per_s); + var res = Result.fromPacket(p, T1, T4); + try testing.expectEqual(@as(i64, 2 * ns_per_s), res.delay); + try testing.expectEqual(-@as(i64, 2 * ns_per_s), res.offset); + try testing.expectEqual(@as(i128, now - 2 * ns_per_s), res.correctTime(@as(i128, now))); // 0 2 2 1 // => offset 1.5, roundtrip 1 - p.ts_org = Time.fromUnixNanos(now - 1 * ns_per_s).t; - p.ts_rec = Time.fromUnixNanos(now + 1 * ns_per_s).t; - p.ts_xmt = Time.fromUnixNanos(now + 1 * ns_per_s).t; - res = Result.fromPacket(p, now); - try testing.expectEqual(@as(i64, 1 * ns_per_s), res.delta); - try testing.expectEqual(@as(i64, 15 * ns_per_s / 10), res.theta); + T1 = Time.fromUnixNanos(now); + p.ts_rec = Time.fromUnixNanos(now + 2 * ns_per_s).t; + p.ts_xmt = Time.fromUnixNanos(now + 2 * ns_per_s).t; + T4 = Time.fromUnixNanos(now + 1 * ns_per_s); + res = Result.fromPacket(p, T1, T4); + try testing.expectEqual(@as(i64, 1 * ns_per_s), res.delay); + try testing.expectEqual(@as(i64, 15 * ns_per_s / 10), res.offset); + try testing.expectEqual(@as(i128, now + 15 * ns_per_s / 10), res.correctTime(@as(i128, now))); + + // 0 20 21 5 + // => offset 18, roundtrip 4 + T1 = Time.fromUnixNanos(now); + p.ts_rec = Time.fromUnixNanos(now + 20 * ns_per_s).t; + p.ts_xmt = Time.fromUnixNanos(now + 21 * ns_per_s).t; + T4 = Time.fromUnixNanos(now + 5 * ns_per_s); + res = Result.fromPacket(p, T1, T4); + try testing.expectEqual(@as(i64, 4 * ns_per_s), res.delay); + try testing.expectEqual(@as(i64, 18 * ns_per_s), res.offset); + try testing.expectEqual(@as(i128, now + 18 * ns_per_s), res.correctTime(@as(i128, now))); + + // 101 102 103 105 + // => offset -0.5, roundtrip 3 + T1 = Time.fromUnixNanos(now + 101 * ns_per_s); + p.ts_rec = Time.fromUnixNanos(now + 102 * ns_per_s).t; + p.ts_xmt = Time.fromUnixNanos(now + 103 * ns_per_s).t; + T4 = Time.fromUnixNanos(now + 105 * ns_per_s); + res = Result.fromPacket(p, T1, T4); + try testing.expectEqual(@as(i64, 3 * ns_per_s), res.delay); + try testing.expectEqual(-@as(i64, ns_per_s / 2), res.offset); + try testing.expectEqual(@as(i128, now - 5 * ns_per_s / 10), res.correctTime(@as(i128, now))); + + res.ref_id = 0x44524f50; // DROP + try testing.expect(res.refIDprintable()); + res.ref_id = 0x00000000; + try testing.expect(!res.refIDprintable()); } diff --git a/src/prettyprint.zig b/src/prettyprint.zig index 3352ef6..8690d8f 100755 --- a/src/prettyprint.zig +++ b/src/prettyprint.zig @@ -6,22 +6,22 @@ const Timezone = zdt.Timezone; const Resolution = zdt.Duration.Resolution; const ns_per_s: u64 = 1_000_000_000; +const ns_per_us: u64 = 1_000; // pretty-print an ntp-results struct pub fn pprint_result(writer: anytype, ntpr: ntp.Result, tz: ?*Timezone) !void { const prc: u64 = ntp.precisionToNanos(ntpr.precision); - const theat_f: f64 = @as(f64, @floatFromInt(ntpr.theta)) / @as(f64, ns_per_s); - const delta_f: f64 = @as(f64, @floatFromInt(ntpr.delta)) / @as(f64, ns_per_s); - const lamda_f: f64 = @as(f64, @floatFromInt(ntpr.lambda)) / @as(f64, ns_per_s); + const offset_f: f64 = @as(f64, @floatFromInt(ntpr.offset)) / @as(f64, ns_per_s); + const delay_f: f64 = @as(f64, @floatFromInt(ntpr.delay)) / @as(f64, ns_per_s); + // const disp_f: f64 = @as(f64, @floatFromInt(ntpr.lambda)) / @as(f64, ns_per_s); var z: *Timezone = if (tz == null) @constCast(&Timezone.UTC) else tz.?; try writer.print( \\NPT query result: - \\--- + \\---***--- \\LI={d} VN={d} Mode={d} Stratum={d} Poll={d} Precision={d} ({d} ns) \\ref_id: {d} - \\root_delay: {d} ns, root_dispersion: {d} ns - \\=> syncronization distance: {d} s + \\root_delay: {d} us, root_dispersion: {d} us \\--- \\Server last synced : {s} \\T1, packet created : {s} @@ -30,9 +30,9 @@ pub fn pprint_result(writer: anytype, ntpr: ntp.Result, tz: ?*Timezone) !void { \\T4, reply received : {s} \\(timezone displayed: {s}) \\--- - \\offset to timserver: {d:.6} s ({d} ns) - \\round-trip delay: {d:.6} s ({d} ns) - \\--- + \\offset to timserver: {d:.3} s ({d} us) + \\round-trip delay: {d:.3} s ({d} us) + \\---***--- \\ , .{ @@ -44,19 +44,18 @@ pub fn pprint_result(writer: anytype, ntpr: ntp.Result, tz: ?*Timezone) !void { ntpr.precision, prc, ntpr.ref_id, - ntpr.root_delay, - ntpr.root_dispersion, - lamda_f, + ntpr.root_delay / ns_per_us, + ntpr.root_dispersion / ns_per_us, try Datetime.fromUnix(ntpr.Tref.toUnixNanos(), Resolution.nanosecond, z.*), try Datetime.fromUnix(ntpr.T1.toUnixNanos(), Resolution.nanosecond, z.*), try Datetime.fromUnix(ntpr.T2.toUnixNanos(), Resolution.nanosecond, z.*), try Datetime.fromUnix(ntpr.T3.toUnixNanos(), Resolution.nanosecond, z.*), try Datetime.fromUnix(ntpr.T4.toUnixNanos(), Resolution.nanosecond, z.*), z.name(), - theat_f, - ntpr.theta, - delta_f, - ntpr.delta, + offset_f, + @divFloor(ntpr.offset, ns_per_us), + delay_f, + @divFloor(ntpr.delay, ns_per_us), }, ); }