src/corosio/src/ipv6_address.cpp

99.6% Lines (266/271) 100.0% List of functions (19/19) 90.1% Branches (137/152)
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 290x 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 28x 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 6x 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 22x 100.0% 63.6% 100.0% boost::corosio::ipv6_address::loopback() :86 76x 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 18x 100.0% 96.0% boost::corosio::ipv6_address::print_impl(char*) const::$_0::operator()(unsigned char const*, unsigned char const*) const :104 55x 100.0% 100.0% 100.0% boost::corosio::ipv6_address::print_impl(char*) const::$_1::operator()(char*, unsigned short) const :117 40x 100.0% 85.3% 100.0% boost::corosio::(anonymous namespace)::hexdig_value(char) :226 577x 100.0% 100.0% 100.0% boost::corosio::(anonymous namespace)::parse_h16(char const*&, char const*, unsigned char&, unsigned char&) :240 221x 100.0% 100.0% 92.0% boost::corosio::(anonymous namespace)::maybe_octet(unsigned char const*) :272 15x 100.0% 100.0% 100.0% boost::corosio::parse_ipv6_impl(std::__1::basic_string_view<char, std::__1::char_traits<char>>, boost::corosio::ipv6_address&) :288 101x 100.0% 98.2% 98.0% boost::corosio::make_ipv6_address(std::__1::basic_string_view<char, std::__1::char_traits<char>>) :454 101x 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 290x ipv6_address::ipv6_address(bytes_type const& bytes) noexcept
22 145x {
23 145x std::memcpy(addr_.data(), bytes.data(), 16);
24 145x }
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 28x ipv6_address::ipv6_address(std::string_view s)
34 14x {
35 14x auto [ec, addr] = make_ipv6_address(s);
36
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 3 times.
14x if (ec)
37 3x detail::throw_system_error(ec, "invalid IPv6 address");
38 11x *this = addr;
39 14x }
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 6x ipv6_address::to_buffer(char* dest, std::size_t dest_size) const
51 {
52
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6x if (dest_size < max_str_len)
53
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3x throw std::length_error("buffer too small for IPv6 address");
54 3x auto n = print_impl(dest);
55 3x 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 22x ipv6_address::is_v4_mapped() const noexcept
78 {
79
6/8
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
38x return addr_[0] == 0 && addr_[1] == 0 && addr_[2] == 0 && addr_[3] == 0 &&
80
4/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
16x addr_[4] == 0 && addr_[5] == 0 && addr_[6] == 0 && addr_[7] == 0 &&
81
4/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 4 times.
16x addr_[8] == 0 && addr_[9] == 0 && addr_[10] == 0xff &&
82 4x addr_[11] == 0xff;
83 }
84
85 ipv6_address
86 76x ipv6_address::loopback() noexcept
87 {
88 76x ipv6_address a;
89 76x a.addr_[15] = 1;
90 76x 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 18x ipv6_address::print_impl(char* dest) const noexcept
103 {
104 73x auto const count_zeroes = [](unsigned char const* first,
105 unsigned char const* const last) {
106 55x std::size_t n = 0;
107
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 152 times.
155x while (first != last)
108 {
109
4/4
✓ Branch 0 taken 137 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 37 times.
✓ Branch 3 taken 100 times.
152x if (first[0] != 0 || first[1] != 0)
110 52x break;
111 100x n += 2;
112 100x first += 2;
113 }
114 55x return n;
115 };
116
117 58x auto const print_hex = [](char* dest, unsigned short v) {
118 40x char const* const dig = "0123456789abcdef";
119
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 30 times.
40x 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 28 times.
30x 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 27 times.
28x 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 27x *dest++ = dig[v];
146 }
147 40x return dest;
148 };
149
150 18x auto const dest0 = dest;
151 // find longest run of zeroes
152 18x std::size_t best_len = 0;
153 18x int best_pos = -1;
154 18x auto it = addr_.data();
155 18x auto const v4 = is_v4_mapped();
156
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 16 times.
18x auto const end = v4 ? (it + addr_.size() - 4) : it + addr_.size();
157
158
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 18 times.
73x while (it != end)
159 {
160
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55x auto n = count_zeroes(it, end);
161
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 40 times.
55x if (n == 0)
162 {
163 40x it += 2;
164 40x continue;
165 }
166
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15x if (n > best_len)
167 {
168 15x best_pos = static_cast<int>(it - addr_.data());
169 15x best_len = n;
170 15x }
171 15x it += n;
172 }
173
174 18x it = addr_.data();
175
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 12 times.
18x 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 12x *dest++ = ':';
184 12x it += best_len;
185
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
12x if (it == end)
186 2x *dest++ = ':';
187 }
188
189
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 18 times.
55x while (it != end)
190 {
191 37x *dest++ = ':';
192
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 34 times.
37x 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 34x unsigned short v = static_cast<unsigned short>(it[0] * 256U + it[1]);
200
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34x dest = print_hex(dest, v);
201 34x it += 2;
202 }
203
204
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 16 times.
18x 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 18x 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 577x hexdig_value(char c) noexcept
227 {
228
4/4
✓ Branch 0 taken 560 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 225 times.
✓ Branch 3 taken 335 times.
577x if (c >= '0' && c <= '9')
229 335x return c - '0';
230
4/4
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 147 times.
✓ Branch 2 taken 36 times.
✓ Branch 3 taken 59 times.
242x if (c >= 'a' && c <= 'f')
231 59x return c - 'a' + 10;
232
4/4
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 139 times.
✓ Branch 2 taken 36 times.
✓ Branch 3 taken 8 times.
183x if (c >= 'A' && c <= 'F')
233 8x return c - 'A' + 10;
234 175x return -1;
235 577x }
236
237 // Parse h16 (1-4 hex digits) returning 16-bit value
238 // Returns true on success, advances `it`
239 bool
240 221x parse_h16(
241 char const*& it,
242 char const* end,
243 unsigned char& hi,
244 unsigned char& lo) noexcept
245 {
246 if (it == end) // LCOV_EXCL_LINE callers pre-check end-of-input
247 return false; // LCOV_EXCL_LINE callers pre-check end-of-input
248
249 221x int d = hexdig_value(*it);
250
2/2
✓ Branch 0 taken 204 times.
✓ Branch 1 taken 17 times.
221x if (d < 0)
251 17x return false;
252
253 204x unsigned v = static_cast<unsigned>(d);
254 204x ++it;
255
256
4/4
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 275 times.
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 233 times.
297x for (int i = 0; i < 3 && it != end; ++i)
257 {
258 233x d = hexdig_value(*it);
259
2/2
✓ Branch 0 taken 140 times.
✓ Branch 1 taken 93 times.
233x if (d < 0)
260 140x break;
261 93x v = (v << 4) | static_cast<unsigned>(d);
262 93x ++it;
263 93x }
264
265 204x hi = static_cast<unsigned char>((v >> 8) & 0xff);
266 204x lo = static_cast<unsigned char>(v & 0xff);
267 204x return true;
268 221x }
269
270 // Check if a hex word could be 0..255 if interpreted as decimal
271 bool
272 15x maybe_octet(unsigned char const* p) noexcept
273 {
274 30x unsigned short word = static_cast<unsigned short>(p[0]) * 256 +
275 15x static_cast<unsigned short>(p[1]);
276
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
15x if (word > 0x255)
277 1x return false;
278
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 13 times.
14x if (((word >> 4) & 0xf) > 9)
279 1x return false;
280
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 12 times.
13x if ((word & 0xf) > 9)
281 1x return false;
282 12x return true;
283 15x }
284
285 } // namespace
286
287 static std::error_code
288 101x parse_ipv6_impl(std::string_view s, ipv6_address& addr) noexcept
289 {
290 101x auto it = s.data();
291 101x auto const end = it + s.size();
292
293 101x int n = 8; // words needed
294 101x int b = -1; // value of n when '::' seen
295 101x bool c = false; // need colon
296 101x auto prev = it;
297 101x ipv6_address::bytes_type bytes{};
298 unsigned char hi, lo;
299
300 101x for (;;)
301 {
302
2/2
✓ Branch 0 taken 320 times.
✓ Branch 1 taken 45 times.
365x if (it == end)
303 {
304
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 4 times.
45x if (b != -1)
305 {
306 // end in "::"
307 41x 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 181 times.
✓ Branch 1 taken 139 times.
320x if (*it == ':')
314 {
315 181x ++it;
316
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 174 times.
181x if (it == end)
317 {
318 // expected ':'
319 7x return std::make_error_code(std::errc::invalid_argument);
320 }
321
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 105 times.
174x if (*it == ':')
322 {
323
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 68 times.
69x if (b == -1)
324 {
325 // first "::"
326 68x ++it;
327 68x --n;
328 68x b = n;
329
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 1 time.
68x if (n == 0)
330 1x break;
331 67x c = false;
332 67x continue;
333 }
334 // extra "::" found
335 1x return std::make_error_code(std::errc::invalid_argument);
336 }
337
2/2
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 3 times.
105x if (c)
338 {
339 102x prev = it;
340
2/2
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 2 times.
102x if (!parse_h16(it, end, hi, lo))
341 2x return std::make_error_code(std::errc::invalid_argument);
342 100x bytes[2 * (8 - n) + 0] = hi;
343 100x bytes[2 * (8 - n) + 1] = lo;
344 100x --n;
345
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 6 times.
100x if (n == 0)
346 6x break;
347 94x continue;
348 }
349 // expected h16
350 3x return std::make_error_code(std::errc::invalid_argument);
351 }
352
353
2/2
✓ Branch 0 taken 123 times.
✓ Branch 1 taken 16 times.
139x if (*it == '.')
354 {
355
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
16x if (b == -1 && n > 1)
356 {
357 // not enough h16
358 1x return std::make_error_code(std::errc::invalid_argument);
359 }
360
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3 times.
15x if (!maybe_octet(&bytes[std::size_t(2) * std::size_t(7 - n)]))
361 {
362 // invalid octet
363 3x return std::make_error_code(std::errc::invalid_argument);
364 }
365 // rewind the h16 and parse it as IPv4
366 12x it = prev;
367 12x auto [v4ec, v4] = make_ipv4_address(
368 12x std::string_view(it, static_cast<std::size_t>(end - it)));
369
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5 times.
12x if (v4ec)
370 7x 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 if (ckec) // LCOV_EXCL_LINE prefix of a parsed tail cannot fail
381 return ckec; // LCOV_EXCL_LINE prefix of a parsed tail cannot fail
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 123x auto d = hexdig_value(*it);
393
4/4
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 64 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 57 times.
123x if (b != -1 && d < 0)
394 {
395 // ends in "::"
396 2x break;
397 }
398
399
2/2
✓ Branch 0 taken 119 times.
✓ Branch 1 taken 2 times.
121x if (!c)
400 {
401 119x prev = it;
402
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 104 times.
119x if (!parse_h16(it, end, hi, lo))
403 15x return std::make_error_code(std::errc::invalid_argument);
404 104x bytes[2 * (8 - n) + 0] = hi;
405 104x bytes[2 * (8 - n) + 1] = lo;
406 104x --n;
407
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 103 times.
104x if (n == 0)
408 1x break;
409 103x c = true;
410 103x 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 51 times.
56x if (it != end)
419 5x return std::make_error_code(std::errc::invalid_argument);
420
421
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 2 times.
51x 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 45 times.
49x 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 26 times.
45x 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 26x auto const i0 = 2 * (7 - b);
444 26x auto const i1 = 2 * (b - n);
445 26x std::memmove(&bytes[16 - i1], &bytes[i0 + 2], i1);
446 26x std::memset(&bytes[i0], 0, 16 - (i0 + i1));
447 }
448
449 49x addr = ipv6_address{bytes};
450 49x return {};
451 101x }
452
453 capy::io_result<ipv6_address>
454 101x make_ipv6_address(std::string_view s) noexcept
455 {
456 101x ipv6_address addr;
457
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 51 times.
101x if (auto ec = parse_ipv6_impl(s, addr))
458 50x return {ec, ipv6_address{}};
459 51x return {std::error_code{}, addr};
460 101x }
461
462 } // namespace boost::corosio
463