ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ntp_server.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file pc_ntp_server.cpp
6 * @brief NTP server (RFC 5905 server mode) - implementation. See pc_ntp_server.h.
7 */
8
10#include "protocore_config.h"
11
12#if PC_ENABLE_NTP_SERVER
13
15#include <string.h> // memset, memcpy
16
17#if defined(ARDUINO)
21#endif
22size_t pc_ntp_server_build_response(const uint8_t *req, size_t req_len, uint8_t stratum, uint32_t refid,
23 uint32_t pc_ntp_secs, uint32_t pc_ntp_frac, uint8_t *out, size_t out_cap)
24{
25 if (!req || !out || req_len < NTP_PACKET_LEN || out_cap < NTP_PACKET_LEN)
26 {
27 return 0;
28 }
29
30 // LI (2 bits) | VN (3 bits) | Mode (3 bits). Echo the client's version; reply as server (4).
31 uint8_t vn = (uint8_t)((req[0] >> 3) & 0x7);
32 memset(out, 0, NTP_PACKET_LEN);
33 out[0] = (uint8_t)((0u << 6) | (vn << 3) | 4u); // LI = 0 (in sync), VN echoed, Mode = 4 (server)
34 out[1] = stratum;
35 out[2] = req[2] ? req[2] : 6; // poll interval: echo the client's, else 2^6 s
36 out[3] = (uint8_t)(-6); // precision: ~2^-6 s (16 ms), the clock's granularity
37 // Root delay (4..7) stays 0; root dispersion (8..11) ~ 1 s to advertise a coarse clock.
38 pc_wr32be(out + 8, 0x00010000u);
39 pc_wr32be(out + 12, refid);
40 pc_wr32be(out + 16, pc_ntp_secs); // reference timestamp (when our clock was last good = now)
41 pc_wr32be(out + 20, pc_ntp_frac);
42 memcpy(out + 24, req + 40, 8); // origin timestamp = the client's transmit timestamp
43 pc_wr32be(out + 32, pc_ntp_secs); // receive timestamp
44 pc_wr32be(out + 36, pc_ntp_frac);
45 pc_wr32be(out + 40, pc_ntp_secs); // transmit timestamp
46 pc_wr32be(out + 44, pc_ntp_frac);
47 return NTP_PACKET_LEN;
48}
49
50#if defined(ARDUINO)
51
52namespace
53{
54// All NTP-server binding state, owned by one instance (internal linkage): the advertised
55// stratum and reference id, grouped so it is one named owner, unreachable cross-TU.
56struct NtpServerCtx
57{
58 uint8_t stratum = PC_NTP_SERVER_STRATUM;
59 uint32_t refid = NTP_REFID_LOCL;
60};
61NtpServerCtx s_ntp;
62
63// UDP handler: answer each request from the current time (silent if we have none).
64void pc_ntp_server_udp_handler(const uint8_t *data, size_t len, const struct pc_udp_peer *peer, void *ctx)
65{
66 (void)ctx;
67 uint32_t unix_secs = pc_time_now();
68 if (unix_secs == 0) // no valid time - do not serve a wrong clock
69 {
70 return;
71 }
72 // Sub-second fraction from the monotonic ms clock (best-effort; not phase-locked to the
73 // 1 Hz second boundary, so the sub-second component is approximate on this class of clock).
74 uint32_t frac = (uint32_t)(((uint64_t)(pc_millis() % 1000u) << 32) / 1000u);
75
76 uint8_t resp[NTP_PACKET_LEN];
77 size_t n = pc_ntp_server_build_response(data, len, s_ntp.stratum, s_ntp.refid, unix_secs + NTP_UNIX_OFFSET, frac,
78 resp, sizeof(resp));
79 if (n)
80 {
81 pc_udp_send(peer, resp, n);
82 }
83}
84} // namespace
85
86bool pc_ntp_server_begin(uint8_t stratum, uint32_t refid)
87{
88 s_ntp.stratum = stratum;
89 s_ntp.refid = refid;
90 return pc_udp_listen(123, pc_ntp_server_udp_handler, nullptr);
91}
92
93#else // host build: no lwIP. The codec above is host-tested; the binding is a stub.
94
95bool pc_ntp_server_begin(uint8_t, uint32_t)
96{
97 return false;
98}
99
100#endif // ARDUINO
101
102#endif // PC_ENABLE_NTP_SERVER
Pluggable monotonic clock for all library timing.
uint32_t pc_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Definition clock.h:71
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
size_t pc_wr32be(uint8_t *p, uint32_t v)
Write v big-endian at p.
Definition endian.h:93
#define NTP_REFID_LOCL
Reference ID "LOCL" - an undisciplined local clock (RFC 5905 sec 7.3).
Definition ntp_server.h:37
#define NTP_UNIX_OFFSET
Seconds between the NTP epoch (1900-01-01) and the Unix epoch (1970-01-01).
Definition ntp_server.h:34
size_t pc_ntp_server_build_response(const uint8_t *req, size_t req_len, uint8_t stratum, uint32_t refid, uint32_t pc_ntp_secs, uint32_t pc_ntp_frac, uint8_t *out, size_t out_cap)
Build a server (mode 4) reply to a client NTP request. Pure - no clock, no I/O.
bool pc_ntp_server_begin(uint8_t stratum, uint32_t refid=NTP_REFID_LOCL)
Start answering NTP requests on UDP/123 from the device's own clock.
#define NTP_PACKET_LEN
One NTP packet on the wire is exactly 48 octets (no extension/auth fields).
Definition ntp_server.h:31
User-facing configuration for ProtoCore.
#define PC_NTP_SERVER_STRATUM
Stratum the NTP server advertises (distance from a reference clock; 1-15).
uint32_t pc_time_now(void)
Current best time.
Multi-source time fallback matrix (PC_ENABLE_TIME_SOURCE).
bool pc_udp_listen(uint16_t port, pc_udp_handler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
Definition udp.cpp:226
bool pc_udp_send(const struct pc_udp_peer *peer, const uint8_t *data, size_t len)
Send a datagram back to the peer captured during the handler call.
Definition udp.cpp:336
Layer 4 (Transport) - connectionless UDP datagram service.