ipaddress 1.1.0
Loading...
Searching...
No Matches
base-v6.hpp
Go to the documentation of this file.
1/**
2 * @file base-v6.hpp
3 * @brief Adds basic functionality for working with IPv6
4 * @author Vladimir Shaleev
5 * @copyright MIT License
6 *
7 * This file provides foundational classes and functions necessary for handling IPv6 addresses,
8 * including parsing, constructing, and manipulating IPv6 address representations. It serves as
9 * a base for higher-level IPv6 address operations and utilities.
10 */
11
12#ifndef IPADDRESS_BASE_V6_HPP
13#define IPADDRESS_BASE_V6_HPP
14
15#include "ipv4-address.hpp"
16#include "uint128.hpp"
17
18namespace IPADDRESS_NAMESPACE {
19
20namespace internal {
21
22IPADDRESS_NODISCARD IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE bool is_invalid_scope_id_symbol(char c) IPADDRESS_NOEXCEPT {
23 return c == '%' || c == '/'
24#ifndef IPADDRESS_SCOPE_ID_SUPPORT_SPACES
25 || c == ' ' || (c >= '\t' && c <= '\r')
26#endif // IPADDRESS_SCOPE_ID_SUPPORT_SPACES
27 ;
28}
29
30} // namespace IPADDRESS_NAMESPACE::internal
31
32/**
33 * A template class providing the base functionality for IPv6 addresses.
34 *
35 * This class encapsulates the basic properties and operations for IPv6 addresses,
36 * such as version identification, size, and conversion from string representations.
37 * It is intended to be extended by more specialized IPv6 address classes.
38 *
39 * @tparam Ext The extension type that provides additional functionality.
40 */
41IPADDRESS_EXPORT template <typename Ext>
42class base_v6 {
43public:
44 using base_type = byte_array<16>;
45 using uint_type = ::IPADDRESS_NAMESPACE::uint128_t;
46
47 static constexpr ip_version base_version = ip_version::V6;
48 static constexpr size_t base_size = 16;
49 static constexpr size_t base_max_string_len = 41 + IPADDRESS_IPV6_SCOPE_MAX_LENGTH;
50 static constexpr size_t base_max_prefixlen = base_size * 8;
51
52 /**
53 * Retrieves the IP version of the address.
54 *
55 * @return The IP version enumeration value for IPv6.
56 */
58 return base_version;
59 }
60
61 /**
62 * Retrieves the size of the IPv6 address.
63 *
64 * @return The size of the IPv6 address in bytes.
65 */
67 return base_size;
68 }
69
70protected:
71 static constexpr size_t _min_parts = 3;
72 static constexpr size_t _max_parts = 8;
73
74 template <typename Iter>
75 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE ip_address_base<Ext> ip_from_string(Iter begin, Iter end, error_code& code, uint32_t& value) IPADDRESS_NOEXCEPT {
76 if (begin == end) {
78 return {};
79 }
80
81 auto ip_and_scope = split_scope_id(begin, end, code, value);
82
83 if (code != error_code::no_error) {
84 return {};
85 }
86
87 const auto parts = split_parts(begin, ip_and_scope.end_ip, value, code);
88
89 if (code != error_code::no_error) {
90 return {};
91 }
92
93 const auto result = get_parts_bound(parts, value, code);
94
95 if (code != error_code::no_error) {
96 return {};
97 }
98
99 auto ip = ip_address_base<Ext>(parse_parts(parts, value, std::get<0>(result), std::get<1>(result), std::get<2>(result), code));
100
101 #if IPADDRESS_IPV6_SCOPE_MAX_LENGTH > 0
102 if (code != error_code::no_error) {
103 return {};
104 }
105
106 if (ip_and_scope.scope_id[0] != '\0') {
107 ip._data.scope_id = make_fixed_string(ip_and_scope.scope_id, code);
108 }
109 #endif // IPADDRESS_IPV6_SCOPE_MAX_LENGTH
110
111 return ip;
112 }
113
114 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE ip_address_base<Ext> ip_from_prefix(size_t prefixlen) {
115 base_type bytes {};
116 for (size_t i = 0; i < (prefixlen >> 3); ++i) {
117 bytes[i] = 0xFF;
118 }
119 auto shift = (prefixlen - ((prefixlen >> 3) << 3));
120 if (shift > 0) {
121 auto byte = 0xFF ^ uint8_t(uint8_t(0xFF) >> shift);
122 bytes[prefixlen >> 3] = byte;
123 }
124 return ip_address_base<Ext>(bytes);
125 }
126
127 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE size_t ip_to_chars(const base_type& bytes, const fixed_string<IPADDRESS_IPV6_SCOPE_MAX_LENGTH>& scope_id, format fmt, char (&result)[base_max_string_len + 1]) IPADDRESS_NOEXCEPT {
128 constexpr auto hextets_count = size_t(base_size >> 1);
129 char hextets[hextets_count][5] = {};
130 const size_t max_hextets = base_size >> 1;
131 for (size_t i = 0; i < max_hextets; ++i) {
132 const uint16_t value = (uint16_t(bytes[i * 2]) << 8) | uint16_t(bytes[i * 2 + 1]);
133 to_hex(value, hextets[i]);
134 }
135
136 auto count = max_hextets;
137 auto has_doublecolon_start = false;
138 auto has_doublecolon_end = false;
139
140 if (fmt == format::compact || fmt == format::compressed) {
141 for (size_t i = 0; i < hextets_count; ++i) {
142 auto& hextet = hextets[i];
143 for (size_t h = 0; h < 3; ++h) {
144 if (hextet[0] != '0') {
145 break;
146 }
147 hextet[0] = hextet[1];
148 hextet[1] = hextet[2];
149 hextet[2] = hextet[3];
150 hextet[3] = hextet[4];
151 }
152 }
153
154 if (fmt == format::compressed) {
155 int best_doublecolon_len = 0;
156 int best_doublecolon_start = -1;
157 int doublecolon_len = 0;
158 int doublecolon_start = -1;
159
160 for (size_t i = 0; i < max_hextets; ++i) {
161 const auto& hextet = hextets[i];
162 if (hextet[0] == '0') {
163 doublecolon_len += 1;
164
165 if (doublecolon_start == -1) {
166 doublecolon_start = int(i);
167 }
168 if (doublecolon_len > best_doublecolon_len) {
169 best_doublecolon_len = doublecolon_len;
170 best_doublecolon_start = doublecolon_start;
171 }
172 } else {
173 doublecolon_len = 0;
174 doublecolon_start = -1;
175 }
176 }
177
178 if (best_doublecolon_len > 1) {
179 const int best_doublecolon_end = best_doublecolon_start + best_doublecolon_len;
180 if (best_doublecolon_end == max_hextets) {
181 has_doublecolon_end = true;
182 }
183 hextets[best_doublecolon_start][0] = '\0';
184 for (int h = best_doublecolon_start; h < max_hextets - 1; ++h) {
185 const auto lindex = h + 1;
186 const auto rindex = h + best_doublecolon_len;
187 if (rindex >= max_hextets) {
188 break;
189 }
190 hextets[lindex][0] = hextets[rindex][0];
191 hextets[lindex][1] = hextets[rindex][1];
192 hextets[lindex][2] = hextets[rindex][2];
193 hextets[lindex][3] = hextets[rindex][3];
194 }
195 count -= best_doublecolon_len - 1;
196
197 if (best_doublecolon_start == 0) {
198 has_doublecolon_start = true;
199 }
200 }
201 }
202 }
203
204 size_t offset = 0;
205 if (has_doublecolon_start) {
206 result[offset++] = ':';
207 }
208 for (size_t i = 0; i < count - 1; ++i) {
209 const auto& hextet = hextets[i];
210 for (size_t c = 0; hextet[c] != '\0'; ++c) {
211 result[offset++] = hextet[c];
212 }
213 result[offset++] = ':';
214 }
215 const auto hextet = hextets[count - 1];
216 for (size_t c = 0; hextet[c] != '\0'; ++c) {
217 result[offset++] = hextet[c];
218 }
219 if (has_doublecolon_end) {
220 result[offset++] = ':';
221 }
222 if (!scope_id.empty()) {
223 result[offset++] = '%';
224 const auto scope_id_size = scope_id.size();
225 for (size_t i = 0; i < scope_id_size; ++i) {
226 result[offset++] = scope_id[i];
227 }
228 }
229 result[offset] = '\0';
230 return offset;
231 }
232
233 IPADDRESS_NODISCARD static IPADDRESS_FORCE_INLINE std::string ip_reverse_pointer(const base_type& bytes) {
234 char result[base_max_string_len + 1] {};
235 const auto len = ip_to_chars(bytes, {}, format::full, result);
236 auto ip = std::string(result, len);
237 ip.erase(std::remove(ip.begin(), ip.end(), ':'), ip.end());
238 auto res = std::accumulate(std::next(ip.rbegin()), ip.rend(), std::string{ip.back()}, [](std::string a, char b) {
239 return std::move(a) + '.' + b;
240 });
241 return res + ".ip6.arpa";
242 }
243
244 template <typename Iter>
245 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE std::tuple<ip_address_base<Ext>, size_t> parse_netmask(Iter begin, Iter end, error_code& code, uint32_t& code_value) IPADDRESS_NOEXCEPT {
246 size_t prefixlen = 0;
247 auto it = begin;
248 auto has_prefixlen = false;
249 while (it < end) {
250 has_prefixlen = true;
251 const auto c = internal::next_char_or_error(it, end, code, code_value);
252 if (code != error_code::no_error) {
253 return std::make_tuple(ip_address_base<Ext>(), 0);
254 }
255 if (c >= '0' && c <= '9') {
256 prefixlen = prefixlen * 10 + (c - '0');
257 } else {
259 return std::make_tuple(ip_address_base<Ext>(), 0);
260 }
261 }
262
263 if (prefixlen > base_max_prefixlen) {
265 return std::make_tuple(ip_address_base<Ext>(), 0);
266 }
267
268 prefixlen = has_prefixlen ? prefixlen : base_max_prefixlen;
269
270 auto netmask = ip_from_prefix(prefixlen);
271 return std::make_tuple(netmask, prefixlen);
272 }
273
274 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE ip_address_base<Ext> strict_netmask(const ip_address_base<Ext>& address, const ip_address_base<Ext>& netmask, bool strict, error_code& code) IPADDRESS_NOEXCEPT {
275 const auto& bytes_address = address.bytes();
276 const auto& bytes_netmask = netmask.bytes();
277 base_type bytes{};
278
279 for (auto i = 0; i < base_size; ++i) {
280 bytes[i] = bytes_address[i] & bytes_netmask[i];
281 }
282
283 if (bytes != bytes_address) {
284 if (strict) {
286 return ip_address_base<Ext>();
287 } else {
288 return ip_address_base<Ext>(bytes);
289 }
290 }
291 return address;
292 }
293
294private:
295 template <typename Iter>
296 struct ip_and_scope {
297 Iter end_ip;
298 char scope_id[IPADDRESS_IPV6_SCOPE_MAX_LENGTH + 1];
299 };
300
301 template <typename Iter>
302 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE ip_and_scope<Iter> split_scope_id(Iter begin, Iter end, error_code& error, uint32_t& error_value) IPADDRESS_NOEXCEPT {
303 auto index = 0;
304 auto it = begin;
305 auto scope = false;
306 ip_and_scope<Iter> result{};
307 while (it < end) {
308 const auto c = internal::next_char_or_error(it, end, error, error_value);
309 if (error != error_code::no_error) {
310 return result;
311 }
312 if (!scope && c != '%') {
313 result.end_ip = it;
314 } else if (scope) {
315 if (index > IPADDRESS_IPV6_SCOPE_MAX_LENGTH - 1) {
317 return result;
318 }
319 if (internal::is_invalid_scope_id_symbol(c)) {
321 return result;
322 }
323 result.scope_id[index++] = c;
324 } else {
325 scope = true;
326 }
327 }
328 if (scope && result.scope_id[0] == '\0') {
330 return result;
331 }
332 return result;
333 }
334
335 template <typename Iter>
336 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE std::array<fixed_string<4>, _max_parts + 1> split_parts(Iter begin, Iter end, uint32_t& parts_count, error_code& error) IPADDRESS_NOEXCEPT {
337 IPADDRESS_CONSTEXPR std::array<fixed_string<4>, _max_parts + 1> empty_parts = {
338 make_fixed_string("\0\0\0\0"),
339 make_fixed_string("\0\0\0\0"),
340 make_fixed_string("\0\0\0\0"),
341 make_fixed_string("\0\0\0\0"),
342 make_fixed_string("\0\0\0\0"),
343 make_fixed_string("\0\0\0\0"),
344 make_fixed_string("\0\0\0\0"),
345 make_fixed_string("\0\0\0\0"),
346 make_fixed_string("\0\0\0\0")};
347
348 char parts[_max_parts + 1][5] = {};
349 char last_part[17] = {};
350
351 parts_count = 0;
352 int symbol = 0;
353 char prev_c = '\0';
354 bool has_double_colon = false;
355
356 Iter it = begin;
357 uint32_t error_symbol = 0;
358
359 while (it < end) {
360 auto c = internal::next_char_or_error(it, end, error, error_symbol);
361 if (error != error_code::no_error) {
362 parts_count = error_symbol;
363 return empty_parts;
364 }
365 if (!has_double_colon && c == ':' && prev_c == ':') {
366 has_double_colon = true;
367 }
368 if (parts_count > _max_parts) {
369 error = has_double_colon
372 return empty_parts;
373 }
374 if (c != ':') {
375 if (symbol > 15) {
377 return empty_parts;
378 }
379 last_part[symbol++] = c;
380 last_part[symbol] = '\0';
381 } else {
382 if (symbol > 4) {
384 return empty_parts;
385 }
386
387 auto& current_part = parts[parts_count++];
388 current_part[0] = last_part[0];
389 current_part[1] = last_part[1];
390 current_part[2] = last_part[2];
391 current_part[3] = last_part[3];
392
393 symbol = 0;
394 last_part[0] = '\0';
395 }
396 prev_c = c;
397 }
398
399 if (parts_count > _max_parts) {
400 if (parts[0][0] == '\0' && parts[1][0] != '\0') {
402 } else if (last_part[0] == '\0') {
404 } else {
406 }
407 return empty_parts;
408 }
409
410 auto has_ipv4 = false;
411
412 for (auto i = 0; i < symbol; ++i) {
413 if (last_part[i] == '.') {
414 has_ipv4 = true;
415 break;
416 }
417 }
418
419 if (has_ipv4) {
420 if (parts_count + 1 >= _max_parts) {
422 return empty_parts;
423 }
424
425 auto ipv4 = ipv4_address::parse(last_part, error).to_uint();
426
427 if (error != error_code::no_error) {
428 return empty_parts;
429 }
430
431 to_hex(uint16_t((ipv4 >> 16) & 0xFFFF), parts[parts_count++]);
432 to_hex(uint16_t(ipv4 & 0xFFFF), parts[parts_count++]);
433 } else {
434 if (symbol > 4) {
436 return empty_parts;
437 }
438
439 auto& current_part = parts[parts_count++];
440 current_part[0] = last_part[0];
441 current_part[1] = last_part[1];
442 current_part[2] = last_part[2];
443 current_part[3] = last_part[3];
444 }
445
446 if (parts_count < _min_parts) {
448 return empty_parts;
449 }
450
451 return {
452 make_fixed_string(parts[0]),
453 make_fixed_string(parts[1]),
454 make_fixed_string(parts[2]),
455 make_fixed_string(parts[3]),
456 make_fixed_string(parts[4]),
457 make_fixed_string(parts[5]),
458 make_fixed_string(parts[6]),
459 make_fixed_string(parts[7]),
460 make_fixed_string(parts[8])};
461 }
462
463 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE std::tuple<size_t, size_t, size_t> get_parts_bound(const std::array<fixed_string<4>, _max_parts + 1>& parts, size_t parts_count, error_code& error) IPADDRESS_NOEXCEPT {
464 size_t skip = 0;
465 for (size_t i = 1; i < parts_count - 1; ++i) {
466 if (parts[i].empty()) {
467 if (skip) {
468 error = error_code::most_one_double_colon_permitted;
469 return { 0, 0, 0 };
470 }
471 skip = i;
472 }
473 }
474
475 if (skip) {
476 auto parts_hi = skip;
477 auto parts_lo = parts_count - skip - 1;
478
479 if (parts[0].empty()) {
480 if (--parts_hi) {
482 return { 0, 0, 0 };
483 }
484 }
485
486 if (parts[parts_count - 1].empty()) {
487 if (--parts_lo) {
489 return { 0, 0, 0 };
490 }
491 }
492
493 const auto parts_skipped = _max_parts - (parts_hi + parts_lo);
494
495 if (parts_skipped < 1) {
497 return { 0, 0, 0 };
498 }
499
500 return { parts_hi, parts_lo, parts_skipped };
501 } else {
502 if (parts_count != _max_parts) {
504 return { 0, 0, 0 };
505 }
506
507 if (parts[0].empty()) {
509 return { 0, 0, 0 };
510 }
511
512 if (parts[parts_count - 1].empty()) {
514 return { 0, 0, 0 };
515 }
516
517 return { parts_count, 0, 0 };
518 }
519 }
520
521 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE base_type parse_parts(const std::array<fixed_string<4>, _max_parts + 1>& parts, size_t parts_count, size_t hi, size_t lo, size_t skipped, error_code& error) IPADDRESS_NOEXCEPT {
522 base_type result = {};
523 size_t index = 0;
524
525 for (size_t i = 0; i < hi; ++i) {
526 const auto part = parse_part(parts[i], error);
527 result[index++] = uint8_t(part >> 8);
528 result[index++] = uint8_t(part & 0xFF);
529
530 if (error != error_code::no_error) {
531 return {};
532 }
533 }
534 index += skipped << 1;
535
536 assert(parts_count > lo);
537 for (size_t i = parts_count - lo; i < parts_count; ++i) {
538 const auto part = parse_part(parts[i], error);
539 result[index++] = uint8_t(part >> 8);
540 result[index++] = uint8_t(part & 0xFF);
541
542 if (error != error_code::no_error) {
543 return {};
544 }
545 }
546 return result;
547 }
548
549 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE uint16_t parse_part(const fixed_string<4>& part, error_code& error) IPADDRESS_NOEXCEPT {
550 uint16_t value = 0;
551 for (size_t i = 0; i < part.size(); ++i) {
552 const auto c = part[i];
553 const auto power = pow16(part.size() - i - 1);
554 if (c >= '0' && c <= '9') {
555 value += (c - '0') * power;
556 } else if(c >= 'A' && c <= 'F') {
557 value += (c - 55) * power;
558 } else if (c >= 'a' && c <= 'f') {
559 value += (c - 87) * power;
560 } else {
561 error = error_code::part_has_invalid_symbol;
562 return 0;
563 }
564 }
565 return value;
566 }
567
568 IPADDRESS_NODISCARD static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE uint16_t pow16(size_t power) IPADDRESS_NOEXCEPT {
569 switch (power) {
570 case 0: return 1;
571 case 1: return 16;
572 case 2: return 16 * 16;
573 case 3: return 16 * 16 * 16;
574 default:
575 assert(!"Unreachable code");
576 return 0;
577 }
578 }
579
580 static IPADDRESS_CONSTEXPR IPADDRESS_FORCE_INLINE void to_hex(uint16_t value, char(&result)[5]) IPADDRESS_NOEXCEPT {
581 constexpr char digits[] = "0123456789abcdef";
582 for (auto i = 0, j = (4 - 1) * 4; i < 4; ++i, j -= 4) {
583 result[i] = digits[(value >> j) & 0x0f];
584 result[i + 1] = '\0';
585 }
586 }
587};
588
589template <typename Ext>
590constexpr ip_version base_v6<Ext>::base_version;
591
592template <typename Ext>
593constexpr size_t base_v6<Ext>::base_size;
594
595template <typename Ext>
596constexpr size_t base_v6<Ext>::base_max_string_len;
597
598template <typename Ext>
599constexpr size_t base_v6<Ext>::base_max_prefixlen;
600
601template <typename Ext>
602constexpr size_t base_v6<Ext>::_min_parts;
603
604template <typename Ext>
605constexpr size_t base_v6<Ext>::_max_parts;
606
607} // namespace IPADDRESS_NAMESPACE
608
609#endif // IPADDRESS_BASE_V6_HPP
A template class providing the base functionality for IPv6 addresses.
Definition base-v6.hpp:42
constexpr inline ip_version version() const noexcept
Retrieves the IP version of the address.
Definition base-v6.hpp:57
constexpr inline size_t size() const noexcept
Retrieves the size of the IPv6 address.
Definition base-v6.hpp:66
A template base class for IP address representations.
Definition ip-address-base.hpp:56
inline std::u8string to_u8string(format fmt=format::decimal) const
Converts the uint128_t value to a string representation.
Definition uint128.hpp:402
#define IPADDRESS_EXPORT
Definition config.hpp:42
#define IPADDRESS_NODISCARD
Definition config.hpp:98
#define IPADDRESS_FORCE_INLINE
Definition config.hpp:112
#define IPADDRESS_NAMESPACE
Definition config.hpp:38
#define IPADDRESS_NOEXCEPT
Definition config.hpp:89
ip_version
Enumerates the IP address versions.
Definition ip-address-base.hpp:29
@ V6
IPv6 version identifier.
format
Enumerates the formatting options for IP address strings.
Definition ip-address-base.hpp:40
@ compressed
Compressed format with maximal omission of segments or octets.
@ compact
Compact format with possible omission of leading zeros.
error_code
Enumeration of error codes for IP address parsing and validation.
Definition errors.hpp:52
@ trailing_colon_only_permitted_as_part_of_double_colon
A trailing colon is only permitted as part of a double colon.
@ empty_address
The IP address string is empty when it should contain a valid address.
@ exactly_8_parts_expected_without_double_colon
Without a double colon, exactly eight parts are expected.
@ leading_colon_only_permitted_as_part_of_double_colon
A leading colon is only permitted as part of a double colon.
@ invalid_scope_id
The scope ID in the IPv6 address is invalid.
@ no_error
Indicates the absence of any errors.
@ scope_id_is_too_long
The scope ID in the IPv6 address exceeds the maximum length.
@ part_is_more_4_chars
A part of the IPv6 address contains more than four characters.
@ has_host_bits_set
The address has host bits set when they are expected to be clear.
@ most_8_colons_permitted
The IPv6 address contains more than the maximum allowed number of colons.
@ least_3_parts
The IPv6 address contains fewer than the minimum required parts.
@ expected_at_most_7_other_parts_with_double_colon
With a double colon present, at most seven other parts are expected.
@ invalid_netmask
The provided netmask is not valid according to standard netmask formatting rules.
A template class for creating and managing a fixed-size array of bytes.
Definition byte-array.hpp:33
Fixed size string class.
Definition fixed-string.hpp:29