ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
span.h
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 span.h
6 * @brief A byte region whose run length is bound in both directions.
7 *
8 * Every buffer in this library has a declared, compile-time run length - SSH_PKT_BUF_SIZE,
9 * PC_RSA_KEY_BYTES, and so on. The problem rule 19 exposes is not that the size is unknown; it is
10 * that once an array becomes a borrowed region, the size gets *restated* at each use and the
11 * restatements can disagree:
12 *
13 * uint8_t *buf = (uint8_t *)scratch_alloc(SSH_PKT_BUF_SIZE, 16);
14 * handler(buf, &n, SSH_PKT_BUF_SIZE); // nothing forces these two constants to match
15 *
16 * and `sizeof(buf)` silently becomes `sizeof(uint8_t *)` - 4 bytes on a 32-bit target - with no
17 * diagnostic, because -Wsizeof-pointer-memaccess only fires for the memcpy / strcpy family.
18 *
19 * A span is stated once, from the constant that already names the size, and then carried. Nothing
20 * here re-derives a length the code already knows: there is no deduction and no `sizeof` on a
21 * borrow, only the one constant travelling with its storage.
22 *
23 * **Both directions, one object.** A fixed-capacity region in this library is never one-way. The
24 * preempting queue is the reference: one depth constant sizes the lane, items go in at either end,
25 * and pc_pq_high_water_lane() reports the peak actually reached so the constant can be right-sized.
26 * A span carries the same pair:
27 *
28 * - forward `cap` - reserves the storage and bounds every write
29 * - backward `pos` - what the payload actually needed, which **keeps counting past `cap`** on
30 * overflow, so an undersized run-length constant reports the size it should
31 * have been instead of merely failing
32 *
33 * That retires the `size_t *out_len` out-parameter that used to ride beside the buffer: capacity in
34 * and produced length out are one object, and neither can be paired with the wrong buffer.
35 *
36 * **One byte-cursor API, not two.** The field names here are exactly the write-cursor convention
37 * that shared_primitives/bytes.h already operates on (`{uint8_t *buf; size_t cap; size_t pos; bool
38 * overflow;}`), so pc_bw_put() / pc_bw_put_be() / pc_bw_len() / pc_bw_ok() apply to a pc_span
39 * unchanged. This type is the storage-binding half - where the region came from and how big it is;
40 * bytes.h is the writing half. There is no second byte-append API.
41 *
42 * **Fail-closed by construction.** A failed allocation yields a null pointer with zero capacity,
43 * never a null pointer with a live capacity. A caller that forgets the check writes nothing into a
44 * zero-capacity region instead of dereferencing null, so an omitted check drops a message rather
45 * than crashing. Callers should still test pc_span_ok() and take their defined fail-closed path
46 * (drop the optional work, close the connection, answer 503).
47 *
48 * @author Douglas Quigg (dstroy0)
49 * @date 2026
50 */
51
52#ifndef PROTOCORE_SPAN_H
53#define PROTOCORE_SPAN_H
54
55#include <stddef.h>
56#include <stdint.h>
57
58/**
59 * @brief A writable byte region: the storage, the capacity that belongs to it, and what has been
60 * produced into it.
61 *
62 * Field names match the bytes.h write-cursor convention on purpose - see the file comment.
63 */
64struct pc_span
65{
66 uint8_t *buf; ///< first byte, or nullptr when the region could not be obtained
67 size_t cap; ///< bytes writable at @ref buf (0 whenever @ref buf is nullptr)
68 size_t pos; ///< bytes the payload needs so far; keeps counting past @ref cap on overflow
69 bool overflow; ///< set once a write did not fit; @ref pos then reports the size required
70};
71
72/**
73 * @brief A read-only byte region.
74 *
75 * Field names match the bytes.h read-cursor convention (`{const uint8_t *buf; size_t len; size_t
76 * pos; bool err;}`), so pc_br_init() / pc_br_take_be() apply unchanged.
77 */
79{
80 const uint8_t *buf; ///< first byte, or nullptr when there is nothing to read
81 size_t len; ///< readable bytes at @ref buf (0 whenever @ref buf is nullptr)
82 size_t pos; ///< read cursor
83 bool err; ///< sticky: a read ran past @ref len
84};
85
86/**
87 * @brief Bind a span to memory whose extent is only known at run time.
88 *
89 * Normalizes the empty case so a span with storage but no capacity - or capacity but no storage -
90 * cannot be constructed.
91 */
92inline pc_span pc_span_from(uint8_t *p, size_t cap)
93{
94 pc_span s;
95 s.buf = (p != nullptr && cap != 0) ? p : nullptr;
96 s.cap = (s.buf != nullptr) ? cap : 0;
97 s.pos = 0;
98 s.overflow = false;
99 return s;
100}
101
102/** @brief Bind a read-only span to memory whose extent is only known at run time. */
103inline pc_cspan pc_cspan_from(const uint8_t *p, size_t len)
104{
105 pc_cspan s;
106 s.buf = (p != nullptr && len != 0) ? p : nullptr;
107 s.len = (s.buf != nullptr) ? len : 0;
108 s.pos = 0;
109 s.err = false;
110 return s;
111}
112
113/** @brief True when the span refers to real storage and every write so far has fit. */
114inline bool pc_span_ok(const pc_span &s)
115{
116 return s.buf != nullptr && !s.overflow;
117}
118
119/** @brief True when the span refers to real storage, whether or not a write has overflowed. */
120inline bool pc_span_has_storage(const pc_span &s)
121{
122 return s.buf != nullptr;
123}
124
125/** @brief True when the read-only span refers to real bytes and no read has run past the end. */
126inline bool pc_cspan_ok(const pc_cspan &s)
127{
128 return s.buf != nullptr && !s.err;
129}
130
131/**
132 * @brief Bytes the payload needs - the backward direction.
133 *
134 * Equals the bytes written while everything fit. After an overflow it keeps counting, so a value
135 * greater than @ref pc_span::cap is exactly the capacity the run-length constant should have had.
136 */
137inline size_t pc_span_len(const pc_span &s)
138{
139 return s.pos;
140}
141
142/** @brief Writable bytes remaining (0 once the region is full or has overflowed). */
143inline size_t pc_span_room(const pc_span &s)
144{
145 return (s.pos < s.cap) ? (s.cap - s.pos) : 0;
146}
147
148/** @brief Rewind to empty, keeping the same storage and clearing the overflow flag. */
149inline void pc_span_reset(pc_span *s)
150{
151 s->pos = 0;
152 s->overflow = false;
153}
154
155/**
156 * @brief The sub-span starting @p off bytes into @p s (a fresh, empty cursor over that tail).
157 *
158 * Clamps rather than trapping: an @p off past the end yields an empty span, so a truncated frame
159 * produces a zero-capacity region that writes nothing instead of a pointer past the allocation.
160 */
161inline pc_span pc_span_after(const pc_span &s, size_t off)
162{
163 if (!pc_span_has_storage(s) || off >= s.cap)
164 {
165 return pc_span_from(nullptr, 0);
166 }
167 return pc_span_from(s.buf + off, s.cap - off);
168}
169
170/** @brief The first @p n bytes of @p s as a fresh span, clamped to what @p s actually has. */
171inline pc_span pc_span_first(const pc_span &s, size_t n)
172{
173 if (!pc_span_has_storage(s))
174 {
175 return pc_span_from(nullptr, 0);
176 }
177 return pc_span_from(s.buf, (n < s.cap) ? n : s.cap);
178}
179
180/**
181 * @brief A read-only view of what has been produced into @p s.
182 *
183 * The natural handoff once a frame is built: the length comes from the span's own cursor, so a
184 * reader cannot be handed a length that disagrees with the bytes. Yields an empty view if the span
185 * overflowed - a partially written frame must not be transmitted as though it were whole.
186 */
188{
189 if (!pc_span_ok(s))
190 {
191 return pc_cspan_from(nullptr, 0);
192 }
193 return pc_cspan_from(s.buf, s.pos);
194}
195
196/** @brief A read-only view of the first @p len bytes of @p s, clamped to its capacity. */
197inline pc_cspan pc_span_read(const pc_span &s, size_t len)
198{
199 if (!pc_span_has_storage(s))
200 {
201 return pc_cspan_from(nullptr, 0);
202 }
203 return pc_cspan_from(s.buf, (len < s.cap) ? len : s.cap);
204}
205
206#endif // PROTOCORE_SPAN_H
pc_cspan pc_span_produced(const pc_span &s)
A read-only view of what has been produced into s.
Definition span.h:187
pc_span pc_span_from(uint8_t *p, size_t cap)
Bind a span to memory whose extent is only known at run time.
Definition span.h:92
bool pc_cspan_ok(const pc_cspan &s)
True when the read-only span refers to real bytes and no read has run past the end.
Definition span.h:126
pc_cspan pc_cspan_from(const uint8_t *p, size_t len)
Bind a read-only span to memory whose extent is only known at run time.
Definition span.h:103
void pc_span_reset(pc_span *s)
Rewind to empty, keeping the same storage and clearing the overflow flag.
Definition span.h:149
size_t pc_span_room(const pc_span &s)
Writable bytes remaining (0 once the region is full or has overflowed).
Definition span.h:143
pc_span pc_span_after(const pc_span &s, size_t off)
The sub-span starting off bytes into s (a fresh, empty cursor over that tail).
Definition span.h:161
bool pc_span_has_storage(const pc_span &s)
True when the span refers to real storage, whether or not a write has overflowed.
Definition span.h:120
bool pc_span_ok(const pc_span &s)
True when the span refers to real storage and every write so far has fit.
Definition span.h:114
pc_span pc_span_first(const pc_span &s, size_t n)
The first n bytes of s as a fresh span, clamped to what s actually has.
Definition span.h:171
pc_cspan pc_span_read(const pc_span &s, size_t len)
A read-only view of the first len bytes of s, clamped to its capacity.
Definition span.h:197
size_t pc_span_len(const pc_span &s)
Bytes the payload needs - the backward direction.
Definition span.h:137
A read-only byte region.
Definition span.h:79
const uint8_t * buf
first byte, or nullptr when there is nothing to read
Definition span.h:80
size_t len
readable bytes at buf (0 whenever buf is nullptr)
Definition span.h:81
size_t pos
read cursor
Definition span.h:82
bool err
sticky: a read ran past len
Definition span.h:83
A writable byte region: the storage, the capacity that belongs to it, and what has been produced into...
Definition span.h:65
bool overflow
set once a write did not fit; pos then reports the size required
Definition span.h:69
uint8_t * buf
first byte, or nullptr when the region could not be obtained
Definition span.h:66
size_t pos
bytes the payload needs so far; keeps counting past cap on overflow
Definition span.h:68
size_t cap
bytes writable at buf (0 whenever buf is nullptr)
Definition span.h:67