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