src/corosio/src/ipv6_address.cpp

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