summaryrefslogtreecommitdiff
path: root/src/ot_ip.c
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-02 20:50:24 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-02 21:14:02 +0100
commitad541c67761b8aa6da1566802c347cac4018d0c2 (patch)
treea9166fa9d349911d69a6948e53b05344dcc348a0 /src/ot_ip.c
parentc679ff8a3b1418a5960cc49ba6458a0007df54fa (diff)
Move the build sources into src/
Diffstat (limited to 'src/ot_ip.c')
-rw-r--r--src/ot_ip.c199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/ot_ip.c b/src/ot_ip.c
new file mode 100644
index 0000000..9c76290
--- /dev/null
+++ b/src/ot_ip.c
@@ -0,0 +1,199 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <stdint.h>
6#include <string.h>
7
8/* Opentracker */
9#include "ot_hex.h"
10#include "ot_ip.h"
11
12const char ip_v4mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
13
14/* The dotted quad is only ever reached through the v4 mapped form, so both
15 halves of it stay in here */
16
17static size_t ip4_format(char *dest, const uint8_t *ip) {
18 char *d = dest;
19 int i;
20
21 for (i = 0; i < 4; ++i) {
22 unsigned int octet = ip[i];
23
24 if (i)
25 *d++ = '.';
26 if (octet >= 100)
27 *d++ = (char)('0' + octet / 100);
28 if (octet >= 10)
29 *d++ = (char)('0' + (octet / 10) % 10);
30 *d++ = (char)('0' + octet % 10);
31 }
32
33 return (size_t)(d - dest);
34}
35
36static size_t ip4_scan(const char *src, uint8_t ip[4]) {
37 uint8_t octets[4];
38 const char *s = src;
39 int i;
40
41 for (i = 0; i < 4; ++i) {
42 unsigned int value = 0;
43 int digits = 0;
44
45 if (i && *s++ != '.')
46 return 0;
47
48 /* Leading zeros are tolerated, a value beyond one octet is not */
49 while (*s >= '0' && *s <= '9' && digits < 4) {
50 value = value * 10 + (unsigned int)(*s++ - '0');
51 ++digits;
52 }
53 if (!digits || value > 255)
54 return 0;
55
56 octets[i] = (uint8_t)value;
57 }
58
59 memcpy(ip, octets, sizeof(octets));
60 return (size_t)(s - src);
61}
62
63static unsigned int group_at(const char *ip, int n) { return ((unsigned int)(uint8_t)ip[2 * n] << 8) | (uint8_t)ip[2 * n + 1]; }
64
65size_t ip_format(char *dest, const ot_ip6 ip) {
66 static const char hexdigit[] = "0123456789abcdef";
67 char *d = dest;
68 int best_start = 0, best_len = 0, i = 0;
69
70 if (ip_is_v4mapped(ip))
71 return ip4_format(dest, (const uint8_t *)ip + sizeof(ip_v4mapped_prefix));
72
73 /* RFC 5952 4.2: the longest run of zero groups becomes "::", never a run
74 of one, and the first of two equal runs wins */
75 while (i < 8) {
76 int j = i;
77
78 if (group_at(ip, i)) {
79 ++i;
80 continue;
81 }
82 while (j < 8 && !group_at(ip, j))
83 ++j;
84 if (j - i > best_len) {
85 best_len = j - i;
86 best_start = i;
87 }
88 i = j;
89 }
90 if (best_len < 2)
91 best_len = 0;
92
93 for (i = 0; i < 8; ++i) {
94 unsigned int group;
95 int shift = 12;
96
97 if (best_len && i == best_start) {
98 *d++ = ':';
99 if (i + best_len == 8)
100 *d++ = ':'; /* nothing follows, so close the run */
101 i += best_len - 1;
102 continue;
103 }
104
105 if (i)
106 *d++ = ':';
107
108 group = group_at(ip, i);
109 while (shift && !(group >> shift))
110 shift -= 4; /* RFC 5952 4.1: no leading zeros */
111 for (;; shift -= 4) {
112 *d++ = hexdigit[(group >> shift) & 15];
113 if (!shift)
114 break;
115 }
116 }
117
118 return (size_t)(d - dest);
119}
120
121size_t ip_parse(const char *src, ot_ip6 ip) {
122 uint8_t prefix[16], suffix[16];
123 size_t prefixlen = 0, suffixlen = 0;
124 const char *s = src;
125 int have_gap = 0;
126 int need_colon = 0;
127
128 /* A plain IPv4 address is an address too, stored v4 mapped */
129 {
130 size_t consumed = ip4_scan(src, prefix);
131 if (consumed) {
132 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
133 memcpy(ip + sizeof(ip_v4mapped_prefix), prefix, 4);
134 return consumed;
135 }
136 }
137
138 for (;;) {
139 unsigned int value = 0;
140 int digits = 0, nibble;
141 uint8_t *into = have_gap ? suffix : prefix;
142 size_t *fill = have_gap ? &suffixlen : &prefixlen;
143
144 /* RFC 4291 2.2: one run of zero groups may be written "::" */
145 if (s[0] == ':' && s[1] == ':') {
146 if (have_gap)
147 return 0;
148 have_gap = 1;
149 need_colon = 0;
150 s += 2;
151 continue;
152 }
153
154 if (need_colon) {
155 if (*s != ':')
156 break;
157 ++s;
158 }
159
160 /* RFC 4291 2.2 form 3: the final two groups may be a dotted quad */
161 if (prefixlen + suffixlen + 4 <= 16) {
162 size_t consumed = ip4_scan(s, into + *fill);
163 if (consumed) {
164 *fill += 4;
165 s += consumed;
166 break;
167 }
168 }
169
170 while (digits < 4 && (nibble = hex_value(*s)) >= 0) {
171 value = value * 16 + (unsigned int)nibble;
172 ++digits;
173 ++s;
174 }
175 if (!digits) {
176 if (need_colon)
177 --s; /* leave the separator we ate for the caller */
178 break;
179 }
180
181 if (prefixlen + suffixlen + 2 > 16)
182 return 0;
183
184 into[(*fill)++] = (uint8_t)(value >> 8);
185 into[(*fill)++] = (uint8_t)value;
186 need_colon = 1;
187 }
188
189 if (!prefixlen && !suffixlen && !have_gap)
190 return 0;
191 if (!have_gap && prefixlen + suffixlen != 16)
192 return 0; /* without "::" every group has to be spelled out */
193
194 memset(ip, 0, 16);
195 memcpy(ip, prefix, prefixlen);
196 memcpy(ip + 16 - suffixlen, suffix, suffixlen);
197
198 return (size_t)(s - src);
199}