ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
hex.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 hex.h
6 * @brief Base-16 conversion between raw bytes and their ASCII digits.
7 *
8 * Four operations cover every hex site in the library: one nibble out, one digit in, a machine
9 * word out, and a byte run in either direction. Each writes into a caller-owned buffer, takes no
10 * heap and no `<stdlib.h>`, and is inline so an unused one costs nothing.
11 *
12 * The decoders report failure through a negative return rather than a sentinel digit, so a
13 * malformed byte can never be mistaken for a valid zero.
14 *
15 * @author Douglas Quigg (dstroy0)
16 * @date 2026
17 */
18
19#ifndef PROTOCORE_HEX_H
20#define PROTOCORE_HEX_H
21
22#include <stdint.h>
23
24/** @brief The 16 hex digits, lowercase - the default rendering everywhere in this library. */
25static const char *const PC_HEX_LOWER = "0123456789abcdef";
26/** @brief The 16 hex digits, uppercase - for the protocols that specify capitals (RFC 3986 %XX). */
27static const char *const PC_HEX_UPPER = "0123456789ABCDEF";
28
29/** @brief Low nibble of @p nibble as a hex character, uppercase when @p upper. */
30inline char pc_hex_digit(uint8_t nibble, bool upper = false)
31{
32 return (upper ? PC_HEX_UPPER : PC_HEX_LOWER)[nibble & 0x0Fu];
33}
34
35/** @brief Hex character @p c as 0..15, or -1 when @p c is not a hex digit of either case. */
36inline int8_t pc_hex_val(char c)
37{
38 if (c >= '0' && c <= '9')
39 {
40 return (int8_t)(c - '0');
41 }
42 if (c >= 'a' && c <= 'f')
43 {
44 return (int8_t)(c - 'a' + 10);
45 }
46 if (c >= 'A' && c <= 'F')
47 {
48 return (int8_t)(c - 'A' + 10);
49 }
50 return -1;
51}
52
53/**
54 * @brief Write @p v into @p out as lowercase hex digits, most significant first, and return the
55 * digit count (1..8; zero renders as a single "0").
56 *
57 * No `0x` prefix, no NUL, and no leading zeros, which is the form the HTTP/1.1 chunked size line
58 * takes. The width is counted before any digit is written so each digit lands at its final index,
59 * which is why no reversal buffer is needed. @p out needs room for 8 characters.
60 */
61inline uint8_t pc_hex_u32(uint32_t v, char *out)
62{
63 uint8_t digits = 1;
64 for (uint32_t probe = v; probe >= 16u; probe >>= 4)
65 {
66 digits++;
67 }
68 for (uint8_t i = digits; i > 0; i--)
69 {
70 out[i - 1] = PC_HEX_LOWER[v & 0x0Fu];
71 v >>= 4;
72 }
73 return digits;
74}
75
76/**
77 * @brief Write @p n bytes of @p in into @p out as 2 * @p n hex characters plus a NUL.
78 * @param upper uppercase A-F when true, else lowercase a-f.
79 *
80 * @p out needs a capacity of at least 2 * @p n + 1; the caller owns that bound, since a byte run
81 * has no self-describing end.
82 */
83inline void pc_hex_encode(const uint8_t *in, uint32_t n, char *out, bool upper = false)
84{
85 const char *digits = upper ? PC_HEX_UPPER : PC_HEX_LOWER;
86 for (uint32_t i = 0; i < n; i++)
87 {
88 out[2 * i] = digits[(in[i] >> 4) & 0x0Fu];
89 out[2 * i + 1] = digits[in[i] & 0x0Fu];
90 }
91 out[2 * n] = '\0';
92}
93
94/**
95 * @brief Read exactly @p hexlen hex characters from @p in into @p out as @p hexlen / 2 bytes.
96 * @return the byte count written, or -1 when @p hexlen is odd, the result exceeds @p out_cap, or
97 * any character is not a hex digit. Nothing is written on the odd-length or capacity
98 * rejections; a bad digit stops the run where it is found.
99 */
100inline int32_t pc_hex_decode(const char *in, uint32_t hexlen, uint8_t *out, uint32_t out_cap)
101{
102 if ((hexlen % 2) != 0 || (hexlen / 2) > out_cap)
103 {
104 return -1;
105 }
106 for (uint32_t i = 0; i < hexlen; i += 2)
107 {
108 int8_t hi = pc_hex_val(in[i]);
109 int8_t lo = pc_hex_val(in[i + 1]);
110 if (hi < 0 || lo < 0)
111 {
112 return -1;
113 }
114 out[i / 2] = (uint8_t)((hi << 4) | lo);
115 }
116 return (int32_t)(hexlen / 2);
117}
118
119#endif // PROTOCORE_HEX_H
char pc_hex_digit(uint8_t nibble, bool upper=false)
Low nibble of nibble as a hex character, uppercase when upper.
Definition hex.h:30
uint8_t pc_hex_u32(uint32_t v, char *out)
Write v into out as lowercase hex digits, most significant first, and return the digit count (1....
Definition hex.h:61
void pc_hex_encode(const uint8_t *in, uint32_t n, char *out, bool upper=false)
Write n bytes of in into out as 2 * n hex characters plus a NUL.
Definition hex.h:83
int32_t pc_hex_decode(const char *in, uint32_t hexlen, uint8_t *out, uint32_t out_cap)
Read exactly hexlen hex characters from in into out as hexlen / 2 bytes.
Definition hex.h:100
int8_t pc_hex_val(char c)
Hex character c as 0..15, or -1 when c is not a hex digit of either case.
Definition hex.h:36