libs/corosio/src/corosio/src/ipv6_address.cpp

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