TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_IPV6_ADDRESS_HPP
12 : #define BOOST_COROSIO_IPV6_ADDRESS_HPP
13 :
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/ipv4_address.hpp>
16 :
17 : #include <boost/capy/io_result.hpp>
18 :
19 : #include <array>
20 : #include <compare>
21 : #include <cstdint>
22 : #include <functional>
23 : #include <iosfwd>
24 : #include <string>
25 : #include <string_view>
26 : #include <system_error>
27 :
28 : namespace boost::corosio {
29 :
30 : /** An IP version 6 style address.
31 :
32 : Objects of this type are used to construct,
33 : parse, and manipulate IP version 6 addresses.
34 :
35 : @par BNF
36 : @code
37 : IPv6address = 6( h16 ":" ) ls32
38 : / "::" 5( h16 ":" ) ls32
39 : / [ h16 ] "::" 4( h16 ":" ) ls32
40 : / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
41 : / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
42 : / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
43 : / [ *4( h16 ":" ) h16 ] "::" ls32
44 : / [ *5( h16 ":" ) h16 ] "::" h16
45 : / [ *6( h16 ":" ) h16 ] "::"
46 :
47 : ls32 = ( h16 ":" h16 ) / IPv4address
48 : ; least-significant 32 bits of address
49 :
50 : h16 = 1*4HEXDIG
51 : ; 16 bits of address represented in hexadecimal
52 :
53 : IPv6addrz = IPv6address "%" ZoneID
54 : ; rfc6874: an address qualified by its zone
55 : @endcode
56 :
57 : The zone accepts a strict decimal interface index on every
58 : platform; where the platform names interfaces (POSIX), an
59 : interface name maps through `if_nametoindex`. An unknown name
60 : or malformed index is a parse error, never a silent zone 0.
61 : Formatting always emits the numeric form (`%2`).
62 :
63 : @par Specification
64 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291"
65 : >IP Version 6 Addressing Architecture (rfc4291)</a>
66 : @li <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2"
67 : >3.2.2. Host (rfc3986)</a>
68 :
69 : @see
70 : @ref ipv4_address,
71 : @ref make_ipv6_address.
72 : */
73 : class BOOST_COROSIO_DECL ipv6_address
74 : {
75 : std::array<unsigned char, 16> addr_{};
76 : std::uint32_t scope_id_ = 0;
77 :
78 : public:
79 : /** The number of characters in the longest possible IPv6 string.
80 :
81 : The longest address body is the IPv4-mapped form
82 : `ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255` (45
83 : characters), and a numeric zone suffix adds up to eleven
84 : more (`%4294967295`), for a worst case of 56; the constant
85 : carries a little slack.
86 : */
87 : static constexpr std::size_t max_str_len = 60;
88 :
89 : /** The type used to represent an address as an array of bytes.
90 :
91 : Octets are stored in network byte order.
92 : */
93 : using bytes_type = std::array<unsigned char, 16>;
94 :
95 : /** Default constructor.
96 :
97 : Constructs the unspecified address (::).
98 :
99 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.2"
100 : >2.5.2. The Unspecified Address</a>
101 :
102 : @see
103 : @ref is_unspecified
104 : */
105 HIT 160374 : ipv6_address() = default;
106 :
107 : /** Copy constructor.
108 : */
109 : ipv6_address(ipv6_address const&) = default;
110 :
111 : /** Copy assignment.
112 :
113 : @return A reference to this object.
114 : */
115 : ipv6_address& operator=(ipv6_address const&) = default;
116 :
117 : /** Construct from an array of bytes.
118 :
119 : This function constructs an address
120 : from the array in `bytes`, which is
121 : interpreted in big-endian.
122 :
123 : @param bytes The value to construct from.
124 : @param scope_id The zone the address belongs to, as an
125 : interface index; 0 means unscoped.
126 : */
127 : explicit ipv6_address(
128 : bytes_type const& bytes, std::uint32_t scope_id = 0) noexcept;
129 :
130 : /** Return the zone the address belongs to.
131 :
132 : Link-local addresses (`fe80::/10`) are unique only per
133 : network link, so the address bits alone do not identify a
134 : destination; the zone — an interface index, written with a
135 : `%` suffix in text form — disambiguates. For global
136 : addresses the zone is 0 and has no meaning.
137 :
138 : @return The zone as an interface index; 0 if unscoped.
139 :
140 : @par Specification
141 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4007"
142 : >IPv6 Scoped Address Architecture (rfc4007)</a>
143 : */
144 177 : std::uint32_t scope_id() const noexcept
145 : {
146 177 : return scope_id_;
147 : }
148 :
149 : /** Construct from an IPv4 address.
150 :
151 : This function constructs an IPv6 address
152 : from the IPv4 address `addr`. The resulting
153 : address is an IPv4-Mapped IPv6 Address.
154 :
155 : @param addr The address to construct from.
156 :
157 : @par Specification
158 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2"
159 : >2.5.5.2. IPv4-Mapped IPv6 Address (rfc4291)</a>
160 : */
161 : explicit ipv6_address(ipv4_address const& addr) noexcept;
162 :
163 : /** Construct from a string.
164 :
165 : This function constructs an address from
166 : the string `s`, which must contain a valid
167 : IPv6 address string or else an exception
168 : is thrown.
169 :
170 : @par Exception Safety
171 : Strong guarantee.
172 :
173 : @throws std::system_error `errc::invalid_argument` if the input
174 : failed to parse correctly.
175 :
176 : @note For a non-throwing parse function,
177 : use @ref make_ipv6_address.
178 :
179 : @param s The string to parse.
180 :
181 : @par Specification
182 : @li <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2"
183 : >3.2.2. Host (rfc3986)</a>
184 :
185 : @see
186 : @ref make_ipv6_address.
187 : */
188 : explicit ipv6_address(std::string_view s);
189 :
190 : /** Return the address as bytes, in network byte order.
191 :
192 : The 16 bytes cannot carry the zone: for a scoped address
193 : the result identifies the value only together with
194 : @ref scope_id.
195 :
196 : @return The address as an array of bytes.
197 : */
198 261 : bytes_type to_bytes() const noexcept
199 : {
200 261 : return addr_;
201 : }
202 :
203 : /** Return the address as a string.
204 :
205 : The returned string does not
206 : contain surrounding square brackets.
207 :
208 : @par Example
209 : @par !example to_string
210 :
211 : @return The address as a string.
212 :
213 : @par Specification
214 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.2">
215 : 2.2. Text Representation of Addresses (rfc4291)</a>
216 : */
217 : std::string to_string() const;
218 :
219 : /** Write a string representing the address to a buffer.
220 :
221 : The resulting buffer is not null-terminated.
222 :
223 : @throws std::length_error `dest_size < ipv6_address::max_str_len`
224 :
225 : @param dest The buffer in which to write,
226 : which must have at least `dest_size` space.
227 :
228 : @param dest_size The size of the output buffer.
229 :
230 : @return The formatted string view.
231 : */
232 : std::string_view to_buffer(char* dest, std::size_t dest_size) const;
233 :
234 : /** Return true if the address is unspecified.
235 :
236 : The address 0:0:0:0:0:0:0:0 is called the
237 : unspecified address. It indicates the
238 : absence of an address.
239 :
240 : @return `true` if the address is unspecified.
241 :
242 : @par Specification
243 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.2">
244 : 2.5.2. The Unspecified Address (rfc4291)</a>
245 : */
246 : bool is_unspecified() const noexcept;
247 :
248 : /** Return true if the address is a loopback address.
249 :
250 : The unicast address 0:0:0:0:0:0:0:1 is called
251 : the loopback address. It may be used by a node
252 : to send an IPv6 packet to itself.
253 :
254 : @return `true` if the address is a loopback address.
255 :
256 : @par Specification
257 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.3">
258 : 2.5.3. The Loopback Address (rfc4291)</a>
259 : */
260 : bool is_loopback() const noexcept;
261 :
262 : /** Return true if the address is a mapped IPv4 address.
263 :
264 : This address type is used to represent the
265 : addresses of IPv4 nodes as IPv6 addresses.
266 :
267 : @return `true` if the address is a mapped IPv4 address.
268 :
269 : @par Specification
270 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2">
271 : 2.5.5.2. IPv4-Mapped IPv6 Address (rfc4291)</a>
272 : */
273 : bool is_v4_mapped() const noexcept;
274 :
275 : /** Convert a v4-mapped address to the IPv4 address it maps.
276 :
277 : This is the inverse of the mapping constructor
278 : `ipv6_address(ipv4_address const&)`: it extracts the low
279 : 32 bits of an IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`)
280 : as an `ipv4_address`.
281 :
282 : @throws std::system_error `errc::address_family_not_supported`
283 : if the address is not v4-mapped.
284 :
285 : @return The mapped IPv4 address.
286 :
287 : @par Specification
288 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2">
289 : 2.5.5.2. IPv4-Mapped IPv6 Address (rfc4291)</a>
290 :
291 : @see
292 : @ref is_v4_mapped.
293 : */
294 : ipv4_address to_v4() const;
295 :
296 : /** Return true if the address is a multicast address.
297 :
298 : IPv6 multicast addresses have the prefix ff00::/8.
299 :
300 : @return `true` if the address is a multicast address.
301 :
302 : @par Specification
303 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.7">
304 : 2.7. Multicast Addresses (rfc4291)</a>
305 : */
306 : bool is_multicast() const noexcept;
307 :
308 : /** Return true if two addresses are equal.
309 :
310 : Addresses are equal if they have the same bytes and the
311 : same zone: the same link-local bits on different links are
312 : different destinations.
313 :
314 : @return `true` if the addresses are equal.
315 : */
316 : friend bool
317 50 : operator==(ipv6_address const& a1, ipv6_address const& a2) noexcept
318 : {
319 50 : return a1.addr_ == a2.addr_ && a1.scope_id_ == a2.scope_id_;
320 : }
321 :
322 : /** Order two addresses.
323 :
324 : Establishes a strict total ordering consistent with
325 : `operator==`: addresses are ordered lexicographically by
326 : their bytes in network order, then by zone. This makes
327 : `ipv6_address` usable as a key in ordered containers such
328 : as `std::map` and `std::set`.
329 :
330 : @return The relative order of `a1` and `a2`.
331 : */
332 : friend std::strong_ordering
333 17 : operator<=>(ipv6_address const& a1, ipv6_address const& a2) noexcept
334 : {
335 17 : if (auto c = a1.addr_ <=> a2.addr_; c != 0)
336 6 : return c;
337 11 : return a1.scope_id_ <=> a2.scope_id_;
338 : }
339 :
340 : /** Return an address object that represents the unspecified address.
341 :
342 : The address 0:0:0:0:0:0:0:0 (::) may be used to bind a socket
343 : to all available interfaces.
344 :
345 : @return The unspecified address (::).
346 : */
347 19 : static ipv6_address any() noexcept
348 : {
349 19 : return ipv6_address();
350 : }
351 :
352 : /** Return an address object that represents the loopback address.
353 :
354 : The unicast address 0:0:0:0:0:0:0:1 is called
355 : the loopback address. It may be used by a node
356 : to send an IPv6 packet to itself.
357 :
358 : @par Specification
359 : @li <a href="https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.3">
360 : 2.5.3. The Loopback Address (rfc4291)</a>
361 :
362 : @return The loopback address (::1).
363 : */
364 : static ipv6_address loopback() noexcept;
365 :
366 : /** Format the address to an output stream.
367 :
368 : This function writes the address to an
369 : output stream using standard notation.
370 :
371 : @return The output stream, for chaining.
372 :
373 : @param os The output stream to write to.
374 :
375 : @param addr The address to write.
376 : */
377 : friend BOOST_COROSIO_DECL std::ostream&
378 : operator<<(std::ostream& os, ipv6_address const& addr);
379 :
380 : private:
381 : std::size_t print_impl(char* dest) const noexcept;
382 : };
383 :
384 : /** Create an IPv6 address from a string.
385 :
386 : This function attempts to parse the string
387 : as an IPv6 address and returns an error code
388 : if the string does not contain a valid IPv6 address.
389 :
390 : @par Exception Safety
391 : Throws nothing.
392 :
393 : @param s The string to parse.
394 : @return The error code, empty on success, and the parsed
395 : address — default-constructed on failure.
396 : */
397 : [[nodiscard]] BOOST_COROSIO_DECL capy::io_result<ipv6_address>
398 : make_ipv6_address(std::string_view s) noexcept;
399 :
400 : } // namespace boost::corosio
401 :
402 : namespace std {
403 :
404 : /// Hash support for `boost::corosio::ipv6_address`.
405 : template<>
406 : struct hash<boost::corosio::ipv6_address>
407 : {
408 : /// Return the hash of `addr`.
409 : std::size_t
410 21 : operator()(boost::corosio::ipv6_address const& addr) const noexcept
411 : {
412 21 : auto const bytes = addr.to_bytes();
413 21 : auto const h = hash<std::string_view>()(std::string_view(
414 21 : reinterpret_cast<char const*>(bytes.data()), bytes.size()));
415 : // The zone participates in equality, so it must feed the
416 : // hash; combine so it cannot cancel the byte entropy
417 21 : auto const z = hash<std::uint32_t>()(addr.scope_id());
418 21 : return h ^ (z + 0x9e3779b9u + (h << 6) + (h >> 2));
419 : }
420 : };
421 :
422 : } // namespace std
423 :
424 : #endif
|