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