src/corosio/src/ipv6_address.cpp

89.8% Lines (221/246) 100.0% Functions (17/17)
src/corosio/src/ipv6_address.cpp
Line Hits Source Code
1 //
2 // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #include <boost/corosio/ipv6_address.hpp>
11 #include <boost/corosio/ipv4_address.hpp>
12
13 #include <cstring>
14 #include <ostream>
15 #include <stdexcept>
16
17 namespace boost::corosio {
18
19 40 ipv6_address::ipv6_address(bytes_type const& bytes) noexcept
20 {
21 40 std::memcpy(addr_.data(), bytes.data(), 16);
22 40 }
23
24 3 ipv6_address::ipv6_address(ipv4_address const& addr) noexcept
25 {
26 3 auto const v = addr.to_bytes();
27 12 addr_ = {
28 3 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, v[0], v[1], v[2], v[3]}};
29 3 }
30
31 3 ipv6_address::ipv6_address(std::string_view s)
32 {
33 3 auto ec = parse_ipv6_address(s, *this);
34 3 if (ec)
35 2 throw std::invalid_argument("invalid IPv6 address");
36 1 }
37
38 std::string
39 6 ipv6_address::to_string() const
40 {
41 char buf[max_str_len];
42 6 auto n = print_impl(buf);
43 12 return std::string(buf, n);
44 }
45
46 std::string_view
47 2 ipv6_address::to_buffer(char* dest, std::size_t dest_size) const
48 {
49 2 if (dest_size < max_str_len)
50 throw std::length_error("buffer too small for IPv6 address");
51 2 auto n = print_impl(dest);
52 2 return std::string_view(dest, n);
53 }
54
55 bool
56 3 ipv6_address::is_unspecified() const noexcept
57 {
58 3 return *this == ipv6_address();
59 }
60
61 bool
62 10 ipv6_address::is_loopback() const noexcept
63 {
64 10 return *this == loopback();
65 }
66
67 bool
68 12 ipv6_address::is_v4_mapped() const noexcept
69 {
70 24 return addr_[0] == 0 && addr_[1] == 0 && addr_[2] == 0 && addr_[3] == 0 &&
71 10 addr_[4] == 0 && addr_[5] == 0 && addr_[6] == 0 && addr_[7] == 0 &&
72 28 addr_[8] == 0 && addr_[9] == 0 && addr_[10] == 0xff &&
73 16 addr_[11] == 0xff;
74 }
75
76 ipv6_address
77 22 ipv6_address::loopback() noexcept
78 {
79 22 ipv6_address a;
80 22 a.addr_[15] = 1;
81 22 return a;
82 }
83
84 std::ostream&
85 1 operator<<(std::ostream& os, ipv6_address const& addr)
86 {
87 char buf[ipv6_address::max_str_len];
88 1 os << addr.to_buffer(buf, sizeof(buf));
89 1 return os;
90 }
91
92 std::size_t
93 8 ipv6_address::print_impl(char* dest) const noexcept
94 {
95 27 auto const count_zeroes = [](unsigned char const* first,
96 unsigned char const* const last) {
97 27 std::size_t n = 0;
98 66 while (first != last)
99 {
100 65 if (first[0] != 0 || first[1] != 0)
101 break;
102 39 n += 2;
103 39 first += 2;
104 }
105 27 return n;
106 };
107
108 21 auto const print_hex = [](char* dest, unsigned short v) {
109 21 char const* const dig = "0123456789abcdef";
110 21 if (v >= 0x1000)
111 {
112 2 *dest++ = dig[v >> 12];
113 2 v &= 0x0fff;
114 2 *dest++ = dig[v >> 8];
115 2 v &= 0x0ff;
116 2 *dest++ = dig[v >> 4];
117 2 v &= 0x0f;
118 2 *dest++ = dig[v];
119 }
120 19 else if (v >= 0x100)
121 {
122 *dest++ = dig[v >> 8];
123 v &= 0x0ff;
124 *dest++ = dig[v >> 4];
125 v &= 0x0f;
126 *dest++ = dig[v];
127 }
128 19 else if (v >= 0x10)
129 {
130 *dest++ = dig[v >> 4];
131 v &= 0x0f;
132 *dest++ = dig[v];
133 }
134 else
135 {
136 19 *dest++ = dig[v];
137 }
138 21 return dest;
139 };
140
141 8 auto const dest0 = dest;
142 // find longest run of zeroes
143 8 std::size_t best_len = 0;
144 8 int best_pos = -1;
145 8 auto it = addr_.data();
146 8 auto const v4 = is_v4_mapped();
147 16 auto const end = v4 ? (it + addr_.size() - 4) : it + addr_.size();
148
149 35 while (it != end)
150 {
151 27 auto n = count_zeroes(it, end);
152 27 if (n == 0)
153 {
154 21 it += 2;
155 21 continue;
156 }
157 6 if (n > best_len)
158 {
159 6 best_pos = static_cast<int>(it - addr_.data());
160 6 best_len = n;
161 }
162 6 it += n;
163 }
164
165 8 it = addr_.data();
166 8 if (best_pos != 0)
167 {
168 2 unsigned short v = static_cast<unsigned short>(it[0] * 256U + it[1]);
169 2 dest = print_hex(dest, v);
170 2 it += 2;
171 }
172 else
173 {
174 6 *dest++ = ':';
175 6 it += best_len;
176 6 if (it == end)
177 1 *dest++ = ':';
178 }
179
180 27 while (it != end)
181 {
182 19 *dest++ = ':';
183 19 if (it - addr_.data() == best_pos)
184 {
185 it += best_len;
186 if (it == end)
187 *dest++ = ':';
188 continue;
189 }
190 19 unsigned short v = static_cast<unsigned short>(it[0] * 256U + it[1]);
191 19 dest = print_hex(dest, v);
192 19 it += 2;
193 }
194
195 8 if (v4)
196 {
197 ipv4_address::bytes_type bytes;
198 2 bytes[0] = it[0];
199 2 bytes[1] = it[1];
200 2 bytes[2] = it[2];
201 2 bytes[3] = it[3];
202 2 ipv4_address a(bytes);
203 2 *dest++ = ':';
204 char buf[ipv4_address::max_str_len];
205 2 auto sv = a.to_buffer(buf, sizeof(buf));
206 2 std::memcpy(dest, sv.data(), sv.size());
207 2 dest += sv.size();
208 }
209
210 8 return static_cast<std::size_t>(dest - dest0);
211 }
212
213
214 namespace {
215
216 // Convert hex character to value (0-15), or -1 if not hex
217 inline int
218 304 hexdig_value(char c) noexcept
219 {
220 304 if (c >= '0' && c <= '9')
221 199 return c - '0';
222 105 if (c >= 'a' && c <= 'f')
223 23 return c - 'a' + 10;
224 82 if (c >= 'A' && c <= 'F')
225 return c - 'A' + 10;
226 82 return -1;
227 }
228
229 // Parse h16 (1-4 hex digits) returning 16-bit value
230 // Returns true on success, advances `it`
231 bool
232 120 parse_h16(
233 char const*& it,
234 char const* end,
235 unsigned char& hi,
236 unsigned char& lo) noexcept
237 {
238 120 if (it == end)
239 return false;
240
241 120 int d = hexdig_value(*it);
242 120 if (d < 0)
243 2 return false;
244
245 118 unsigned v = static_cast<unsigned>(d);
246 118 ++it;
247
248 164 for (int i = 0; i < 3 && it != end; ++i)
249 {
250 124 d = hexdig_value(*it);
251 124 if (d < 0)
252 78 break;
253 46 v = (v << 4) | static_cast<unsigned>(d);
254 46 ++it;
255 }
256
257 118 hi = static_cast<unsigned char>((v >> 8) & 0xff);
258 118 lo = static_cast<unsigned char>(v & 0xff);
259 118 return true;
260 }
261
262 // Check if a hex word could be 0..255 if interpreted as decimal
263 bool
264 4 maybe_octet(unsigned char const* p) noexcept
265 {
266 4 unsigned short word = static_cast<unsigned short>(p[0]) * 256 +
267 4 static_cast<unsigned short>(p[1]);
268 4 if (word > 0x255)
269 return false;
270 4 if (((word >> 4) & 0xf) > 9)
271 return false;
272 4 if ((word & 0xf) > 9)
273 return false;
274 4 return true;
275 }
276
277 } // namespace
278
279 std::error_code
280 52 parse_ipv6_address(std::string_view s, ipv6_address& addr) noexcept
281 {
282 52 auto it = s.data();
283 52 auto const end = it + s.size();
284
285 52 int n = 8; // words needed
286 52 int b = -1; // value of n when '::' seen
287 52 bool c = false; // need colon
288 52 auto prev = it;
289 52 ipv6_address::bytes_type bytes{};
290 unsigned char hi, lo;
291
292 for (;;)
293 {
294 205 if (it == end)
295 {
296 32 if (b != -1)
297 {
298 // end in "::"
299 28 break;
300 }
301 // not enough words
302 4 return std::make_error_code(std::errc::invalid_argument);
303 }
304
305 173 if (*it == ':')
306 {
307 109 ++it;
308 109 if (it == end)
309 {
310 // expected ':'
311 3 return std::make_error_code(std::errc::invalid_argument);
312 }
313 106 if (*it == ':')
314 {
315 42 if (b == -1)
316 {
317 // first "::"
318 41 ++it;
319 41 --n;
320 41 b = n;
321 41 if (n == 0)
322 break;
323 41 c = false;
324 41 continue;
325 }
326 // extra "::" found
327 1 return std::make_error_code(std::errc::invalid_argument);
328 }
329 64 if (c)
330 {
331 61 prev = it;
332 61 if (!parse_h16(it, end, hi, lo))
333 return std::make_error_code(std::errc::invalid_argument);
334 61 bytes[2 * (8 - n) + 0] = hi;
335 61 bytes[2 * (8 - n) + 1] = lo;
336 61 --n;
337 61 if (n == 0)
338 5 break;
339 56 continue;
340 }
341 // expected h16
342 3 return std::make_error_code(std::errc::invalid_argument);
343 }
344
345 64 if (*it == '.')
346 {
347 4 if (b == -1 && n > 1)
348 {
349 // not enough h16
350 return std::make_error_code(std::errc::invalid_argument);
351 }
352 4 if (!maybe_octet(&bytes[std::size_t(2) * std::size_t(7 - n)]))
353 {
354 // invalid octet
355 return std::make_error_code(std::errc::invalid_argument);
356 }
357 // rewind the h16 and parse it as IPv4
358 4 it = prev;
359 4 ipv4_address v4;
360 4 auto ec = parse_ipv4_address(
361 4 std::string_view(it, static_cast<std::size_t>(end - it)), v4);
362 4 if (ec)
363 return ec;
364 // Must consume exactly the IPv4 address portion
365 // Re-parse to find where it ends
366 4 auto v4_it = it;
367 45 while (v4_it != end &&
368 41 (*v4_it == '.' || (*v4_it >= '0' && *v4_it <= '9')))
369 41 ++v4_it;
370 // Verify it parsed correctly by re-parsing the exact substring
371 4 ipv4_address v4_check;
372 4 ec = parse_ipv4_address(
373 4 std::string_view(it, static_cast<std::size_t>(v4_it - it)),
374 v4_check);
375 4 if (ec)
376 return ec;
377 4 it = v4_it;
378 4 auto const b4 = v4_check.to_bytes();
379 4 bytes[2 * (7 - n) + 0] = b4[0];
380 4 bytes[2 * (7 - n) + 1] = b4[1];
381 4 bytes[2 * (7 - n) + 2] = b4[2];
382 4 bytes[2 * (7 - n) + 3] = b4[3];
383 4 --n;
384 4 break;
385 }
386
387 60 auto d = hexdig_value(*it);
388 60 if (b != -1 && d < 0)
389 {
390 // ends in "::"
391 break;
392 }
393
394 60 if (!c)
395 {
396 59 prev = it;
397 59 if (!parse_h16(it, end, hi, lo))
398 2 return std::make_error_code(std::errc::invalid_argument);
399 57 bytes[2 * (8 - n) + 0] = hi;
400 57 bytes[2 * (8 - n) + 1] = lo;
401 57 --n;
402 57 if (n == 0)
403 1 break;
404 56 c = true;
405 56 continue;
406 }
407
408 // ':' divides a word
409 1 return std::make_error_code(std::errc::invalid_argument);
410 153 }
411
412 // Must have consumed entire string
413 38 if (it != end)
414 2 return std::make_error_code(std::errc::invalid_argument);
415
416 36 if (b == -1)
417 {
418 1 addr = ipv6_address{bytes};
419 1 return {};
420 }
421
422 35 if (b == n)
423 {
424 // "::" last
425 2 auto const i = 2 * (7 - n);
426 2 std::memset(&bytes[i], 0, 16 - i);
427 }
428 33 else if (b == 7)
429 {
430 // "::" first
431 19 auto const i = 2 * (b - n);
432 19 std::memmove(&bytes[16 - i], &bytes[2], i);
433 19 std::memset(&bytes[0], 0, 16 - i);
434 }
435 else
436 {
437 // "::" in middle
438 14 auto const i0 = 2 * (7 - b);
439 14 auto const i1 = 2 * (b - n);
440 14 std::memmove(&bytes[16 - i1], &bytes[i0 + 2], i1);
441 14 std::memset(&bytes[i0], 0, 16 - (i0 + i1));
442 }
443
444 35 addr = ipv6_address{bytes};
445 35 return {};
446 }
447
448 } // namespace boost::corosio
449