DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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 ntp_server.cpp
6 * @brief NTP server (RFC 5905 server mode) - implementation. See ntp_server.h.
7 */
8
10#include "ServerConfig.h"
11
12#if DETWS_ENABLE_NTP_SERVER
13
14#include <string.h> // memset, memcpy
15
16namespace
17{
18void put_be32(uint8_t *p, uint32_t v)
19{
20 p[0] = (uint8_t)(v >> 24);
21 p[1] = (uint8_t)(v >> 16);
22 p[2] = (uint8_t)(v >> 8);
23 p[3] = (uint8_t)v;
24}
25} // namespace
26
27size_t ntp_server_build_response(const uint8_t *req, size_t req_len, uint8_t stratum, uint32_t refid, uint32_t ntp_secs,
28 uint32_t ntp_frac, uint8_t *out, size_t out_cap)
29{
30 if (!req || !out || req_len < NTP_PACKET_LEN || out_cap < NTP_PACKET_LEN)
31 return 0;
32
33 // LI (2 bits) | VN (3 bits) | Mode (3 bits). Echo the client's version; reply as server (4).
34 uint8_t vn = (uint8_t)((req[0] >> 3) & 0x7);
35 memset(out, 0, NTP_PACKET_LEN);
36 out[0] = (uint8_t)((0u << 6) | (vn << 3) | 4u); // LI = 0 (in sync), VN echoed, Mode = 4 (server)
37 out[1] = stratum;
38 out[2] = req[2] ? req[2] : 6; // poll interval: echo the client's, else 2^6 s
39 out[3] = (uint8_t)(-6); // precision: ~2^-6 s (16 ms), the clock's granularity
40 // Root delay (4..7) stays 0; root dispersion (8..11) ~ 1 s to advertise a coarse clock.
41 put_be32(out + 8, 0x00010000u);
42 put_be32(out + 12, refid);
43 put_be32(out + 16, ntp_secs); // reference timestamp (when our clock was last good = now)
44 put_be32(out + 20, ntp_frac);
45 memcpy(out + 24, req + 40, 8); // origin timestamp = the client's transmit timestamp
46 put_be32(out + 32, ntp_secs); // receive timestamp
47 put_be32(out + 36, ntp_frac);
48 put_be32(out + 40, ntp_secs); // transmit timestamp
49 put_be32(out + 44, ntp_frac);
50 return NTP_PACKET_LEN;
51}
52
53#if defined(ARDUINO)
54
56#include "services/clock.h"
58
59namespace
60{
61// All NTP-server binding state, owned by one instance (internal linkage): the advertised
62// stratum and reference id, grouped so it is one named owner, unreachable cross-TU.
63struct NtpServerCtx
64{
65 uint8_t stratum = DETWS_NTP_SERVER_STRATUM;
66 uint32_t refid = NTP_REFID_LOCL;
67};
68NtpServerCtx s_ntp;
69
70// UDP handler: answer each request from the current time (silent if we have none).
71void ntp_server_udp_handler(const uint8_t *data, size_t len, struct DetUdpPeer *peer, void *ctx)
72{
73 (void)ctx;
74 uint32_t unix_secs = detws_time_now();
75 if (unix_secs == 0) // no valid time - do not serve a wrong clock
76 return;
77 // Sub-second fraction from the monotonic ms clock (best-effort; not phase-locked to the
78 // 1 Hz second boundary, so the sub-second component is approximate on this class of clock).
79 uint32_t frac = (uint32_t)(((uint64_t)(detws_millis() % 1000u) << 32) / 1000u);
80
81 uint8_t resp[NTP_PACKET_LEN];
82 size_t n = ntp_server_build_response(data, len, s_ntp.stratum, s_ntp.refid, unix_secs + NTP_UNIX_OFFSET, frac, resp,
83 sizeof(resp));
84 if (n)
85 det_udp_send(peer, resp, n);
86}
87} // namespace
88
89bool ntp_server_begin(uint8_t stratum, uint32_t refid)
90{
91 s_ntp.stratum = stratum;
92 s_ntp.refid = refid;
93 return det_udp_listen(123, ntp_server_udp_handler, nullptr);
94}
95
96#else // host build: no lwIP. The codec above is host-tested; the binding is a stub.
97
98bool ntp_server_begin(uint8_t, uint32_t)
99{
100 return false;
101}
102
103#endif // ARDUINO
104
105#endif // DETWS_ENABLE_NTP_SERVER
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_NTP_SERVER_STRATUM
Stratum the NTP server advertises (distance from a reference clock; 1-15).
Pluggable monotonic clock for all library timing.
uint32_t detws_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Definition clock.h:71
void put_be32(uint8_t *p, uint32_t v)
Definition ghash.h:43
NTP/SNTP time server (RFC 5905 / RFC 4330 server mode) on UDP/123.
#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
bool 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
size_t ntp_server_build_response(const uint8_t *req, size_t req_len, uint8_t stratum, uint32_t refid, uint32_t ntp_secs, uint32_t 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.
uint32_t detws_time_now(void)
Current best time.
Multi-source time fallback matrix (DETWS_ENABLE_TIME_SOURCE).
bool det_udp_send(struct DetUdpPeer *peer, const uint8_t *data, size_t len)
Send a datagram back to the peer captured during the handler call.
Definition udp.cpp:178
bool det_udp_listen(uint16_t port, DetUdpHandler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
Definition udp.cpp:151
Layer 4 (Transport) - connectionless UDP datagram service.