cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
seq.c
Go to the documentation of this file.
3
4#include <string.h>
5
11struct cwist_seq_assembler {
12 bool have_state;
13 uint16_t total;
14 uint16_t chunk_size;
15 size_t total_len;
16 size_t data_cap;
17 size_t max_data_len;
18 uint8_t *data;
19 bool *received;
20 uint16_t *payload_lens;
21 uint16_t received_count;
22 bool contaminated;
23};
24
25/* -------------------------------------------------------------------------- */
26/* Header helpers */
27/* -------------------------------------------------------------------------- */
28
34static uint16_t seq_read_u16(const uint8_t *p) {
35 return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]);
36}
37
43static void seq_write_u16(uint8_t *p, uint16_t v) {
44 p[0] = (uint8_t)((v >> 8) & 0xff);
45 p[1] = (uint8_t)(v & 0xff);
46}
47
63bool cwist_seq_chunk_parse(const uint8_t *data, size_t len, cwist_seq_chunk_t *out) {
64 if (!data || !out || len < CWIST_SEQ_HEADER_SIZE) return false;
65
66 out->seq = seq_read_u16(data + 0);
67 out->total = seq_read_u16(data + 2);
68 out->payload_len = seq_read_u16(data + 4);
69 out->chunk_size = seq_read_u16(data + 6);
70 out->payload = data + CWIST_SEQ_HEADER_SIZE;
71
72 /* Reject missing or malformed sequence pairs. */
73 if (out->seq == 0 || out->total == 0 || out->total > CWIST_SEQ_MAX_CHUNKS ||
74 out->seq > out->total)
75 return false;
76 if (out->payload_len == 0 || out->chunk_size == 0) return false;
77 if (out->payload_len > out->chunk_size) return false;
78 if (out->seq != out->total && out->payload_len != out->chunk_size) return false;
79 if (len - CWIST_SEQ_HEADER_SIZE < out->payload_len) return false;
80
81 return true;
82}
83
98 uint16_t payload_len, uint16_t chunk_size) {
99 seq_write_u16(out + 0, seq);
100 seq_write_u16(out + 2, total);
101 seq_write_u16(out + 4, payload_len);
102 seq_write_u16(out + 6, chunk_size);
103}
104
105/* -------------------------------------------------------------------------- */
106/* Message splitter */
107/* -------------------------------------------------------------------------- */
108
126 cwist_seq_message_t *out) {
127 if (!data || len == 0 || chunk_payload_size == 0 || !out) return false;
128 memset(out, 0, sizeof(*out));
129
133 return false;
134 uint16_t total = (uint16_t)total32;
135
136 out->chunks = (uint8_t **)cwist_alloc_array(total, sizeof(uint8_t *));
137 out->chunk_lens = (size_t *)cwist_alloc_array(total, sizeof(size_t));
138 if (!out->chunks || !out->chunk_lens) {
139 cwist_free(out->chunks);
141 memset(out, 0, sizeof(*out));
142 return false;
143 }
144
145 for (uint16_t i = 0; i < total; i++) {
146 size_t offset = (size_t)i * chunk_payload_size;
147 size_t plen = len - offset;
150
152 if (!chunk) {
153 for (uint16_t j = 0; j < i; j++) cwist_free(out->chunks[j]);
154 cwist_free(out->chunks);
156 memset(out, 0, sizeof(*out));
157 return false;
158 }
159
161 memcpy(chunk + CWIST_SEQ_HEADER_SIZE, data + offset, plen);
162 out->chunks[i] = chunk;
163 out->chunk_lens[i] = chunk_len;
164 }
165
166 out->count = total;
167 return true;
168}
169
179 if (!msg) return;
180 if (msg->chunks) {
181 for (size_t i = 0; i < msg->count; i++) cwist_free(msg->chunks[i]);
182 cwist_free(msg->chunks);
183 }
184 cwist_free(msg->chunk_lens);
185 memset(msg, 0, sizeof(*msg));
186}
187
188/* -------------------------------------------------------------------------- */
189/* Assembler */
190/* -------------------------------------------------------------------------- */
191
199
213 if (!a) return NULL;
214 memset(a, 0, sizeof(*a));
215 a->max_data_len = max_data_len;
216 return a;
217}
218
224 if (!a) return;
225 cwist_free(a->data);
226 cwist_free(a->received);
227 cwist_free(a->payload_lens);
228 cwist_free(a);
229}
230
241 if (!a) return;
242 size_t max_data_len = a->max_data_len;
243 cwist_free(a->data);
244 cwist_free(a->received);
245 cwist_free(a->payload_lens);
246 memset(a, 0, sizeof(*a));
247 a->max_data_len = max_data_len;
248}
249
269 if (!a || !chunk) return false;
270
271 /* Validate sequence pair. */
272 if (chunk->seq == 0 || chunk->total == 0 || chunk->total > CWIST_SEQ_MAX_CHUNKS ||
273 chunk->seq > chunk->total)
274 return false;
275 if (chunk->payload_len == 0 || chunk->chunk_size == 0) return false;
276 if (chunk->payload_len > chunk->chunk_size) return false;
277 if (chunk->seq != chunk->total && chunk->payload_len != chunk->chunk_size) return false;
278 if (!chunk->payload) return false;
279
280 if (!a->have_state) {
281 a->total = chunk->total;
282 a->chunk_size = chunk->chunk_size;
283 /* The first packet may be the short final packet. Allocate for the
284 * full layout and determine total_len only after seq == total is
285 * actually received; deriving it from arrival order was the source of
286 * false protocol errors on reordered HTTP/2/3-adjacent transports. */
287 if ((size_t)chunk->total > SIZE_MAX / (size_t)chunk->chunk_size) return false;
288 a->data_cap = (size_t)chunk->total * chunk->chunk_size;
289 size_t ceiling = a->max_data_len ? a->max_data_len : CWIST_SEQ_MAX_REASSEMBLED_SIZE;
290 if (a->data_cap > ceiling) {
291 a->data_cap = 0;
292 return false;
293 }
294 a->data = (uint8_t *)cwist_alloc(a->data_cap);
295 a->received = (bool *)cwist_alloc_array(chunk->total, sizeof(bool));
296 a->payload_lens = (uint16_t *)cwist_alloc_array(chunk->total, sizeof(uint16_t));
297 if (!a->data || !a->received || !a->payload_lens) {
298 cwist_free(a->data);
299 cwist_free(a->received);
300 cwist_free(a->payload_lens);
301 memset(a, 0, sizeof(*a));
302 return false;
303 }
304 a->have_state = true;
305 } else {
306 if (chunk->total != a->total || chunk->chunk_size != a->chunk_size) return false;
307 }
308
309 size_t index = (size_t)chunk->seq - 1;
310 size_t offset = index * a->chunk_size;
311 if (a->received[index]) {
312 /* Safe duplicates are idempotent. A different duplicate is data
313 * corruption, not a retry, and must never overwrite good bytes. */
314 bool identical = a->payload_lens[index] == chunk->payload_len &&
315 memcmp(a->data + offset, chunk->payload, chunk->payload_len) == 0;
316 if (!identical) a->contaminated = true;
317 return identical;
318 }
319
320 if (offset > a->data_cap || chunk->payload_len > a->data_cap - offset) return false;
321
322 memcpy(a->data + offset, chunk->payload, chunk->payload_len);
323 a->received[index] = true;
324 a->payload_lens[index] = chunk->payload_len;
325 if (chunk->seq == chunk->total) {
326 a->total_len = offset + chunk->payload_len;
327 }
328 a->received_count++;
329 return true;
330}
331
344 return a && a->have_state && !a->contaminated && a->received_count == a->total &&
345 a->received[a->total - 1] && a->total_len > 0;
346}
347
363 size_t out_cap) {
364 if (!a || !a->have_state || cwist_seq_assembler_is_complete(a)) return 0;
365
366 size_t missing = 0;
367 for (uint16_t i = 0; i < a->total; ++i) {
368 if (!a->received[i]) {
369 if (out && missing < out_cap) out[missing] = (uint16_t)(i + 1);
370 missing++;
371 }
372 }
373 return missing;
374}
375
391 size_t *out_len) {
392 if (!cwist_seq_assembler_is_complete(a) || !out_data || !out_len) return false;
393 *out_data = a->data;
394 *out_len = a->total_len;
395 return true;
396}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
Definition alloc.c:21
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
size_t cap
Definition otel.c:56
cwist_seq_assembler_t * cwist_seq_assembler_create_limited(size_t max_data_len)
Create an assembler with a reassembled-size limit.
Definition seq.c:211
bool cwist_seq_assembler_is_complete(const cwist_seq_assembler_t *a)
Check whether all chunks of the message have been received intact.
Definition seq.c:343
bool cwist_seq_chunk_parse(const uint8_t *data, size_t len, cwist_seq_chunk_t *out)
Parse and validate a sequenced chunk header.
Definition seq.c:63
void cwist_seq_assembler_reset(cwist_seq_assembler_t *a)
Discard all received state, keeping the size limit.
Definition seq.c:240
cwist_seq_assembler_t * cwist_seq_assembler_create(void)
Create an assembler with no reassembled-size limit.
Definition seq.c:196
void cwist_seq_assembler_destroy(cwist_seq_assembler_t *a)
Destroy an assembler and free all its buffers.
Definition seq.c:223
size_t cwist_seq_assembler_recovery_targets(const cwist_seq_assembler_t *a, uint16_t *out, size_t out_cap)
List the sequence numbers of missing chunks.
Definition seq.c:362
void cwist_seq_message_free(cwist_seq_message_t *msg)
Free all memory owned by a cwist_seq_message_t.
Definition seq.c:178
bool cwist_seq_assembler_get_data(cwist_seq_assembler_t *a, const uint8_t **out_data, size_t *out_len)
Access the fully reassembled message.
Definition seq.c:390
bool cwist_seq_assembler_feed(cwist_seq_assembler_t *a, const cwist_seq_chunk_t *chunk)
Feed one sequenced chunk into the assembler.
Definition seq.c:268
void cwist_seq_chunk_build_header(uint8_t out[CWIST_SEQ_HEADER_SIZE], uint16_t seq, uint16_t total, uint16_t payload_len, uint16_t chunk_size)
Serialize a chunk header into an 8-byte buffer.
Definition seq.c:97
bool cwist_seq_split(const uint8_t *data, size_t len, uint16_t chunk_payload_size, cwist_seq_message_t *out)
Split a message buffer into sequenced chunks.
Definition seq.c:125
TCP-like sequenced message fragmentation and reassembly.
#define CWIST_SEQ_MAX_CHUNKS
Definition seq.h:36
#define CWIST_SEQ_MAX_REASSEMBLED_SIZE
Definition seq.h:37
struct cwist_seq_assembler cwist_seq_assembler_t
Opaque stateful assembler that reorders chunks and detects duplicates.
Definition seq.h:69
#define CWIST_SEQ_HEADER_SIZE
Definition seq.h:29
Parsed view of one sequenced chunk.
Definition seq.h:45
uint16_t payload_len
Payload length in this chunk.
Definition seq.h:48
uint16_t total
Total chunks in the message.
Definition seq.h:47
uint16_t chunk_size
Payload length of a full chunk.
Definition seq.h:49
const uint8_t * payload
Pointer to payload bytes.
Definition seq.h:50
uint16_t seq
1-based sequence number.
Definition seq.h:46
Split a message into sequenced chunks.
Definition seq.h:60
size_t count
Number of chunks.
Definition seq.h:63
size_t * chunk_lens
Length of each chunk buffer.
Definition seq.h:62
uint8_t ** chunks
Array of chunk buffers.
Definition seq.h:61