ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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
10
11#if PC_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 pc_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 {
31 return 0;
32 }
33 size_t n = 4 + body_len;
34 if (n > cap)
35 {
36 return 0;
37 }
38 put_u16(out, (uint16_t)((type & 0x7FFF) | (critical ? Nts::NTS_KE_CRITICAL : 0)));
39 put_u16(out + 2, (uint16_t)body_len);
40 if (body_len)
41 {
42 memcpy(out + 4, body, body_len);
43 }
44 return n;
45}
46
47size_t pc_nts_ke_request(uint8_t *out, size_t cap)
48{
49 uint8_t proto[2];
50 put_u16(proto, Nts::NTS_NEXT_PROTO_NTPV4);
51 uint8_t aead[2];
52 put_u16(aead, Nts::NTS_AEAD_AES_SIV_CMAC_256);
53
54 size_t n = 0;
55 size_t r;
56 r = pc_nts_ke_record(true, Nts::NTS_KE_NEXT_PROTOCOL, proto, 2, out + n, cap - n);
57 if (!r)
58 {
59 return 0;
60 }
61 n += r;
62 r = pc_nts_ke_record(true, Nts::NTS_KE_AEAD_ALGORITHM, aead, 2, out + n, cap - n);
63 if (!r)
64 {
65 return 0;
66 }
67 n += r;
68 r = pc_nts_ke_record(true, Nts::NTS_KE_END_OF_MESSAGE, nullptr, 0, out + n, cap - n);
69 if (!r)
70 {
71 return 0;
72 }
73 n += r;
74 return n;
75}
76
77bool pc_nts_ke_parse(const uint8_t *buf, size_t len, pc_nts_ke_cb cb, void *arg)
78{
79 size_t off = 0;
80 bool saw_end = false;
81 while (off + 4 <= len)
82 {
83 uint16_t tf = get_u16(buf + off);
84 uint16_t blen = get_u16(buf + off + 2);
85 bool critical = (tf & Nts::NTS_KE_CRITICAL) != 0;
86 uint16_t type = (uint16_t)(tf & 0x7FFF);
87 if (off + 4 + blen > len)
88 {
89 return false; // truncated body
90 }
91 if (cb)
92 {
93 cb(critical, type, blen ? (buf + off + 4) : nullptr, blen, arg);
94 }
95 off += 4 + blen;
96 if (type == Nts::NTS_KE_END_OF_MESSAGE)
97 {
98 saw_end = true;
99 break;
100 }
101 }
102 return saw_end; // a well-formed stream is terminated by an End-of-Message record
103}
104
105size_t pc_nts_ef(uint16_t field_type, const uint8_t *value, size_t value_len, uint8_t *out, size_t cap)
106{
107 if (!out || (value_len && !value))
108 {
109 return 0;
110 }
111 // RFC 7822: Length = type + length + value + padding, a multiple of 4 (min 4).
112 size_t total = 4 + value_len;
113 size_t padded = (total + 3) & ~(size_t)3;
114 if (padded > 0xFFFF || padded > cap)
115 {
116 return 0;
117 }
118 put_u16(out, field_type);
119 put_u16(out + 2, (uint16_t)padded);
120 if (value_len)
121 {
122 memcpy(out + 4, value, value_len);
123 }
124 for (size_t i = total; i < padded; i++)
125 {
126 out[i] = 0; // zero padding
127 }
128 return padded;
129}
130
131size_t pc_nts_ef_unique_id(const uint8_t *nonce, size_t nonce_len, uint8_t *out, size_t cap)
132{
133 return pc_nts_ef(NtsEf::NTS_EF_UNIQUE_IDENTIFIER, nonce, nonce_len, out, cap);
134}
135
136size_t pc_nts_ef_cookie(const uint8_t *cookie, size_t cookie_len, uint8_t *out, size_t cap)
137{
138 return pc_nts_ef(NtsEf::NTS_EF_COOKIE, cookie, cookie_len, out, cap);
139}
140
141#endif // PC_ENABLE_NTS
Network Time Security (NTS, RFC 8915) wire codec (PC_ENABLE_NTS).