src/corosio/src/ipv6_address.cpp

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