DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
nts.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 nts.cpp
6 * @brief Network Time Security wire codec (see nts.h).
7 */
8
9#include "services/nts/nts.h"
10
11#if DETWS_ENABLE_NTS
12
13#include <string.h>
14
15const char NTS_EXPORTER_LABEL[] = "EXPORTER-network-time-security";
16
17static void put_u16(uint8_t *p, uint16_t v)
18{
19 p[0] = (uint8_t)(v >> 8);
20 p[1] = (uint8_t)v;
21}
22static uint16_t get_u16(const uint8_t *p)
23{
24 return (uint16_t)((p[0] << 8) | p[1]);
25}
26
27size_t detws_nts_ke_record(bool critical, uint16_t type, const uint8_t *body, size_t body_len, uint8_t *out, size_t cap)
28{
29 if (!out || (body_len && !body) || body_len > 0xFFFF)
30 return 0;
31 size_t n = 4 + body_len;
32 if (n > cap)
33 return 0;
34 put_u16(out, (uint16_t)((type & 0x7FFF) | (critical ? Nts::NTS_KE_CRITICAL : 0)));
35 put_u16(out + 2, (uint16_t)body_len);
36 if (body_len)
37 memcpy(out + 4, body, body_len);
38 return n;
39}
40
41size_t detws_nts_ke_request(uint8_t *out, size_t cap)
42{
43 uint8_t proto[2];
44 put_u16(proto, Nts::NTS_NEXT_PROTO_NTPV4);
45 uint8_t aead[2];
46 put_u16(aead, Nts::NTS_AEAD_AES_SIV_CMAC_256);
47
48 size_t n = 0;
49 size_t r;
50 r = detws_nts_ke_record(true, Nts::NTS_KE_NEXT_PROTOCOL, proto, 2, out + n, cap - n);
51 if (!r)
52 return 0;
53 n += r;
54 r = detws_nts_ke_record(true, Nts::NTS_KE_AEAD_ALGORITHM, aead, 2, out + n, cap - n);
55 if (!r)
56 return 0;
57 n += r;
58 r = detws_nts_ke_record(true, Nts::NTS_KE_END_OF_MESSAGE, nullptr, 0, out + n, cap - n);
59 if (!r)
60 return 0;
61 n += r;
62 return n;
63}
64
65bool detws_nts_ke_parse(const uint8_t *buf, size_t len, DetwsNtsKeCb cb, void *arg)
66{
67 size_t off = 0;
68 bool saw_end = false;
69 while (off + 4 <= len)
70 {
71 uint16_t tf = get_u16(buf + off);
72 uint16_t blen = get_u16(buf + off + 2);
73 bool critical = (tf & Nts::NTS_KE_CRITICAL) != 0;
74 uint16_t type = (uint16_t)(tf & 0x7FFF);
75 if (off + 4 + blen > len)
76 return false; // truncated body
77 if (cb)
78 cb(critical, type, blen ? (buf + off + 4) : nullptr, blen, arg);
79 off += 4 + blen;
80 if (type == Nts::NTS_KE_END_OF_MESSAGE)
81 {
82 saw_end = true;
83 break;
84 }
85 }
86 return saw_end; // a well-formed stream is terminated by an End-of-Message record
87}
88
89size_t detws_nts_ef(uint16_t field_type, const uint8_t *value, size_t value_len, uint8_t *out, size_t cap)
90{
91 if (!out || (value_len && !value))
92 return 0;
93 // RFC 7822: Length = type + length + value + padding, a multiple of 4 (min 4).
94 size_t total = 4 + value_len;
95 size_t padded = (total + 3) & ~(size_t)3;
96 if (padded > 0xFFFF || padded > cap)
97 return 0;
98 put_u16(out, field_type);
99 put_u16(out + 2, (uint16_t)padded);
100 if (value_len)
101 memcpy(out + 4, value, value_len);
102 for (size_t i = total; i < padded; i++)
103 out[i] = 0; // zero padding
104 return padded;
105}
106
107size_t detws_nts_ef_unique_id(const uint8_t *nonce, size_t nonce_len, uint8_t *out, size_t cap)
108{
109 return detws_nts_ef(NtsEf::NTS_EF_UNIQUE_IDENTIFIER, nonce, nonce_len, out, cap);
110}
111
112size_t detws_nts_ef_cookie(const uint8_t *cookie, size_t cookie_len, uint8_t *out, size_t cap)
113{
114 return detws_nts_ef(NtsEf::NTS_EF_COOKIE, cookie, cookie_len, out, cap);
115}
116
117#endif // DETWS_ENABLE_NTS
Network Time Security (NTS, RFC 8915) wire codec (DETWS_ENABLE_NTS).