src/corosio/src/ipv6_address.cpp

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