ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dtls_handshake.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#ifndef PROTOCORE_DTLS_HANDSHAKE_H
5#define PROTOCORE_DTLS_HANDSHAKE_H
6
7#include "protocore_config.h" // the entry point: protocore_types.h for the widths
8
10
11/**
12 * @file dtls_handshake.h
13 * @brief DTLS 1.3 handshake framing and reliability (RFC 9147 §5, §7).
14 *
15 * The datagram-reliability layer that sits between the DTLS record layer (protocore_dtls_record) and the
16 * reused TLS 1.3 message builders (protocore_tls13_msg). TLS 1.3 assumes an in-order reliable byte stream;
17 * DTLS carries the same handshake messages over lossy, reorderable datagrams, so each message gains
18 * a 12-byte DTLS handshake header (RFC 9147 §5.2) that lets a fragment be placed independently of
19 * the record that carried it, and lost flights are recovered with acknowledgements (§7) rather than
20 * TCP retransmission.
21 *
22 * This file is pure framing - no crypto state, no sockets. It provides:
23 * - the 12-byte handshake header (@ref protocore_dtls_hs_header_parse / @ref protocore_dtls_hs_frag_build);
24 * - overlap-tolerant message reassembly (@ref DtlsHsReasm), modelled on the QUIC CRYPTO-stream
25 * reassembler - a fragment may arrive split, duplicated, or overlapping (§5.4);
26 * - the ACK message (@ref protocore_dtls_ack_build / @ref protocore_dtls_ack_parse, content type 26, §7);
27 * - the stateless HelloRetryRequest cookie (@ref protocore_dtls_cookie_make / @ref protocore_dtls_cookie_verify,
28 * the §5.1 return-routability / anti-amplification defense).
29 *
30 * The handshake state machine that drives these (flights, epochs, PTO) is protocore_dtls_conn; the TLS 1.3
31 * message bodies and key schedule are reused verbatim from the HTTP/3 stack (protocore_tls13_msg, protocore_tls13_kdf).
32 *
33 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
34 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
35 * a caller drives every namespace the same way.
36 *
37 * @author Douglas Quigg (dstroy0)
38 * @date 2026
39 */
40
41// PROTOCORE_DTLS_HANDSHAKE_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
42// it into its arena. Its size and its offset are each a static_assert, so a feature
43// combination that does not fit fails to compile rather than overrunning at run time.
44
45/** @brief DTLS handshake header length: msg_type(1) + length(3) + message_seq(2) + fragment_offset(3)
46 * + fragment_length(3) = 12 bytes (RFC 9147 §5.2). */
47#define PROTOCORE_DTLS_HS_HDR_LEN 12
48
49/** @brief message_hash synthetic-message type used when wrapping ClientHello1 for a HelloRetryRequest
50 * transcript (RFC 8446 §4.4.1). Framing constant; the transcript itself lives in protocore_dtls_conn. */
51#define PROTOCORE_DTLS_HS_TYPE_MESSAGE_HASH 254
52
53/** @brief Max distinct byte ranges tracked while reassembling one message (bounds the work an
54 * adversary can force by sending maximally fragmented flights). */
55#define PROTOCORE_DTLS_HS_REASM_MAX_RANGES 8
56
57/** @brief Maximum cookie length this implementation emits / accepts. Overhead is 43 bytes
58 * (version + timestamp + payload_len + HMAC); the rest is available for the payload. */
59#define PROTOCORE_DTLS_COOKIE_MAX 128
60
61/** @brief Parsed view of one DTLS handshake message fragment (fields point into the caller buffer). */
62typedef struct
63{
64 uint8_t msg_type; ///< HandshakeType (client_hello, server_hello, finished, ...)
65 uint32_t length; ///< full reassembled body length (uint24 on the wire)
66 uint16_t msg_seq; ///< handshake message sequence number
67 uint32_t frag_offset; ///< byte offset of this fragment within the body (uint24)
68 uint32_t frag_length; ///< length of this fragment (uint24)
69 const uint8_t *fragment; ///< fragment bytes, into the input buffer
71
72/**
73 * @brief Reassembles the fragments of a single handshake message into a contiguous body.
74 *
75 * Fragments may arrive out of order, duplicated, or with overlapping ranges (RFC 9147 §5.4 requires
76 * an implementation to handle overlap). Received byte ranges are merged into a bounded interval list;
77 * the message is complete when a single interval covers [0, length).
78 */
79typedef struct
80{
81 proto_bool active; ///< false until the first fragment of the target message is seen
82 proto_bool have_len; ///< true once a fragment established the full message length
83 uint8_t msg_type; ///< HandshakeType of the message being reassembled
84 uint16_t msg_seq; ///< the message sequence number this reassembler accepts
85 uint32_t length; ///< full body length (from the first non-empty fragment)
86 uint8_t *buf; ///< caller-provided body buffer (>= length bytes)
87 size_t buf_cap; ///< capacity of @ref buf
88 uint32_t range_lo[PROTOCORE_DTLS_HS_REASM_MAX_RANGES]; ///< received interval starts
89 uint32_t range_hi[PROTOCORE_DTLS_HS_REASM_MAX_RANGES]; ///< received interval ends (exclusive)
90 uint8_t range_count; ///< number of active intervals
92
93/** @brief A record identified for acknowledgement: (epoch, sequence_number), 8 bytes each on the
94 * wire (RFC 9147 §7). */
95typedef struct
96{
97 uint64_t epoch;
98 uint64_t seq;
100
101/** @brief Dispatch table. Addressed by offset, so the layout is asserted below. */
102typedef struct
103{
104 size_t (*header_parse)(uint8_t *, const uint8_t *, size_t, DtlsHsHeader *);
105 size_t (*frag_build)(uint8_t *, uint8_t, uint16_t, uint32_t, uint32_t, const uint8_t *, uint32_t, uint8_t *,
106 size_t);
107 void (*reasm_init)(uint8_t *, DtlsHsReasm *, uint16_t, uint8_t *, size_t);
108 size_t (*reasm_add)(uint8_t *, DtlsHsReasm *, const DtlsHsHeader *);
109 size_t (*ack_build)(uint8_t *, const DtlsRecordNumber *, size_t, uint8_t *, size_t);
110 proto_bool (*ack_parse)(uint8_t *, const uint8_t *, size_t, DtlsRecordNumber *, size_t, size_t *);
111 size_t (*cookie_make)(uint8_t *, uint8_t *, const uint8_t *, uint64_t, const uint8_t *, size_t, const uint8_t *,
112 size_t, uint8_t *, size_t);
113 proto_bool (*cookie_verify)(uint8_t *, uint8_t *, const uint8_t *, uint64_t, uint64_t, const uint8_t *, size_t,
114 const uint8_t *, size_t, uint8_t *, size_t, size_t *);
116PROTOCORE_NS_LAYOUT(DtlsHandshakeNs, header_parse, frag_build, reasm_init, reasm_add, ack_build, ack_parse, cookie_make,
117 cookie_verify);
118
119/**
120 * @brief The 12-byte DTLS handshake header; bytes consumed, or 0 if truncated.
121 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
122 * @param p P
123 * @param len Len
124 * @param out Out
125 * @return The size_t.
126 */
127size_t protocore_dtls_handshake_header_parse(uint8_t *work, const uint8_t *p, size_t len, DtlsHsHeader *out);
128/**
129 * @brief One handshake fragment, header and body; bytes written, or 0 on .
130 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
131 * @param msg_type Msg type
132 * @param msg_seq Msg seq
133 * @param full_len Full len
134 * @param frag_offset Frag offset
135 * @param frag Frag
136 * @param frag_len Frag len
137 * @param out Out
138 * @param out_cap Out cap
139 * @return The size_t.
140 */
141size_t protocore_dtls_handshake_frag_build(uint8_t *work, uint8_t msg_type, uint16_t msg_seq, uint32_t full_len,
142 uint32_t frag_offset, const uint8_t *frag, uint32_t frag_len, uint8_t *out,
143 size_t out_cap);
144/**
145 * @brief Bind a reassembler to a caller buffer for one message sequence .
146 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
147 * @param r R
148 * @param msg_seq Msg seq
149 * @param buf Buf
150 * @param buf_cap Buf cap
151 */
152void protocore_dtls_handshake_reasm_init(uint8_t *work, DtlsHsReasm *r, uint16_t msg_seq, uint8_t *buf, size_t buf_cap);
153/**
154 * @brief Add one fragment to the reassembly in progress.
155 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
156 * @param r R
157 * @param frag Frag
158 * @return The size_t.
159 */
160size_t protocore_dtls_handshake_reasm_add(uint8_t *work, DtlsHsReasm *r, const DtlsHsHeader *frag);
161/**
162 * @brief An ACK body (RFC 9147 sec 7) over count record numbers.
163 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
164 * @param nums Nums
165 * @param count Count
166 * @param out Out
167 * @param out_cap Out cap
168 * @return The size_t.
169 */
170size_t protocore_dtls_handshake_ack_build(uint8_t *work, const DtlsRecordNumber *nums, size_t count, uint8_t *out,
171 size_t out_cap);
172/**
173 * @brief The same body back into at most out_cap record numbers.
174 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
175 * @param body Body
176 * @param len Len
177 * @param out Out
178 * @param out_cap Out cap
179 * @param out_count Out count
180 * @return PROTO_TRUE on success.
181 */
182proto_bool protocore_dtls_handshake_ack_parse(uint8_t *work, const uint8_t *body, size_t len, DtlsRecordNumber *out,
183 size_t out_cap, size_t *out_count);
184/**
185 * @brief A stateless HelloRetryRequest cookie bound to the client address.
186 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
187 * @param mac_work Mac work
188 * @param protocore_hmac_key 32 bytes
189 * @param timestamp Timestamp
190 * @param payload Payload
191 * @param payload_len Payload len
192 * @param client_addr Client addr
193 * @param addr_len Addr len
194 * @param out Out
195 * @param out_cap Out cap
196 * @return The size_t.
197 */
198size_t protocore_dtls_handshake_cookie_make(uint8_t *work, uint8_t *mac_work, const uint8_t *protocore_hmac_key,
199 uint64_t timestamp, const uint8_t *payload, size_t payload_len,
200 const uint8_t *client_addr, size_t addr_len, uint8_t *out, size_t out_cap);
201/**
202 * @brief The same cookie back, checking the address binding and the age .
203 * @param work PROTOCORE_DTLS_HANDSHAKE_BORROW bytes the caller took. Not held past the call.
204 * @param mac_work Mac work
205 * @param protocore_hmac_key 32 bytes
206 * @param now Now
207 * @param max_age Max age
208 * @param client_addr Client addr
209 * @param addr_len Addr len
210 * @param cookie Cookie
211 * @param cookie_len Cookie len
212 * @param payload_out Payload out
213 * @param payload_cap Payload cap
214 * @param payload_len_out Payload len out
215 * @return PROTO_TRUE on success.
216 */
217proto_bool protocore_dtls_handshake_cookie_verify(uint8_t *work, uint8_t *mac_work, const uint8_t *protocore_hmac_key,
218 uint64_t now, uint64_t max_age, const uint8_t *client_addr,
219 size_t addr_len, const uint8_t *cookie, size_t cookie_len,
220 uint8_t *payload_out, size_t payload_cap, size_t *payload_len_out);
221
222/** @brief Module namespace. */
231
233
234#endif // PROTOCORE_DTLS_HANDSHAKE_H
PROTOCORE_NS DtlsHandshakeNs DtlsHandshake PROTOCORE_UNUSED
Module namespace.
size_t protocore_dtls_handshake_reasm_add(uint8_t *work, DtlsHsReasm *r, const DtlsHsHeader *frag)
Add one fragment to the reassembly in progress.
proto_bool protocore_dtls_handshake_cookie_verify(uint8_t *work, uint8_t *mac_work, const uint8_t *protocore_hmac_key, uint64_t now, uint64_t max_age, const uint8_t *client_addr, size_t addr_len, const uint8_t *cookie, size_t cookie_len, uint8_t *payload_out, size_t payload_cap, size_t *payload_len_out)
The same cookie back, checking the address binding and the age .
proto_bool protocore_dtls_handshake_ack_parse(uint8_t *work, const uint8_t *body, size_t len, DtlsRecordNumber *out, size_t out_cap, size_t *out_count)
The same body back into at most out_cap record numbers.
size_t protocore_dtls_handshake_ack_build(uint8_t *work, const DtlsRecordNumber *nums, size_t count, uint8_t *out, size_t out_cap)
An ACK body (RFC 9147 sec 7) over count record numbers.
#define PROTOCORE_DTLS_HS_REASM_MAX_RANGES
Max distinct byte ranges tracked while reassembling one message (bounds the work an adversary can for...
void protocore_dtls_handshake_reasm_init(uint8_t *work, DtlsHsReasm *r, uint16_t msg_seq, uint8_t *buf, size_t buf_cap)
Bind a reassembler to a caller buffer for one message sequence .
size_t protocore_dtls_handshake_header_parse(uint8_t *work, const uint8_t *p, size_t len, DtlsHsHeader *out)
The 12-byte DTLS handshake header; bytes consumed, or 0 if truncated.
size_t protocore_dtls_handshake_frag_build(uint8_t *work, uint8_t msg_type, uint16_t msg_seq, uint32_t full_len, uint32_t frag_offset, const uint8_t *frag, uint32_t frag_len, uint8_t *out, size_t out_cap)
One handshake fragment, header and body; bytes written, or 0 on .
size_t protocore_dtls_handshake_cookie_make(uint8_t *work, uint8_t *mac_work, const uint8_t *protocore_hmac_key, uint64_t timestamp, const uint8_t *payload, size_t payload_len, const uint8_t *client_addr, size_t addr_len, uint8_t *out, size_t out_cap)
A stateless HelloRetryRequest cookie bound to the client address.
#define PROTOCORE_NS_LAYOUT(T,...)
Pin every dispatch slot of a table that is nothing but function pointers.
#define PROTOCORE_NS
Storage for a dispatch table. The const is load bearing.
Dispatch table. Addressed by offset, so the layout is asserted below.
size_t(* header_parse)(uint8_t *, const uint8_t *, size_t, DtlsHsHeader *)
Parsed view of one DTLS handshake message fragment (fields point into the caller buffer).
uint32_t length
full reassembled body length (uint24 on the wire)
uint32_t frag_length
length of this fragment (uint24)
uint16_t msg_seq
handshake message sequence number
uint32_t frag_offset
byte offset of this fragment within the body (uint24)
uint8_t msg_type
HandshakeType (client_hello, server_hello, finished, ...)
const uint8_t * fragment
fragment bytes, into the input buffer
Reassembles the fragments of a single handshake message into a contiguous body.
proto_bool active
false until the first fragment of the target message is seen
uint8_t range_count
number of active intervals
uint32_t length
full body length (from the first non-empty fragment)
uint8_t * buf
caller-provided body buffer (>= length bytes)
size_t buf_cap
capacity of buf
uint8_t msg_type
HandshakeType of the message being reassembled.
proto_bool have_len
true once a fragment established the full message length
uint16_t msg_seq
the message sequence number this reassembler accepts
A record identified for acknowledgement: (epoch, sequence_number), 8 bytes each on the wire (RFC 9147...
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97