ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
bytes.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 bytes.h
6 * @brief The byte verbs - append into a pc_span, take out of a pc_cspan.
7 *
8 * A bounded byte region is one thing with two accessors. span.h is the region: where the storage
9 * came from, how big it is, how much has been produced, and whether anything overran. This file is
10 * what you do to it. The two halves are split that way so a region can be passed somewhere that only
11 * reads it without carrying an append API along.
12 *
13 * The subtle invariants live here once, so a bug is fixed in one place and every codec inherits it:
14 * keep counting `pos` past `cap` on overflow so the caller can size the buffer, sticky fault flags,
15 * and network (big-endian) byte order.
16 *
17 * These take pc_span / pc_cspan directly rather than being templated on a per-codec cursor struct.
18 * CBOR and MessagePack each declared their own writer and reader, all four field-identical to the
19 * spans, and the templates existed only to bind them by field name. One concrete pair replaces four
20 * near-duplicate structs and removes the deduction along with them.
21 *
22 * @author Douglas Quigg (dstroy0)
23 * @date 2026
24 */
25
26#ifndef PROTOCORE_BYTES_H
27#define PROTOCORE_BYTES_H
28
29#include "shared_primitives/endian.h" // pc_rd32be - the fixed-width serializers live there
30#include "shared_primitives/span.h" // pc_span / pc_cspan - the region these verbs act on
31#include <stddef.h>
32#include <stdint.h>
33
34// --- append into a pc_span ---
35
36/** @brief Append one byte; on overflow set the flag but keep counting @p pos. */
37inline void pc_bw_put(pc_span *w, uint8_t b)
38{
39 if (w->pos < w->cap)
40 {
41 w->buf[w->pos] = b;
42 }
43 else
44 {
45 w->overflow = true;
46 }
47 w->pos++; // keep counting so pc_span_len() reports the size the payload needs
48}
49
50/** @brief Append the low @p nbytes of @p val, big-endian (network order). */
51inline void pc_bw_put_be(pc_span *w, uint64_t val, int32_t nbytes)
52{
53 for (int32_t s = (nbytes - 1) * 8; s >= 0; s -= 8)
54 {
55 pc_bw_put(w, static_cast<uint8_t>(val >> s));
56 }
57}
58
59// --- take out of a pc_cspan ---
60
61/**
62 * @brief Read @p nbytes big-endian immediately after the tag byte at @p pos,
63 * advancing past the tag and the argument (pos += 1 + nbytes).
64 *
65 * Both CBOR heads and MessagePack format bytes are a 1-byte tag followed by a big-endian argument,
66 * so this consumes the tag + argument in one step. Sets the sticky err and returns false if the read
67 * would run past the buffer.
68 */
69inline bool pc_br_take_be(pc_cspan *r, size_t nbytes, uint64_t *out)
70{
71 if (r->pos + 1 + nbytes > r->len)
72 {
73 r->err = true;
74 return false;
75 }
76 uint64_t v{0};
77 for (size_t i = 0; i < nbytes; i++)
78 {
79 v = (v << 8) | r->buf[r->pos + 1 + i];
80 }
81 *out = v;
82 r->pos += 1 + nbytes;
83 return true;
84}
85
86// --- offset-passing reads over a caller-owned buffer (no region object needed) ---
87//
88// A length-prefixed field is the same shape in every protocol: a big-endian u32 count, then that
89// many bytes. SSH calls it a "string" (RFC 4251 sec 5) and had it written four separate times.
90// These bounds-check and advance an offset the caller owns, for parsers that walk a raw payload.
91
92/** @brief Read a big-endian u32 at @p *off, advancing it by 4. False if it would run past @p len. */
93inline bool pc_rd_u32(const uint8_t *p, size_t len, size_t *off, uint32_t *out)
94{
95 if (*off + 4 > len)
96 {
97 return false;
98 }
99 *out = pc_rd32be(p + *off);
100 *off += 4;
101 return true;
102}
103
104/**
105 * @brief Read a u32-length-prefixed blob: @p out points into @p p, @p slen is its length.
106 *
107 * Nothing is copied, so the result must not outlive @p p. On a length that would run past the end,
108 * @p *off is left where it started so the caller can report which field failed.
109 */
110inline bool pc_rd_str(const uint8_t *p, size_t len, size_t *off, const uint8_t **out, uint32_t *slen)
111{
112 size_t start{*off};
113 uint32_t n{0};
114 if (!pc_rd_u32(p, len, off, &n))
115 {
116 return false;
117 }
118 if (*off + n > len)
119 {
120 *off = start;
121 return false;
122 }
123 *out = p + *off;
124 *slen = n;
125 *off += n;
126 return true;
127}
128
129#endif // PROTOCORE_BYTES_H
void pc_bw_put_be(pc_span *w, uint64_t val, int32_t nbytes)
Append the low nbytes of val, big-endian (network order).
Definition bytes.h:51
bool pc_rd_str(const uint8_t *p, size_t len, size_t *off, const uint8_t **out, uint32_t *slen)
Read a u32-length-prefixed blob: out points into p, slen is its length.
Definition bytes.h:110
bool pc_rd_u32(const uint8_t *p, size_t len, size_t *off, uint32_t *out)
Read a big-endian u32 at *off, advancing it by 4. False if it would run past len.
Definition bytes.h:93
void pc_bw_put(pc_span *w, uint8_t b)
Append one byte; on overflow set the flag but keep counting pos.
Definition bytes.h:37
bool pc_br_take_be(pc_cspan *r, size_t nbytes, uint64_t *out)
Read nbytes big-endian immediately after the tag byte at pos, advancing past the tag and the argument...
Definition bytes.h:69
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
uint32_t pc_rd32be(const uint8_t *p)
Read a big-endian u32 at p.
Definition endian.h:119
A byte region whose run length is bound in both directions.
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