summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-24 17:21:47 +0200
committerDirk Engling <erdgeist@erdgeist.org>2026-10-24 17:21:47 +0200
commit60f55f34eeec70842b21accdd7fc0dc5d89493a2 (patch)
tree21a50b9d936df6b4348d6d1c95681a4269165804
parentea9c9a1766f1ce716c359fc04628855293fcffbd (diff)
Add an ip address parser and formatter for all formats opentracker needs
-rw-r--r--ot_hex.c20
-rw-r--r--ot_hex.h4
-rw-r--r--ot_ip.c201
-rw-r--r--ot_ip.h33
4 files changed, 249 insertions, 9 deletions
diff --git a/ot_hex.c b/ot_hex.c
index 17dd1ef..9581951 100644
--- a/ot_hex.c
+++ b/ot_hex.c
@@ -10,16 +10,18 @@
10/* Opentracker */ 10/* Opentracker */
11#include "ot_hex.h" 11#include "ot_hex.h"
12 12
13static int fromhex(unsigned char c) { 13int hex_value(int c) {
14 if (c >= '0' && c <= '9') 14 unsigned char u = (unsigned char)c;
15 return c - '0'; 15
16 c |= 0x20; /* fold upper case onto lower, digits are already handled */ 16 if (u >= '0' && u <= '9')
17 if (c >= 'a' && c <= 'f') 17 return u - '0';
18 return c - 'a' + 10; 18 u |= 0x20; /* fold upper case onto lower, digits are already handled */
19 if (u >= 'a' && u <= 'f')
20 return u - 'a' + 10;
19 return -1; 21 return -1;
20} 22}
21 23
22int hex_isdigit(int c) { return fromhex((unsigned char)c) >= 0; } 24int hex_isdigit(int c) { return hex_value(c) >= 0; }
23 25
24char *hex_fromhash(char *dest, const uint8_t *hash) { 26char *hex_fromhash(char *dest, const uint8_t *hash) {
25 static const char digits[] = "0123456789ABCDEF"; 27 static const char digits[] = "0123456789ABCDEF";
@@ -43,9 +45,9 @@ int hex_tohash(ot_hash hash, const char *src) {
43 45
44 /* Tested one character at a time so that a string ending early stops us 46 /* Tested one character at a time so that a string ending early stops us
45 at its terminator rather than one byte past it */ 47 at its terminator rather than one byte past it */
46 if ((high = fromhex((unsigned char)src[2 * i])) < 0) 48 if ((high = hex_value((unsigned char)src[2 * i])) < 0)
47 return 0; 49 return 0;
48 if ((low = fromhex((unsigned char)src[2 * i + 1])) < 0) 50 if ((low = hex_value((unsigned char)src[2 * i + 1])) < 0)
49 return 0; 51 return 0;
50 52
51 decoded[i] = (uint8_t)((high << 4) | low); 53 decoded[i] = (uint8_t)((high << 4) | low);
diff --git a/ot_hex.h b/ot_hex.h
index cb2e86f..3b45b61 100644
--- a/ot_hex.h
+++ b/ot_hex.h
@@ -22,4 +22,8 @@ int hex_tohash(ot_hash hash, const char *src);
22/* Non-zero if c is a hexadecimal digit, in either case. */ 22/* Non-zero if c is a hexadecimal digit, in either case. */
23int hex_isdigit(int c); 23int hex_isdigit(int c);
24 24
25/* The value of a hexadecimal digit, or -1 if c is not one. Shared with the
26 address scanner so that both agree on what a digit is. */
27int hex_value(int c);
28
25#endif 29#endif
diff --git a/ot_ip.c b/ot_ip.c
new file mode 100644
index 0000000..8cde7d2
--- /dev/null
+++ b/ot_ip.c
@@ -0,0 +1,201 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <stdint.h>
8#include <string.h>
9
10/* Opentracker */
11#include "ot_hex.h"
12#include "ot_ip.h"
13
14const char ip_v4mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
15
16/* The dotted quad is only ever reached through the v4 mapped form, so both
17 halves of it stay in here */
18
19static size_t ip4_format(char *dest, const uint8_t *ip) {
20 char *d = dest;
21 int i;
22
23 for (i = 0; i < 4; ++i) {
24 unsigned int octet = ip[i];
25
26 if (i)
27 *d++ = '.';
28 if (octet >= 100)
29 *d++ = (char)('0' + octet / 100);
30 if (octet >= 10)
31 *d++ = (char)('0' + (octet / 10) % 10);
32 *d++ = (char)('0' + octet % 10);
33 }
34
35 return (size_t)(d - dest);
36}
37
38static size_t ip4_scan(const char *src, uint8_t ip[4]) {
39 uint8_t octets[4];
40 const char *s = src;
41 int i;
42
43 for (i = 0; i < 4; ++i) {
44 unsigned int value = 0;
45 int digits = 0;
46
47 if (i && *s++ != '.')
48 return 0;
49
50 /* Leading zeros are tolerated, a value beyond one octet is not */
51 while (*s >= '0' && *s <= '9' && digits < 4) {
52 value = value * 10 + (unsigned int)(*s++ - '0');
53 ++digits;
54 }
55 if (!digits || value > 255)
56 return 0;
57
58 octets[i] = (uint8_t)value;
59 }
60
61 memcpy(ip, octets, sizeof(octets));
62 return (size_t)(s - src);
63}
64
65static unsigned int group_at(const char *ip, int n) { return ((unsigned int)(uint8_t)ip[2 * n] << 8) | (uint8_t)ip[2 * n + 1]; }
66
67size_t ip_format(char *dest, const ot_ip6 ip) {
68 static const char hexdigit[] = "0123456789abcdef";
69 char *d = dest;
70 int best_start = 0, best_len = 0, i = 0;
71
72 if (ip_is_v4mapped(ip))
73 return ip4_format(dest, (const uint8_t *)ip + sizeof(ip_v4mapped_prefix));
74
75 /* RFC 5952 4.2: the longest run of zero groups becomes "::", never a run
76 of one, and the first of two equal runs wins */
77 while (i < 8) {
78 int j = i;
79
80 if (group_at(ip, i)) {
81 ++i;
82 continue;
83 }
84 while (j < 8 && !group_at(ip, j))
85 ++j;
86 if (j - i > best_len) {
87 best_len = j - i;
88 best_start = i;
89 }
90 i = j;
91 }
92 if (best_len < 2)
93 best_len = 0;
94
95 for (i = 0; i < 8; ++i) {
96 unsigned int group;
97 int shift = 12;
98
99 if (best_len && i == best_start) {
100 *d++ = ':';
101 if (i + best_len == 8)
102 *d++ = ':'; /* nothing follows, so close the run */
103 i += best_len - 1;
104 continue;
105 }
106
107 if (i)
108 *d++ = ':';
109
110 group = group_at(ip, i);
111 while (shift && !(group >> shift))
112 shift -= 4; /* RFC 5952 4.1: no leading zeros */
113 for (;; shift -= 4) {
114 *d++ = hexdigit[(group >> shift) & 15];
115 if (!shift)
116 break;
117 }
118 }
119
120 return (size_t)(d - dest);
121}
122
123size_t ip_scan(const char *src, ot_ip6 ip) {
124 uint8_t prefix[16], suffix[16];
125 size_t prefixlen = 0, suffixlen = 0;
126 const char *s = src;
127 int have_gap = 0;
128 int need_colon = 0;
129
130 /* A plain IPv4 address is an address too, stored v4 mapped */
131 {
132 size_t consumed = ip4_scan(src, prefix);
133 if (consumed) {
134 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
135 memcpy(ip + sizeof(ip_v4mapped_prefix), prefix, 4);
136 return consumed;
137 }
138 }
139
140 for (;;) {
141 unsigned int value = 0;
142 int digits = 0, nibble;
143 uint8_t *into = have_gap ? suffix : prefix;
144 size_t *fill = have_gap ? &suffixlen : &prefixlen;
145
146 /* RFC 4291 2.2: one run of zero groups may be written "::" */
147 if (s[0] == ':' && s[1] == ':') {
148 if (have_gap)
149 return 0;
150 have_gap = 1;
151 need_colon = 0;
152 s += 2;
153 continue;
154 }
155
156 if (need_colon) {
157 if (*s != ':')
158 break;
159 ++s;
160 }
161
162 /* RFC 4291 2.2 form 3: the final two groups may be a dotted quad */
163 if (prefixlen + suffixlen + 4 <= 16) {
164 size_t consumed = ip4_scan(s, into + *fill);
165 if (consumed) {
166 *fill += 4;
167 s += consumed;
168 break;
169 }
170 }
171
172 while (digits < 4 && (nibble = hex_value(*s)) >= 0) {
173 value = value * 16 + (unsigned int)nibble;
174 ++digits;
175 ++s;
176 }
177 if (!digits) {
178 if (need_colon)
179 --s; /* leave the separator we ate for the caller */
180 break;
181 }
182
183 if (prefixlen + suffixlen + 2 > 16)
184 return 0;
185
186 into[(*fill)++] = (uint8_t)(value >> 8);
187 into[(*fill)++] = (uint8_t)value;
188 need_colon = 1;
189 }
190
191 if (!prefixlen && !suffixlen && !have_gap)
192 return 0;
193 if (!have_gap && prefixlen + suffixlen != 16)
194 return 0; /* without "::" every group has to be spelled out */
195
196 memset(ip, 0, 16);
197 memcpy(ip, prefix, prefixlen);
198 memcpy(ip + 16 - suffixlen, suffix, suffixlen);
199
200 return (size_t)(s - src);
201}
diff --git a/ot_ip.h b/ot_ip.h
new file mode 100644
index 0000000..ac8964d
--- /dev/null
+++ b/ot_ip.h
@@ -0,0 +1,33 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_IP_H__
7#define OT_IP_H__
8
9#include <stddef.h>
10#include <string.h>
11
12#include "trackerlogic.h"
13
14/* opentracker holds every address as sixteen bytes, writing IPv4 as the v4
15 mapped form of RFC 4291 section 2.5.5.2. */
16extern const char ip_v4mapped_prefix[12];
17
18static inline int ip_is_v4mapped(const void *ip) { return !memcmp(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); }
19
20/* Read an address and return how many characters it took, or zero if src
21 does not start with one. A plain IPv4 address is accepted and stored in
22 its v4 mapped form.
23
24 The count lets the caller carry on where the address ended, which is how
25 "address:port" and "address/bits" are read. */
26size_t ip_scan(const char *src, ot_ip6 ip);
27
28/* Write an address and return how many characters it took. Nothing is
29 terminated, so a caller may keep appending. A v4 mapped address is written
30 as the plain dotted quad rather than "::ffff:1.2.3.4". */
31size_t ip_format(char *dest, const ot_ip6 ip);
32
33#endif