26 if (c >=
'0' && c <=
'9')
30 if (c >=
'a' && c <=
'f')
34 if (c >=
'A' && c <=
'F')
42bool parse_v4(
const char *s,
size_t len, uint8_t out[4])
47 for (
size_t i = 0; i < len; i++)
52 if (digits == 0 || oct >= 3)
56 out[oct++] = (uint8_t)val;
60 else if (c >=
'0' && c <=
'9')
66 val = (val < 0 ? 0 : val) * 10 + (c -
'0');
78 if (oct != 3 || digits == 0)
82 out[3] = (uint8_t)val;
87bool parse_hextet(
const char *s,
size_t len, uint16_t *out)
89 if (len == 0 || len > 4)
94 for (
size_t i = 0; i < len; i++)
112void assemble_v6(
const uint16_t *head,
int nhead,
const uint16_t *tail,
int ntail, uint8_t out[16])
115 memset(g, 0,
sizeof(g));
116 for (
int k = 0; k < nhead; k++)
120 for (
int k = 0; k < ntail; k++)
122 g[8 - ntail + k] = tail[k];
124 for (
int k = 0; k < 8; k++)
126 out[2 * k] = (uint8_t)(g[k] >> 8);
127 out[2 * k + 1] = (uint8_t)(g[k] & 0xFF);
139bool parse_v6(
const char *s,
size_t len, uint8_t out[16])
145 bool seen_dc =
false;
146 uint16_t *cur = head;
152 if (len >= 1 && s[0] ==
':')
154 if (len < 2 || s[1] !=
':')
173 bool has_dot =
false;
174 while (j < len && s[j] !=
':')
187 if (!parse_v4(s + i, tlen, q) || *ncur > 6 || j != len)
191 cur[(*ncur)++] = (uint16_t)((q[0] << 8) | q[1]);
192 cur[(*ncur)++] = (uint16_t)((q[2] << 8) | q[3]);
197 if (!parse_hextet(s + i, tlen, &hx) || *ncur >= 8)
207 if (i + 1 < len && s[i + 1] ==
':')
234 int total = nhead + ntail;
235 if (seen_dc ? (total > 7) : (total != 8))
240 assemble_v6(head, nhead, tail, ntail, out);
249size_t put_u8(uint8_t v,
char *o)
254 o[n++] = (char)(
'0' + v / 100);
258 o[n++] = (char)(
'0' + (v / 10) % 10);
260 o[n++] = (char)(
'0' + v % 10);
265size_t format_v4(
const uint8_t *b,
char *out,
size_t cap)
269 for (
int k = 0; k < 4; k++)
275 n += put_u8(b[k], tmp + n);
287size_t put_hex16(uint16_t v,
char *o)
289 static const char H[] =
"0123456789abcdef";
297 for (
int k = 0; k < n; k++)
308void longest_zero_run(
const uint16_t g[8],
int *start,
int *len)
314 for (
int k = 0; k < 8; k++)
323 if (cur_len > best_len)
326 best_start = cur_start;
335 *start = (best_len >= 2) ? best_start : -1;
344bool is_v4_mapped_bytes(
const uint8_t *b)
346 for (
int k = 0; k < 10; k++)
353 return b[10] == 0xff && b[11] == 0xff;
359 if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0)
367 if (b[0] == 169 && b[1] == 254)
371 if (b[0] == 10 || (b[0] == 172 && b[1] >= 16 && b[1] <= 31) || (b[0] == 192 && b[1] == 168))
375 if (b[0] >= 224 && b[0] <= 239)
386 for (
int k = 0; k < 16; k++)
399 bool loopback = (b[15] == 1);
400 for (
int k = 0; k < 15 && loopback; k++)
412 if (is_v4_mapped_bytes(b))
414 return classify_v4(b + 12);
420 if (b[0] == 0xfe && (b[1] & 0xc0) == 0x80)
424 if ((b[0] & 0xfe) == 0xfc)
452 else if (s[len] ==
'.')
466 memset(out->
bytes, 0, 16);
469 if (!parse_v6(s, len, out->
bytes))
478 if (!parse_v4(s, len, out->
bytes))
490 if (!ip || !out || cap == 0)
496 return format_v4(ip->
bytes, out, cap);
507 size_t tn = format_v4(ip->
bytes + 12, tail,
sizeof(tail));
510 if (tn == 0 || 7 + tn + 1 > cap)
514 memcpy(out,
"::ffff:", 7);
515 memcpy(out + 7, tail, tn);
521 for (
int k = 0; k < 8; k++)
523 g[k] = (uint16_t)((ip->
bytes[2 * k] << 8) | ip->
bytes[2 * k + 1]);
528 longest_zero_run(g, &zs, &zl);
533 for (
int k = 0; k < 8; k++)
535 if (zs != -1 && k >= zs && k < zs + zl)
547 n += put_hex16(g[k], tmp + n);
549 if (zs != -1 && zs + zl == 8)
576 return classify_v4(ip->
bytes);
580 return classify_v6(ip->
bytes);
604 return memcmp(a->
bytes, b->
bytes, (
size_t)n) == 0;
610 memset(&ip, 0,
sizeof(ip));
623 memcpy(ip.
bytes, bytes, 16);
633 const uint8_t *b = ip->
bytes;
636 return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3];
640 return ((uint32_t)b[12] << 24) | ((uint32_t)b[13] << 16) | ((uint32_t)b[14] << 8) | b[15];
652 for (
int i = 0; i < n; i++)
669 if (bits == 0 || prefix_len > bits)
673 int whole = prefix_len / 8;
674 for (
int i = 0; i < whole; i++)
681 int rem = prefix_len % 8;
684 uint8_t mask = (uint8_t)(0xFF << (8 - rem));
685 if ((addr->
bytes[whole] & mask) != (net->
bytes[whole] & mask))
pc_ip pc_ip_from_v4_octets(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
Build a v4 pc_ip from four octets (a.b.c.d).
bool pc_ip_equal(const pc_ip *a, const pc_ip *b)
True if a and b are the same family and address.
bool pc_ip_prefix_match(const pc_ip *addr, const pc_ip *net, uint8_t prefix_len)
CIDR containment: is addr inside the net / prefix_len block?
size_t pc_ip_format(const pc_ip *ip, char *out, size_t cap)
Format ip into out as its RFC 5952 canonical text.
pc_ip pc_ip_from_v6_bytes(const uint8_t bytes[16])
Build a v6 pc_ip from 16 address bytes in network (big-endian) order.
bool pc_ip_parse(const char *s, pc_ip *out)
Parse an IPv4 or IPv6 textual address (RFC 4291 §2.2) into out.
pc_ip_scope pc_ip_classify(const pc_ip *ip)
Classify ip into a pc_ip_scope.
bool pc_ip_is_unspecified(const pc_ip *ip)
True if ip is empty (pc_ip_family::PC_IP_NONE) or the all-zero unspecified address (0....
uint32_t pc_ip_to_v4_be(const pc_ip *ip)
The v4 address as a big-endian (network-order) uint32 (a<<24 | b<<16 | c<<8 | d).
bool pc_ip_is_v4_mapped(const pc_ip *ip)
True if ip is an IPv4-mapped IPv6 address (::ffff:a.b.c.d, RFC 4291 §2.5.5.2).
Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful text parsing,...
pc_ip_scope
Address scope, in rough order of reachability (used for allow/deny policy + logging).
@ PC_IP_SCOPE_GLOBAL
globally routable unicast
@ PC_IP_SCOPE_LINK_LOCAL
169.254.0.0/16 / fe80::/10
@ PC_IP_SCOPE_LOOPBACK
127.0.0.0/8 / ::1
@ PC_IP_SCOPE_MULTICAST
224.0.0.0/4 / ff00::/8
@ PC_IP_SCOPE_PRIVATE
RFC1918 (10/8, 172.16/12, 192.168/16) / ULA fc00::/7.
@ PC_IP_SCOPE_UNSPECIFIED
0.0.0.0 / ::
@ PC_IP_NONE
empty / unparsed
@ PC_IP_V6
IPv6 (bytes[0..15])
@ PC_IP_V4
IPv4 (bytes[0..3])
#define PC_IP_STR_MAX
Longest text an pc_ip_format can produce, including the NUL (RFC 5952 v4-mapped).
A v4 or v6 address in network (big-endian) byte order.
pc_ip_family family
address family tag
uint8_t bytes[16]
network order; v4 uses the first 4