ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ip.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/**
5 * @file ip.h
6 * @brief Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful
7 * text parsing, canonical formatting, and scope classification.
8 *
9 * One representation for both address families so the rest of the stack can carry a peer
10 * address without caring whether it is v4 or v6. The address bytes are stored in network
11 * (big-endian, left-to-right) order: a v4 address uses bytes[0..3], a v6 address bytes[0..15].
12 *
13 * Pure and host-testable - no lwIP, no Arduino, no heap, no stdlib parsing. The parser
14 * implements RFC 4291 §2.2 text forms (dotted-quad v4; v6 with `::` zero-compression and the
15 * embedded-v4 `::ffff:a.b.c.d` tail); the formatter emits the RFC 5952 canonical form
16 * (lower-case, no leading zeros, the longest zero run compressed to `::`, v4-mapped shown as
17 * dotted). ESP32 dual-stack bring-up (enabling IPv6 on the netif) lives in the physical layer
18 * behind PROTOCORE_ENABLE_IPV6; the TCP/UDP listeners already bind IPADDR_TYPE_ANY, so the server
19 * accepts v6 connections the moment the interface has a v6 address.
20 *
21 * @author Douglas Quigg (dstroy0)
22 * @date 2026
23 */
24
25#ifndef PROTOCORE_IP_H
26#define PROTOCORE_IP_H
27
28#include "protocore_config.h" // the entry point; it sets the widths and reaches protocore_types.h for proto_bool
29
31
32/** @brief Address family tag. */
34{
35 PROTOCORE_IP_NONE = 0, ///< empty / unparsed
36 PROTOCORE_IP_V4 = 4, ///< IPv4 (bytes[0..3])
37 PROTOCORE_IP_V6 = 6, ///< IPv6 (bytes[0..15])
39static_assert(sizeof(protocore_ip_family) == 1, "protocore_ip_family must stay one byte (PROTO_ENUM_PACKED); "
40 "protocore_ip is embedded wherever an address is stored");
41
42/** @brief Address scope, in rough order of reachability (used for allow/deny policy + logging). */
44{
45 PROTOCORE_IP_SCOPE_UNSPECIFIED = 0, ///< 0.0.0.0 / ::
46 PROTOCORE_IP_SCOPE_LOOPBACK, ///< 127.0.0.0/8 / ::1
47 PROTOCORE_IP_SCOPE_LINK_LOCAL, ///< 169.254.0.0/16 / fe80::/10
48 PROTOCORE_IP_SCOPE_PRIVATE, ///< RFC1918 (10/8, 172.16/12, 192.168/16) / ULA fc00::/7
49 PROTOCORE_IP_SCOPE_MULTICAST, ///< 224.0.0.0/4 / ff00::/8
50 PROTOCORE_IP_SCOPE_GLOBAL, ///< globally routable unicast
52static_assert(sizeof(protocore_ip_scope) == 1, "protocore_ip_scope must stay one byte (PROTO_ENUM_PACKED)");
53
54/** @brief A v4 or v6 address in network (big-endian) byte order. */
55typedef struct protocore_ip
56{
57 protocore_ip_family family; ///< address family tag
58 uint8_t bytes[16]; ///< network order; v4 uses the first 4
60
61/** @brief Longest text an ::IpNs::format can produce, including the NUL (RFC 5952 v4-mapped). */
62#define PROTOCORE_IP_STR_MAX 46
63
64/**
65 * @brief Parse an IPv4 or IPv6 textual address (RFC 4291 §2.2) into @p out.
66 * @return true on success (@p out->family set to PROTOCORE_IP_V4/V6), false if @p s is malformed.
67 */
68
69/**
70 * @brief Format @p ip into @p out as its RFC 5952 canonical text.
71 * @return the length written (excluding the NUL), or 0 if @p ip is empty or @p cap is too small
72 * (need up to ::PROTOCORE_IP_STR_MAX).
73 */
74
75/** @brief Classify @p ip into a ::protocore_ip_scope. */
76
77/** @brief True if @p a and @p b are the same family and address. */
78
79/** @brief True if @p ip is an IPv4-mapped IPv6 address (::ffff:a.b.c.d, RFC 4291 §2.5.5.2). */
81
82/**
83 * @brief Build a v4 ::protocore_ip from four octets (a.b.c.d).
84 */
85protocore_ip protocore_ip_from_v4_octets(uint8_t a, uint8_t b, uint8_t c, uint8_t d);
86
87/**
88 * @brief Build a v6 ::protocore_ip from 16 address bytes in network (big-endian) order.
89 */
91
92/**
93 * @brief The v4 address as a big-endian (network-order) uint32 (a<<24 | b<<16 | c<<8 | d).
94 * @return 0 if @p ip is not a v4 (or v4-mapped) address.
95 */
97
98/** @brief True if @p ip is empty (PROTOCORE_IP_NONE) or the all-zero unspecified address (0.0.0.0 / ::). */
99
100/**
101 * @brief CIDR containment: is @p addr inside the @p net / @p prefix_len block?
102 *
103 * The two must be the same family. @p prefix_len is 0..32 for v4, 0..128 for v6; the top
104 * @p prefix_len bits of the address bytes must match @p net (a prefix of 0 matches everything).
105 * This is the standard v4/v6 allowlist match.
106 * @return true if @p addr is covered; false on a family mismatch or an out-of-range prefix.
107 */
108
109/** @brief The address, or pair of addresses, one call acts on. */
110typedef struct
111{
112 const char *text; ///< the textual address a parse reads
113 const protocore_ip *ip; ///< the address a call reads
114 const protocore_ip *b; ///< the second address a compare or a prefix test reads
115 protocore_ip *out; ///< where a parse lands its result
116 char *buf; ///< where a format writes
117 size_t cap; ///< how much room that has
118 uint8_t prefix_len; ///< the prefix length a match tests to
119} IpArgs;
120
121/**
122 * @brief An IP address, as a value.
123 *
124 * @var IpNs::parse read a textual address, v4 or v6, into @c out
125 * @var IpNs::format write @c ip as text into @c out, returning the length
126 * @var IpNs::classify what scope the address names
127 * @var IpNs::equal whether two addresses are the same address
128 * @var IpNs::is_unspecified whether the address names nothing
129 * @var IpNs::prefix_match whether @c addr falls inside @c net at @c prefix_len bits
130 *
131 * No storage member: every operation reads its operands and holds nothing. Reached as @ref Ip,
132 * or through network.ip, which points at it: set @c args, invoke the call with @c Ip.internal,
133 * then read the result member.
134 */
142
143/** @brief The operands and the outcome. */
144extern IpVars IpV;
145
146/** @brief The entries. */
147typedef struct
148{
149 void (*const parse)(uint8_t *work);
150 void (*const format)(uint8_t *work);
151 void (*const classify)(uint8_t *work);
152 void (*const equal)(uint8_t *work);
153 void (*const is_unspecified)(uint8_t *work);
154 void (*const prefix_match)(uint8_t *work);
155} IpNs;
156
157// What the table binds, defined once in the .c and taking one parameter each: everything
158// else an entry needs is an operand in IpV or a region of the borrow at a fixed offset.
159void protocore_ip_parse(uint8_t *work);
160void protocore_ip_format(uint8_t *work);
161void protocore_ip_classify(uint8_t *work);
162void protocore_ip_equal(uint8_t *work);
163void protocore_ip_is_unspecified(uint8_t *work);
164void protocore_ip_prefix_match(uint8_t *work);
165
166// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
167// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
168// `Ip.parse(work)` resolves to a named function and becomes a DIRECT call. An extern table
169// leaves the call indirect and the symbol live at every level, -O2 -flto included.
170static const IpNs Ip __attribute__((unused)) = {
172 .format = protocore_ip_format,
173 .classify = protocore_ip_classify,
174 .equal = protocore_ip_equal,
175 .is_unspecified = protocore_ip_is_unspecified,
176 .prefix_match = protocore_ip_prefix_match,
177};
178
180
181#endif // PROTOCORE_IP_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
uint32_t protocore_ip_to_v4_be(const protocore_ip *ip)
The v4 address as a big-endian (network-order) uint32 (a<<24 | b<<16 | c<<8 | d).
protocore_ip protocore_ip_from_v4_octets(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
Build a v4 protocore_ip from four octets (a.b.c.d).
void protocore_ip_parse(uint8_t *work)
void protocore_ip_is_unspecified(uint8_t *work)
protocore_ip protocore_ip_from_v6_bytes(const uint8_t bytes[16])
Build a v6 protocore_ip from 16 address bytes in network (big-endian) order.
enum PROTO_ENUM_PACKED protocore_ip_scope
Address scope, in rough order of reachability (used for allow/deny policy + logging).
PROTOCORE_BEGIN_DECLS enum PROTO_ENUM_PACKED protocore_ip_family
Address family tag.
void protocore_ip_prefix_match(uint8_t *work)
@ PROTOCORE_IP_NONE
empty / unparsed
Definition ip.h:35
@ PROTOCORE_IP_V4
IPv4 (bytes[0..3])
Definition ip.h:36
@ PROTOCORE_IP_SCOPE_MULTICAST
224.0.0.0/4 / ff00::/8
Definition ip.h:49
@ PROTOCORE_IP_V6
IPv6 (bytes[0..15])
Definition ip.h:37
@ PROTOCORE_IP_SCOPE_LOOPBACK
127.0.0.0/8 / ::1
Definition ip.h:46
@ PROTOCORE_IP_SCOPE_GLOBAL
globally routable unicast
Definition ip.h:50
@ PROTOCORE_IP_SCOPE_UNSPECIFIED
0.0.0.0 / ::
Definition ip.h:45
@ PROTOCORE_IP_SCOPE_LINK_LOCAL
169.254.0.0/16 / fe80::/10
Definition ip.h:47
@ PROTOCORE_IP_SCOPE_PRIVATE
RFC1918 (10/8, 172.16/12, 192.168/16) / ULA fc00::/7.
Definition ip.h:48
proto_bool protocore_ip_is_v4_mapped(const protocore_ip *ip)
Parse an IPv4 or IPv6 textual address (RFC 4291 §2.2) into out.
void protocore_ip_classify(uint8_t *work)
IpVars IpV
The operands and the outcome.
void protocore_ip_equal(uint8_t *work)
void protocore_ip_format(uint8_t *work)
True if ip is empty (PROTOCORE_IP_NONE) or the all-zero unspecified address (0.0.0....
Definition ip.h:111
char * buf
where a format writes
Definition ip.h:116
const char * text
the textual address a parse reads
Definition ip.h:112
uint8_t prefix_len
the prefix length a match tests to
Definition ip.h:118
size_t cap
how much room that has
Definition ip.h:117
const protocore_ip * b
the second address a compare or a prefix test reads
Definition ip.h:114
protocore_ip * out
where a parse lands its result
Definition ip.h:115
const protocore_ip * ip
the address a call reads
Definition ip.h:113
The entries.
Definition ip.h:148
void(*const parse)(uint8_t *work)
Definition ip.h:149
Definition ip.h:136
protocore_ip_scope scope
Definition ip.h:140
proto_bool ok
Definition ip.h:138
IpArgs args
Definition ip.h:137
size_t n
Definition ip.h:139
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:56
uint8_t bytes[16]
network order; v4 uses the first 4
Definition ip.h:58
protocore_ip_family family
address family tag
Definition ip.h:57
#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