src/corosio/src/ipv6_address.cpp

97.6% Lines (246/0/252) 100.0% List of functions (19/0/19)
ipv6_address.cpp
f(x) Functions (19)
Function Calls Lines Blocks
boost::corosio::ipv6_address::ipv6_address(std::array<unsigned char, 16ul> const&) :21 138x 100.0% 100.0% boost::corosio::ipv6_address::ipv6_address(boost::corosio::ipv4_address const&) :26 3x 100.0% 100.0% boost::corosio::ipv6_address::ipv6_address(std::basic_string_view<char, std::char_traits<char> >) :33 14x 100.0% 100.0% boost::corosio::ipv6_address::to_string[abi:cxx11]() const :42 15x 100.0% 73.0% boost::corosio::ipv6_address::to_buffer(char*, unsigned long) const :50 3x 100.0% 91.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% 100.0% boost::corosio::ipv6_address::loopback() :86 79x 100.0% 100.0% boost::corosio::operator<<(std::ostream&, boost::corosio::ipv6_address const&) :94 1x 100.0% 100.0% boost::corosio::ipv6_address::print_impl(char*) const :102 17x 100.0% 100.0% boost::corosio::ipv6_address::print_impl(char*) const::{lambda(unsigned char const*, unsigned char const*)#1}::operator()(unsigned char const*, unsigned char const*) const :104 53x 100.0% 100.0% boost::corosio::ipv6_address::print_impl(char*) const::{lambda(char*, unsigned short)#1}::operator()(char*, unsigned short) const :117 39x 100.0% 100.0% boost::corosio::(anonymous namespace)::hexdig_value(char) :226 510x 100.0% 100.0% boost::corosio::(anonymous namespace)::parse_h16(char const*&, char const*, unsigned char&, unsigned char&) :240 195x 94.1% 93.0% boost::corosio::(anonymous namespace)::maybe_octet(unsigned char const*) :272 10x 70.0% 62.0% boost::corosio::parse_ipv6_impl(std::basic_string_view<char, std::char_traits<char> >, boost::corosio::ipv6_address&) :288 81x 97.9% 98.0% boost::corosio::make_ipv6_address(std::basic_string_view<char, std::char_traits<char> >) :454 81x 100.0% 100.0%
Line TLA 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 <boost/corosio/detail/except.hpp>
14
15 #include <cstring>
16 #include <ostream>
17 #include <stdexcept>
18
19 namespace boost::corosio {
20
21 138x ipv6_address::ipv6_address(bytes_type const& bytes) noexcept
22 {
23 138x std::memcpy(addr_.data(), bytes.data(), 16);
24 138x }
25
26 3x ipv6_address::ipv6_address(ipv4_address const& addr) noexcept
27 {
28 3x auto const v = addr.to_bytes();
29 12x 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 14x ipv6_address::ipv6_address(std::string_view s)
34 {
35 14x auto [ec, addr] = make_ipv6_address(s);
36 14x if (ec)
37 3x detail::throw_system_error(ec, "invalid IPv6 address");
38 11x *this = addr;
39 11x }
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 30x 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 3x if (dest_size < max_str_len)
53 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 39x return addr_[0] == 0 && addr_[1] == 0 && addr_[2] == 0 && addr_[3] == 0 &&
80 15x addr_[4] == 0 && addr_[5] == 0 && addr_[6] == 0 && addr_[7] == 0 &&
81 43x addr_[8] == 0 && addr_[9] == 0 && addr_[10] == 0xff &&
82 25x addr_[11] == 0xff;
83 }
84
85 ipv6_address
86 79x ipv6_address::loopback() noexcept
87 {
88 79x ipv6_address a;
89 79x a.addr_[15] = 1;
90 79x 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 53x auto const count_zeroes = [](unsigned char const* first,
105 unsigned char const* const last) {
106 53x std::size_t n = 0;
107 146x while (first != last)
108 {
109 143x if (first[0] != 0 || first[1] != 0)
110 break;
111 93x n += 2;
112 93x first += 2;
113 }
114 53x return n;
115 };
116
117 39x auto const print_hex = [](char* dest, unsigned short v) {
118 39x char const* const dig = "0123456789abcdef";
119 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 }
129 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 }
137 27x else if (v >= 0x10)
138 {
139 1x *dest++ = dig[v >> 4];
140 1x v &= 0x0f;
141 1x *dest++ = dig[v];
142 }
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 34x auto const end = v4 ? (it + addr_.size() - 4) : it + addr_.size();
157
158 70x while (it != end)
159 {
160 53x auto n = count_zeroes(it, end);
161 53x if (n == 0)
162 {
163 39x it += 2;
164 39x continue;
165 }
166 14x if (n > best_len)
167 {
168 14x best_pos = static_cast<int>(it - addr_.data());
169 14x best_len = n;
170 }
171 14x it += n;
172 }
173
174 17x it = addr_.data();
175 17x if (best_pos != 0)
176 {
177 6x unsigned short v = static_cast<unsigned short>(it[0] * 256U + it[1]);
178 6x dest = print_hex(dest, v);
179 6x it += 2;
180 }
181 else
182 {
183 11x *dest++ = ':';
184 11x it += best_len;
185 11x if (it == end)
186 2x *dest++ = ':';
187 }
188
189 53x while (it != end)
190 {
191 36x *dest++ = ':';
192 36x if (it - addr_.data() == best_pos)
193 {
194 3x it += best_len;
195 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 33x dest = print_hex(dest, v);
201 33x it += 2;
202 }
203
204 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 2x auto sv = a.to_buffer(buf, sizeof(buf));
215 2x std::memcpy(dest, sv.data(), sv.size());
216 2x dest += sv.size();
217 }
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 510x hexdig_value(char c) noexcept
227 {
228 510x if (c >= '0' && c <= '9')
229 311x return c - '0';
230 199x if (c >= 'a' && c <= 'f')
231 55x return c - 'a' + 10;
232 144x if (c >= 'A' && c <= 'F')
233 8x return c - 'A' + 10;
234 136x return -1;
235 }
236
237 // Parse h16 (1-4 hex digits) returning 16-bit value
238 // Returns true on success, advances `it`
239 bool
240 195x parse_h16(
241 char const*& it,
242 char const* end,
243 unsigned char& hi,
244 unsigned char& lo) noexcept
245 {
246 195x if (it == end)
247 return false;
248
249 195x int d = hexdig_value(*it);
250 195x if (d < 0)
251 5x return false;
252
253 190x unsigned v = static_cast<unsigned>(d);
254 190x ++it;
255
256 277x for (int i = 0; i < 3 && it != end; ++i)
257 {
258 213x d = hexdig_value(*it);
259 213x if (d < 0)
260 126x break;
261 87x v = (v << 4) | static_cast<unsigned>(d);
262 87x ++it;
263 }
264
265 190x hi = static_cast<unsigned char>((v >> 8) & 0xff);
266 190x lo = static_cast<unsigned char>(v & 0xff);
267 190x return true;
268 }
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 10x unsigned short word = static_cast<unsigned short>(p[0]) * 256 +
275 10x static_cast<unsigned short>(p[1]);
276 10x if (word > 0x255)
277 return false;
278 10x if (((word >> 4) & 0xf) > 9)
279 return false;
280 10x if ((word & 0xf) > 9)
281 return false;
282 10x return true;
283 }
284
285 } // namespace
286
287 static std::error_code
288 81x parse_ipv6_impl(std::string_view s, ipv6_address& addr) noexcept
289 {
290 81x auto it = s.data();
291 81x auto const end = it + s.size();
292
293 81x int n = 8; // words needed
294 81x int b = -1; // value of n when '::' seen
295 81x bool c = false; // need colon
296 81x auto prev = it;
297 81x ipv6_address::bytes_type bytes{};
298 unsigned char hi, lo;
299
300 for (;;)
301 {
302 325x if (it == end)
303 {
304 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 280x if (*it == ':')
314 {
315 167x ++it;
316 167x if (it == end)
317 {
318 // expected ':'
319 5x return std::make_error_code(std::errc::invalid_argument);
320 }
321 162x if (*it == ':')
322 {
323 63x if (b == -1)
324 {
325 // first "::"
326 62x ++it;
327 62x --n;
328 62x b = n;
329 62x if (n == 0)
330 1x break;
331 61x c = false;
332 61x continue;
333 }
334 // extra "::" found
335 1x return std::make_error_code(std::errc::invalid_argument);
336 }
337 99x if (c)
338 {
339 96x prev = it;
340 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 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 113x if (*it == '.')
354 {
355 11x if (b == -1 && n > 1)
356 {
357 // not enough h16
358 6x return std::make_error_code(std::errc::invalid_argument);
359 }
360 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 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 53x while (v4_it != end &&
375 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 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 102x auto d = hexdig_value(*it);
393 102x if (b != -1 && d < 0)
394 {
395 // ends in "::"
396 2x break;
397 }
398
399 100x if (!c)
400 {
401 99x prev = it;
402 99x if (!parse_h16(it, end, hi, lo))
403 3x return std::make_error_code(std::errc::invalid_argument);
404 96x bytes[2 * (8 - n) + 0] = hi;
405 96x bytes[2 * (8 - n) + 1] = lo;
406 96x --n;
407 96x if (n == 0)
408 1x break;
409 95x c = true;
410 95x continue;
411 }
412
413 // ':' divides a word
414 1x return std::make_error_code(std::errc::invalid_argument);
415 244x }
416
417 // Must have consumed entire string
418 56x if (it != end)
419 5x return std::make_error_code(std::errc::invalid_argument);
420
421 51x if (b == -1)
422 {
423 2x addr = ipv6_address{bytes};
424 2x return {};
425 }
426
427 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 }
433 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 }
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 }
452
453 capy::io_result<ipv6_address>
454 81x make_ipv6_address(std::string_view s) noexcept
455 {
456 81x ipv6_address addr;
457 81x if (auto ec = parse_ipv6_impl(s, addr))
458 30x return {ec, ipv6_address{}};
459 51x return {std::error_code{}, addr};
460 }
461
462 } // namespace boost::corosio
463