100.00% Lines (42/42) 100.00% Functions (16/16)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Steve Gerbino 3   // Copyright (c) 2026 Steve Gerbino
4   // Copyright (c) 2026 Michael Vandeberg 4   // Copyright (c) 2026 Michael Vandeberg
5   // 5   //
6   // Distributed under the Boost Software License, Version 1.0. (See accompanying 6   // Distributed under the Boost Software License, Version 1.0. (See accompanying
7   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8   // 8   //
9   // Official repository: https://github.com/cppalliance/corosio 9   // Official repository: https://github.com/cppalliance/corosio
10   // 10   //
11   11  
12   #ifndef BOOST_COROSIO_IP_ADDRESS_HPP 12   #ifndef BOOST_COROSIO_IP_ADDRESS_HPP
13   #define BOOST_COROSIO_IP_ADDRESS_HPP 13   #define BOOST_COROSIO_IP_ADDRESS_HPP
14   14  
15   #include <boost/corosio/detail/config.hpp> 15   #include <boost/corosio/detail/config.hpp>
16   #include <boost/corosio/detail/except.hpp> 16   #include <boost/corosio/detail/except.hpp>
17   #include <boost/corosio/family.hpp> 17   #include <boost/corosio/family.hpp>
18   #include <boost/corosio/ipv4_address.hpp> 18   #include <boost/corosio/ipv4_address.hpp>
19   #include <boost/corosio/ipv6_address.hpp> 19   #include <boost/corosio/ipv6_address.hpp>
20   20  
21   #include <boost/capy/results.hpp> 21   #include <boost/capy/results.hpp>
22   22  
23   #include <compare> 23   #include <compare>
24   #include <iosfwd> 24   #include <iosfwd>
25   #include <string> 25   #include <string>
26   #include <string_view> 26   #include <string_view>
27   #include <system_error> 27   #include <system_error>
28   28  
29   namespace boost::corosio { 29   namespace boost::corosio {
30   30  
31   /** A version-independent IP address. 31   /** A version-independent IP address.
32   32  
33   This class holds either an IPv4 or an IPv6 address. Code that works with 33   This class holds either an IPv4 or an IPv6 address. Code that works with
34   both families carries one value instead of branching between @ref 34   both families carries one value instead of branching between @ref
35   ipv4_address and @ref ipv6_address. Family-generic queries such as 35   ipv4_address and @ref ipv6_address. Family-generic queries such as
36   @ref is_loopback dispatch to the held address, and @ref to_v4 / 36   @ref is_loopback dispatch to the held address, and @ref to_v4 /
37   @ref to_v6 recover the family-specific form. 37   @ref to_v6 recover the family-specific form.
38   38  
39   A v4-mapped IPv6 address (`::ffff:a.b.c.d`) is an IPv6-family 39   A v4-mapped IPv6 address (`::ffff:a.b.c.d`) is an IPv6-family
40   value: it does not compare equal to the IPv4 address it maps. 40   value: it does not compare equal to the IPv4 address it maps.
41   To compare across the mapping, normalize both sides with 41   To compare across the mapping, normalize both sides with
42   @ref to_v4 first. 42   @ref to_v4 first.
43   43  
44   @par Thread Safety 44   @par Thread Safety
45   Distinct objects: Safe.@n 45   Distinct objects: Safe.@n
46   Shared objects: Safe. 46   Shared objects: Safe.
47   47  
48   @par Example 48   @par Example
49   @code 49   @code
50   auto [ec, addr] = make_ip_address("2001:db8::1"); 50   auto [ec, addr] = make_ip_address("2001:db8::1");
51   if (ec) 51   if (ec)
52   return ec; // not a valid address string 52   return ec; // not a valid address string
53   if (addr.is_loopback()) 53   if (addr.is_loopback())
54   { 54   {
55   // family-generic query, no branching 55   // family-generic query, no branching
56   } 56   }
57   @endcode 57   @endcode
58   58  
59   @see 59   @see
60   @ref ipv4_address, 60   @ref ipv4_address,
61   @ref ipv6_address, 61   @ref ipv6_address,
62   @ref make_ip_address 62   @ref make_ip_address
63   */ 63   */
64   class BOOST_COROSIO_DECL ip_address 64   class BOOST_COROSIO_DECL ip_address
65   { 65   {
66   ipv4_address v4_; 66   ipv4_address v4_;
67   ipv6_address v6_; 67   ipv6_address v6_;
68   corosio::family family_ = corosio::family::v4; 68   corosio::family family_ = corosio::family::v4;
69   69  
70   public: 70   public:
71   /** The number of characters in the longest possible address string. 71   /** The number of characters in the longest possible address string.
72   */ 72   */
73   static constexpr std::size_t max_str_len = ipv6_address::max_str_len; 73   static constexpr std::size_t max_str_len = ipv6_address::max_str_len;
74   74  
75   /** Default constructor. 75   /** Default constructor.
76   76  
77   Constructs the IPv4 unspecified address (0.0.0.0). 77   Constructs the IPv4 unspecified address (0.0.0.0).
78   */ 78   */
HITCBC 79   264618 ip_address() = default; 79   269341 ip_address() = default;
80   80  
81   /** Copy constructor. 81   /** Copy constructor.
82   */ 82   */
83   ip_address(ip_address const&) = default; 83   ip_address(ip_address const&) = default;
84   84  
85   /** Copy assignment. 85   /** Copy assignment.
86   86  
87   @return A reference to this object. 87   @return A reference to this object.
88   */ 88   */
89   ip_address& operator=(ip_address const&) = default; 89   ip_address& operator=(ip_address const&) = default;
90   90  
91   /** Construct from an IPv4 address. 91   /** Construct from an IPv4 address.
92   92  
93   @param addr The address to hold. 93   @param addr The address to hold.
94   */ 94   */
HITCBC 95   34836 ip_address(ipv4_address const& addr) noexcept : v4_(addr) {} 95   35267 ip_address(ipv4_address const& addr) noexcept : v4_(addr) {}
96   96  
97   /** Construct from an IPv6 address. 97   /** Construct from an IPv6 address.
98   98  
99   @param addr The address to hold. 99   @param addr The address to hold.
100   */ 100   */
HITCBC 101   227 ip_address(ipv6_address const& addr) noexcept 101   227 ip_address(ipv6_address const& addr) noexcept
HITCBC 102   454 : v6_(addr) 102   454 : v6_(addr)
HITCBC 103   227 , family_(corosio::family::v6) 103   227 , family_(corosio::family::v6)
104   { 104   {
HITCBC 105   227 } 105   227 }
106   106  
107   /** Return the address family. 107   /** Return the address family.
108   108  
109   The portable spelling of the family; @ref is_v4 and 109   The portable spelling of the family; @ref is_v4 and
110   @ref is_v6 are sugar over it. 110   @ref is_v6 are sugar over it.
111   111  
112   @return The family of the held address. 112   @return The family of the held address.
113   */ 113   */
HITCBC 114   647 corosio::family family() const noexcept 114   647 corosio::family family() const noexcept
115   { 115   {
HITCBC 116   647 return family_; 116   647 return family_;
117   } 117   }
118   118  
119   /** Check if the held address is IPv4. 119   /** Check if the held address is IPv4.
120   120  
121   @return `true` if the address is IPv4, `false` if IPv6. 121   @return `true` if the address is IPv4, `false` if IPv6.
122   */ 122   */
HITCBC 123   29875 bool is_v4() const noexcept 123   30652 bool is_v4() const noexcept
124   { 124   {
HITCBC 125   29875 return family_ == corosio::family::v4; 125   30652 return family_ == corosio::family::v4;
126   } 126   }
127   127  
128   /** Check if the held address is IPv6. 128   /** Check if the held address is IPv6.
129   129  
130   @return `true` if the address is IPv6, `false` if IPv4. 130   @return `true` if the address is IPv6, `false` if IPv4.
131   */ 131   */
HITCBC 132   69 bool is_v6() const noexcept 132   69 bool is_v6() const noexcept
133   { 133   {
HITCBC 134   69 return family_ == corosio::family::v6; 134   69 return family_ == corosio::family::v6;
135   } 135   }
136   136  
137   /** Check if the address is a loopback address. 137   /** Check if the address is a loopback address.
138   138  
139   @return `true` if the held address is a loopback 139   @return `true` if the held address is a loopback
140   address of its family. 140   address of its family.
141   */ 141   */
HITCBC 142   13 bool is_loopback() const noexcept 142   13 bool is_loopback() const noexcept
143   { 143   {
HITCBC 144   13 return is_v4() ? v4_.is_loopback() : v6_.is_loopback(); 144   13 return is_v4() ? v4_.is_loopback() : v6_.is_loopback();
145   } 145   }
146   146  
147   /** Check if the address is unspecified. 147   /** Check if the address is unspecified.
148   148  
149   @return `true` if the held address is the unspecified 149   @return `true` if the held address is the unspecified
150   address of its family. 150   address of its family.
151   */ 151   */
HITCBC 152   6 bool is_unspecified() const noexcept 152   6 bool is_unspecified() const noexcept
153   { 153   {
HITCBC 154   6 return is_v4() ? v4_.is_unspecified() : v6_.is_unspecified(); 154   6 return is_v4() ? v4_.is_unspecified() : v6_.is_unspecified();
155   } 155   }
156   156  
157   /** Check if the address is a multicast address. 157   /** Check if the address is a multicast address.
158   158  
159   @return `true` if the held address is a multicast 159   @return `true` if the held address is a multicast
160   address of its family. 160   address of its family.
161   */ 161   */
HITCBC 162   4 bool is_multicast() const noexcept 162   4 bool is_multicast() const noexcept
163   { 163   {
HITCBC 164   4 return is_v4() ? v4_.is_multicast() : v6_.is_multicast(); 164   4 return is_v4() ? v4_.is_multicast() : v6_.is_multicast();
165   } 165   }
166   166  
167   /** Check if the address is a v4-mapped IPv6 address. 167   /** Check if the address is a v4-mapped IPv6 address.
168   168  
169   @return `true` if the address is IPv6 and is an 169   @return `true` if the address is IPv6 and is an
170   IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`). 170   IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`).
171   171  
172   @see 172   @see
173   @ref to_v4 173   @ref to_v4
174   */ 174   */
HITCBC 175   3 bool is_v4_mapped() const noexcept 175   3 bool is_v4_mapped() const noexcept
176   { 176   {
HITCBC 177   3 return is_v6() && v6_.is_v4_mapped(); 177   3 return is_v6() && v6_.is_v4_mapped();
178   } 178   }
179   179  
180   /** Convert to an IPv4 address. 180   /** Convert to an IPv4 address.
181   181  
182   Returns the held IPv4 address, or the IPv4 address that a 182   Returns the held IPv4 address, or the IPv4 address that a
183   v4-mapped IPv6 address maps. This makes normalize-then-compare 183   v4-mapped IPv6 address maps. This makes normalize-then-compare
184   a single call when matching addresses across the mapping. 184   a single call when matching addresses across the mapping.
185   185  
186   @throws std::system_error `errc::address_family_not_supported` 186   @throws std::system_error `errc::address_family_not_supported`
187   if the address is IPv6 and not v4-mapped. 187   if the address is IPv6 and not v4-mapped.
188   188  
189   @return The IPv4 form of the address. 189   @return The IPv4 form of the address.
190   190  
191   @see 191   @see
192   @ref is_v4, @ref is_v4_mapped 192   @ref is_v4, @ref is_v4_mapped
193   */ 193   */
HITCBC 194   10583 ipv4_address to_v4() const 194   10842 ipv4_address to_v4() const
195   { 195   {
HITCBC 196   10583 return is_v4() ? v4_ : v6_.to_v4(); 196   10842 return is_v4() ? v4_ : v6_.to_v4();
197   } 197   }
198   198  
199   /** Convert to an IPv6 address. 199   /** Convert to an IPv6 address.
200   200  
201   To map an IPv4 address into IPv6, use the 201   To map an IPv4 address into IPv6, use the
202   `ipv6_address(ipv4_address const&)` constructor instead. 202   `ipv6_address(ipv4_address const&)` constructor instead.
203   203  
204   @throws std::system_error `errc::address_family_not_supported` 204   @throws std::system_error `errc::address_family_not_supported`
205   if the address is IPv4. 205   if the address is IPv4.
206   206  
207   @return The held IPv6 address. 207   @return The held IPv6 address.
208   208  
209   @see 209   @see
210   @ref is_v6 210   @ref is_v6
211   */ 211   */
HITCBC 212   89 ipv6_address to_v6() const 212   89 ipv6_address to_v6() const
213   { 213   {
HITCBC 214   89 if (is_v4()) 214   89 if (is_v4())
HITCBC 215   2 detail::throw_system_error( 215   2 detail::throw_system_error(
HITCBC 216   2 std::make_error_code(std::errc::address_family_not_supported), 216   2 std::make_error_code(std::errc::address_family_not_supported),
217   "address is not IPv6"); 217   "address is not IPv6");
HITCBC 218   87 return v6_; 218   87 return v6_;
219   } 219   }
220   220  
221   /** Return the address as a string. 221   /** Return the address as a string.
222   222  
223   IPv4 addresses format in dotted decimal, IPv6 addresses 223   IPv4 addresses format in dotted decimal, IPv6 addresses
224   in standard notation without surrounding brackets. 224   in standard notation without surrounding brackets.
225   225  
226   @return The address as a string. 226   @return The address as a string.
227   */ 227   */
HITCBC 228   13 std::string to_string() const 228   13 std::string to_string() const
229   { 229   {
HITCBC 230   13 return is_v4() ? v4_.to_string() : v6_.to_string(); 230   13 return is_v4() ? v4_.to_string() : v6_.to_string();
231   } 231   }
232   232  
233   /** Write a string representing the address to a buffer. 233   /** Write a string representing the address to a buffer.
234   234  
235   The resulting buffer is not null-terminated. 235   The resulting buffer is not null-terminated.
236   236  
237   @throws std::length_error `dest_size < ip_address::max_str_len` 237   @throws std::length_error `dest_size < ip_address::max_str_len`
238   238  
239   @param dest The buffer in which to write, 239   @param dest The buffer in which to write,
240   which must have at least `dest_size` space. 240   which must have at least `dest_size` space.
241   241  
242   @param dest_size The size of the output buffer. 242   @param dest_size The size of the output buffer.
243   243  
244   @return The formatted string view. 244   @return The formatted string view.
245   */ 245   */
246   std::string_view to_buffer(char* dest, std::size_t dest_size) const; 246   std::string_view to_buffer(char* dest, std::size_t dest_size) const;
247   247  
248   /** Return true if two addresses are equal. 248   /** Return true if two addresses are equal.
249   249  
250   Addresses are equal if they have the same family and the 250   Addresses are equal if they have the same family and the
251   same value. A v4-mapped IPv6 address is not equal to the 251   same value. A v4-mapped IPv6 address is not equal to the
252   IPv4 address it maps; normalize with @ref ip_address::to_v4 252   IPv4 address it maps; normalize with @ref ip_address::to_v4
253   to compare 253   to compare
254   across the mapping. 254   across the mapping.
255   255  
256   @return `true` if the addresses are equal. 256   @return `true` if the addresses are equal.
257   */ 257   */
HITCBC 258   160 friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept 258   160 friend bool operator==(ip_address const& a1, ip_address const& a2) noexcept
259   { 259   {
HITCBC 260   160 if (a1.family_ != a2.family_) 260   160 if (a1.family_ != a2.family_)
HITCBC 261   8 return false; 261   8 return false;
HITCBC 262   152 return a1.is_v4() ? a1.v4_ == a2.v4_ : a1.v6_ == a2.v6_; 262   152 return a1.is_v4() ? a1.v4_ == a2.v4_ : a1.v6_ == a2.v6_;
263   } 263   }
264   264  
265   /** Order two addresses. 265   /** Order two addresses.
266   266  
267   Establishes a strict total ordering consistent with 267   Establishes a strict total ordering consistent with
268   @ref operator==: addresses are ordered first by family 268   @ref operator==: addresses are ordered first by family
269   (IPv4 before IPv6), then by value. This makes `ip_address` 269   (IPv4 before IPv6), then by value. This makes `ip_address`
270   usable as a key in ordered containers such as `std::map` 270   usable as a key in ordered containers such as `std::map`
271   and `std::set`. 271   and `std::set`.
272   272  
273   @return The relative order of `a1` and `a2`. 273   @return The relative order of `a1` and `a2`.
274   */ 274   */
275   friend std::strong_ordering 275   friend std::strong_ordering
HITCBC 276   33 operator<=>(ip_address const& a1, ip_address const& a2) noexcept 276   33 operator<=>(ip_address const& a1, ip_address const& a2) noexcept
277   { 277   {
HITCBC 278   33 if (a1.family_ != a2.family_) 278   33 if (a1.family_ != a2.family_)
HITCBC 279   11 return a1.is_v4() ? std::strong_ordering::less 279   11 return a1.is_v4() ? std::strong_ordering::less
HITCBC 280   11 : std::strong_ordering::greater; 280   11 : std::strong_ordering::greater;
HITCBC 281   22 return a1.is_v4() ? a1.v4_ <=> a2.v4_ : a1.v6_ <=> a2.v6_; 281   22 return a1.is_v4() ? a1.v4_ <=> a2.v4_ : a1.v6_ <=> a2.v6_;
282   } 282   }
283   283  
284   /** Format the address to an output stream. 284   /** Format the address to an output stream.
285   285  
286   @param os The output stream. 286   @param os The output stream.
287   @param addr The address to format. 287   @param addr The address to format.
288   @return The output stream. 288   @return The output stream.
289   */ 289   */
290   friend BOOST_COROSIO_DECL std::ostream& 290   friend BOOST_COROSIO_DECL std::ostream&
291   operator<<(std::ostream& os, ip_address const& addr); 291   operator<<(std::ostream& os, ip_address const& addr);
292   }; 292   };
293   293  
294   /** Create an IP address from a string. 294   /** Create an IP address from a string.
295   295  
296   This function parses `s` as an IPv4 address in dotted decimal form, or 296   This function parses `s` as an IPv4 address in dotted decimal form, or
297   an IPv6 address in hexadecimal notation. An IPv6 address may carry a 297   an IPv6 address in hexadecimal notation. An IPv6 address may carry a
298   `%zone` suffix: a decimal interface index, or an interface name where 298   `%zone` suffix: a decimal interface index, or an interface name where
299   the platform names interfaces. The string must contain the address 299   the platform names interfaces. The string must contain the address
300   alone: port suffixes, surrounding brackets, and host names are not 300   alone: port suffixes, surrounding brackets, and host names are not
301   accepted. 301   accepted.
302   302  
303   @par Exception Safety 303   @par Exception Safety
304   Throws nothing. 304   Throws nothing.
305   305  
306   @param s The string to parse. 306   @param s The string to parse.
307   @return The error code, empty on success, and the parsed 307   @return The error code, empty on success, and the parsed
308   address — default-constructed on failure. 308   address — default-constructed on failure.
309   */ 309   */
310   [[nodiscard]] BOOST_COROSIO_DECL capy::results<ip_address> 310   [[nodiscard]] BOOST_COROSIO_DECL capy::results<ip_address>
311   make_ip_address(std::string_view s) noexcept; 311   make_ip_address(std::string_view s) noexcept;
312   312  
313   } // namespace boost::corosio 313   } // namespace boost::corosio
314   314  
315   namespace std { 315   namespace std {
316   316  
317   /// Hash support for `boost::corosio::ip_address`. 317   /// Hash support for `boost::corosio::ip_address`.
318   template<> 318   template<>
319   struct hash<boost::corosio::ip_address> 319   struct hash<boost::corosio::ip_address>
320   { 320   {
321   /// Return the hash of `addr`. 321   /// Return the hash of `addr`.
322   std::size_t 322   std::size_t
HITCBC 323   25 operator()(boost::corosio::ip_address const& addr) const noexcept 323   25 operator()(boost::corosio::ip_address const& addr) const noexcept
324   { 324   {
325   // Family-guarded dispatch keeps the throwing conversions 325   // Family-guarded dispatch keeps the throwing conversions
326   // unreachable 326   // unreachable
HITCBC 327   25 return addr.is_v4() 327   25 return addr.is_v4()
HITCBC 328   25 ? hash<boost::corosio::ipv4_address>()(addr.to_v4()) 328   25 ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
HITCBC 329   25 : hash<boost::corosio::ipv6_address>()(addr.to_v6()); 329   25 : hash<boost::corosio::ipv6_address>()(addr.to_v6());
330   } 330   }
331   }; 331   };
332   332  
333   } // namespace std 333   } // namespace std
334   334  
335   #endif 335   #endif