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 - ip_address addr("2001:db8::1"); 50 + auto [ec, addr] = make_ip_address("2001:db8::1");
  51 + if (ec)
  52 + return ec; // not a valid address string
51   if (addr.is_loopback()) 53   if (addr.is_loopback())
52   { 54   {
53   // family-generic query, no branching 55   // family-generic query, no branching
54   } 56   }
55   @endcode 57   @endcode
56   58  
57   @see 59   @see
58   @ref ipv4_address, 60   @ref ipv4_address,
59   @ref ipv6_address, 61   @ref ipv6_address,
60   @ref make_ip_address 62   @ref make_ip_address
61   */ 63   */
62   class BOOST_COROSIO_DECL ip_address 64   class BOOST_COROSIO_DECL ip_address
63   { 65   {
64   ipv4_address v4_; 66   ipv4_address v4_;
65   ipv6_address v6_; 67   ipv6_address v6_;
66   corosio::family family_ = corosio::family::v4; 68   corosio::family family_ = corosio::family::v4;
67   69  
68   public: 70   public:
69   /** The number of characters in the longest possible address string. 71   /** The number of characters in the longest possible address string.
70   */ 72   */
71   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;
72   74  
73   /** Default constructor. 75   /** Default constructor.
74   76  
75   Constructs the IPv4 unspecified address (0.0.0.0). 77   Constructs the IPv4 unspecified address (0.0.0.0).
76   */ 78   */
HITCBC 77   262025 ip_address() = default; 79   264426 ip_address() = default;
78   80  
79   /** Copy constructor. 81   /** Copy constructor.
80   */ 82   */
81   ip_address(ip_address const&) = default; 83   ip_address(ip_address const&) = default;
82   84  
83   /** Copy assignment. 85   /** Copy assignment.
84   86  
85   @return A reference to this object. 87   @return A reference to this object.
86   */ 88   */
87   ip_address& operator=(ip_address const&) = default; 89   ip_address& operator=(ip_address const&) = default;
88   90  
89   /** Construct from an IPv4 address. 91   /** Construct from an IPv4 address.
90   92  
91   @param addr The address to hold. 93   @param addr The address to hold.
92   */ 94   */
HITCBC 93   34588 ip_address(ipv4_address const& addr) noexcept : v4_(addr) {} 95   34897 ip_address(ipv4_address const& addr) noexcept : v4_(addr) {}
94   96  
95   /** Construct from an IPv6 address. 97   /** Construct from an IPv6 address.
96   98  
97   @param addr The address to hold. 99   @param addr The address to hold.
98   */ 100   */
HITCBC 99   227 ip_address(ipv6_address const& addr) noexcept 101   227 ip_address(ipv6_address const& addr) noexcept
HITCBC 100   454 : v6_(addr) 102   454 : v6_(addr)
HITCBC 101   227 , family_(corosio::family::v6) 103   227 , family_(corosio::family::v6)
102   { 104   {
HITCBC 103   227 } 105   227 }
104 - /** Construct from a string.  
105 -  
106 - This function constructs an address from the string `s`,  
107 - which must contain a valid IPv4 or IPv6 address string  
108 - or else an exception is thrown.  
109 -  
110 - @par Exception Safety  
111 - Strong guarantee.  
112 -  
113 - @throws std::system_error `errc::invalid_argument` if the input  
114 - failed to parse correctly.  
115 -  
116 - @note For a non-throwing parse function,  
117 - use @ref make_ip_address.  
118 -  
119 - @param s The string to parse.  
120 -  
121 - @see  
122 - @ref make_ip_address  
123 - */  
124 - explicit ip_address(std::string_view s);  
125 -  
126   106  
127   /** Return the address family. 107   /** Return the address family.
128   108  
129   The portable spelling of the family; @ref is_v4 and 109   The portable spelling of the family; @ref is_v4 and
130   @ref is_v6 are sugar over it. 110   @ref is_v6 are sugar over it.
131   111  
132   @return The family of the held address. 112   @return The family of the held address.
133   */ 113   */
HITCBC 134   637 corosio::family family() const noexcept 114   647 corosio::family family() const noexcept
135   { 115   {
HITCBC 136   637 return family_; 116   647 return family_;
137   } 117   }
138   118  
139   /** Check if the held address is IPv4. 119   /** Check if the held address is IPv4.
140   120  
141   @return `true` if the address is IPv4, `false` if IPv6. 121   @return `true` if the address is IPv4, `false` if IPv6.
142   */ 122   */
HITCBC 143   29468 bool is_v4() const noexcept 123   29401 bool is_v4() const noexcept
144   { 124   {
HITCBC 145   29468 return family_ == corosio::family::v4; 125   29401 return family_ == corosio::family::v4;
146   } 126   }
147   127  
148   /** Check if the held address is IPv6. 128   /** Check if the held address is IPv6.
149   129  
150   @return `true` if the address is IPv6, `false` if IPv4. 130   @return `true` if the address is IPv6, `false` if IPv4.
151   */ 131   */
HITCBC 152   69 bool is_v6() const noexcept 132   69 bool is_v6() const noexcept
153   { 133   {
HITCBC 154   69 return family_ == corosio::family::v6; 134   69 return family_ == corosio::family::v6;
155   } 135   }
156   136  
157   /** Check if the address is a loopback address. 137   /** Check if the address is a loopback address.
158   138  
159   @return `true` if the held address is a loopback 139   @return `true` if the held address is a loopback
160   address of its family. 140   address of its family.
161   */ 141   */
HITCBC 162   13 bool is_loopback() const noexcept 142   13 bool is_loopback() const noexcept
163   { 143   {
HITCBC 164   13 return is_v4() ? v4_.is_loopback() : v6_.is_loopback(); 144   13 return is_v4() ? v4_.is_loopback() : v6_.is_loopback();
165   } 145   }
166   146  
167   /** Check if the address is unspecified. 147   /** Check if the address is unspecified.
168   148  
169   @return `true` if the held address is the unspecified 149   @return `true` if the held address is the unspecified
170   address of its family. 150   address of its family.
171   */ 151   */
HITCBC 172   6 bool is_unspecified() const noexcept 152   6 bool is_unspecified() const noexcept
173   { 153   {
HITCBC 174   6 return is_v4() ? v4_.is_unspecified() : v6_.is_unspecified(); 154   6 return is_v4() ? v4_.is_unspecified() : v6_.is_unspecified();
175   } 155   }
176   156  
177   /** Check if the address is a multicast address. 157   /** Check if the address is a multicast address.
178   158  
179   @return `true` if the held address is a multicast 159   @return `true` if the held address is a multicast
180   address of its family. 160   address of its family.
181   */ 161   */
HITCBC 182   4 bool is_multicast() const noexcept 162   4 bool is_multicast() const noexcept
183   { 163   {
HITCBC 184   4 return is_v4() ? v4_.is_multicast() : v6_.is_multicast(); 164   4 return is_v4() ? v4_.is_multicast() : v6_.is_multicast();
185   } 165   }
186   166  
187   /** Check if the address is a v4-mapped IPv6 address. 167   /** Check if the address is a v4-mapped IPv6 address.
188   168  
189   @return `true` if the address is IPv6 and is an 169   @return `true` if the address is IPv6 and is an
190   IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`). 170   IPv4-Mapped IPv6 Address (`::ffff:a.b.c.d`).
191   171  
192   @see 172   @see
193   @ref to_v4 173   @ref to_v4
194   */ 174   */
HITCBC 195   3 bool is_v4_mapped() const noexcept 175   3 bool is_v4_mapped() const noexcept
196   { 176   {
HITCBC 197   3 return is_v6() && v6_.is_v4_mapped(); 177   3 return is_v6() && v6_.is_v4_mapped();
198   } 178   }
199   179  
200   /** Convert to an IPv4 address. 180   /** Convert to an IPv4 address.
201   181  
202   Returns the held IPv4 address, or the IPv4 address that a 182   Returns the held IPv4 address, or the IPv4 address that a
203   v4-mapped IPv6 address maps. This makes normalize-then-compare 183   v4-mapped IPv6 address maps. This makes normalize-then-compare
204   a single call when matching addresses across the mapping. 184   a single call when matching addresses across the mapping.
205   185  
206   @throws std::system_error `errc::address_family_not_supported` 186   @throws std::system_error `errc::address_family_not_supported`
207   if the address is IPv6 and not v4-mapped. 187   if the address is IPv6 and not v4-mapped.
208   188  
209   @return The IPv4 form of the address. 189   @return The IPv4 form of the address.
210   190  
211   @see 191   @see
212   @ref is_v4, @ref is_v4_mapped 192   @ref is_v4, @ref is_v4_mapped
213   */ 193   */
HITCBC 214   10441 ipv4_address to_v4() const 194   10425 ipv4_address to_v4() const
215   { 195   {
HITCBC 216   10441 return is_v4() ? v4_ : v6_.to_v4(); 196   10425 return is_v4() ? v4_ : v6_.to_v4();
217   } 197   }
218   198  
219   /** Convert to an IPv6 address. 199   /** Convert to an IPv6 address.
220   200  
221   To map an IPv4 address into IPv6, use the 201   To map an IPv4 address into IPv6, use the
222   `ipv6_address(ipv4_address const&)` constructor instead. 202   `ipv6_address(ipv4_address const&)` constructor instead.
223   203  
224   @throws std::system_error `errc::address_family_not_supported` 204   @throws std::system_error `errc::address_family_not_supported`
225   if the address is IPv4. 205   if the address is IPv4.
226   206  
227   @return The held IPv6 address. 207   @return The held IPv6 address.
228   208  
229   @see 209   @see
230   @ref is_v6 210   @ref is_v6
231   */ 211   */
HITCBC 232   89 ipv6_address to_v6() const 212   89 ipv6_address to_v6() const
233   { 213   {
HITCBC 234   89 if (is_v4()) 214   89 if (is_v4())
HITCBC 235   2 detail::throw_system_error( 215   2 detail::throw_system_error(
HITCBC 236   2 std::make_error_code(std::errc::address_family_not_supported), 216   2 std::make_error_code(std::errc::address_family_not_supported),
237   "address is not IPv6"); 217   "address is not IPv6");
HITCBC 238   87 return v6_; 218   87 return v6_;
239   } 219   }
240   220  
241   /** Return the address as a string. 221   /** Return the address as a string.
242   222  
243   IPv4 addresses format in dotted decimal, IPv6 addresses 223   IPv4 addresses format in dotted decimal, IPv6 addresses
244   in standard notation without surrounding brackets. 224   in standard notation without surrounding brackets.
245   225  
246   @return The address as a string. 226   @return The address as a string.
247   */ 227   */
HITCBC 248   13 std::string to_string() const 228   13 std::string to_string() const
249   { 229   {
HITCBC 250   13 return is_v4() ? v4_.to_string() : v6_.to_string(); 230   13 return is_v4() ? v4_.to_string() : v6_.to_string();
251   } 231   }
252   232  
253   /** Write a string representing the address to a buffer. 233   /** Write a string representing the address to a buffer.
254   234  
255   The resulting buffer is not null-terminated. 235   The resulting buffer is not null-terminated.
256   236  
257   @throws std::length_error `dest_size < ip_address::max_str_len` 237   @throws std::length_error `dest_size < ip_address::max_str_len`
258   238  
259   @param dest The buffer in which to write, 239   @param dest The buffer in which to write,
260   which must have at least `dest_size` space. 240   which must have at least `dest_size` space.
261   241  
262   @param dest_size The size of the output buffer. 242   @param dest_size The size of the output buffer.
263   243  
264   @return The formatted string view. 244   @return The formatted string view.
265   */ 245   */
266   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;
267   247  
268   /** Return true if two addresses are equal. 248   /** Return true if two addresses are equal.
269   249  
270   Addresses are equal if they have the same family and the 250   Addresses are equal if they have the same family and the
271   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
272   IPv4 address it maps; normalize with @ref ip_address::to_v4 252   IPv4 address it maps; normalize with @ref ip_address::to_v4
273   to compare 253   to compare
274   across the mapping. 254   across the mapping.
275   255  
276   @return `true` if the addresses are equal. 256   @return `true` if the addresses are equal.
277   */ 257   */
HITCBC 278   143 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
279   { 259   {
HITCBC 280   143 if (a1.family_ != a2.family_) 260   160 if (a1.family_ != a2.family_)
HITCBC 281   8 return false; 261   8 return false;
HITCBC 282   135 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_;
283   } 263   }
284   264  
285   /** Order two addresses. 265   /** Order two addresses.
286   266  
287   Establishes a strict total ordering consistent with 267   Establishes a strict total ordering consistent with
288   @ref operator==: addresses are ordered first by family 268   @ref operator==: addresses are ordered first by family
289   (IPv4 before IPv6), then by value. This makes `ip_address` 269   (IPv4 before IPv6), then by value. This makes `ip_address`
290   usable as a key in ordered containers such as `std::map` 270   usable as a key in ordered containers such as `std::map`
291   and `std::set`. 271   and `std::set`.
292   272  
293   @return The relative order of `a1` and `a2`. 273   @return The relative order of `a1` and `a2`.
294   */ 274   */
295   friend std::strong_ordering 275   friend std::strong_ordering
HITCBC 296   33 operator<=>(ip_address const& a1, ip_address const& a2) noexcept 276   33 operator<=>(ip_address const& a1, ip_address const& a2) noexcept
297   { 277   {
HITCBC 298   33 if (a1.family_ != a2.family_) 278   33 if (a1.family_ != a2.family_)
HITCBC 299   11 return a1.is_v4() ? std::strong_ordering::less 279   11 return a1.is_v4() ? std::strong_ordering::less
HITCBC 300   11 : std::strong_ordering::greater; 280   11 : std::strong_ordering::greater;
HITCBC 301   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_;
302   } 282   }
303   283  
304   /** Format the address to an output stream. 284   /** Format the address to an output stream.
305   285  
306   @param os The output stream. 286   @param os The output stream.
307   @param addr The address to format. 287   @param addr The address to format.
308   @return The output stream. 288   @return The output stream.
309   */ 289   */
310   friend BOOST_COROSIO_DECL std::ostream& 290   friend BOOST_COROSIO_DECL std::ostream&
311   operator<<(std::ostream& os, ip_address const& addr); 291   operator<<(std::ostream& os, ip_address const& addr);
312   }; 292   };
313   293  
314   /** Create an IP address from a string. 294   /** Create an IP address from a string.
315   295  
316   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
317   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
318   `%zone` suffix: a decimal interface index, or an interface name where 298   `%zone` suffix: a decimal interface index, or an interface name where
319   the platform names interfaces. The string must contain the address 299   the platform names interfaces. The string must contain the address
320   alone: port suffixes, surrounding brackets, and host names are not 300   alone: port suffixes, surrounding brackets, and host names are not
321   accepted. 301   accepted.
322   302  
323   @par Exception Safety 303   @par Exception Safety
324   Throws nothing. 304   Throws nothing.
325   305  
326   @param s The string to parse. 306   @param s The string to parse.
327   @return The error code, empty on success, and the parsed 307   @return The error code, empty on success, and the parsed
328   address — default-constructed on failure. 308   address — default-constructed on failure.
329   */ 309   */
330   [[nodiscard]] BOOST_COROSIO_DECL capy::results<ip_address> 310   [[nodiscard]] BOOST_COROSIO_DECL capy::results<ip_address>
331 -  
332 - inline ip_address::ip_address(std::string_view s)  
DCB 333 - 25 {  
334 - auto [ec, addr] = make_ip_address(s);  
DCB 335 - 25 if (ec)  
DCB 336 - 25 detail::throw_system_error(ec, "invalid IP address");  
DCB 337 - 2 *this = addr;  
DCB 338 - 23 }  
ECB 339   23 make_ip_address(std::string_view s) noexcept; 311   make_ip_address(std::string_view s) noexcept;
340   312  
341   } // namespace boost::corosio 313   } // namespace boost::corosio
342   314  
343   namespace std { 315   namespace std {
344   316  
345   /// Hash support for `boost::corosio::ip_address`. 317   /// Hash support for `boost::corosio::ip_address`.
346   template<> 318   template<>
347   struct hash<boost::corosio::ip_address> 319   struct hash<boost::corosio::ip_address>
348   { 320   {
349   /// Return the hash of `addr`. 321   /// Return the hash of `addr`.
350   std::size_t 322   std::size_t
HITCBC 351   25 operator()(boost::corosio::ip_address const& addr) const noexcept 323   25 operator()(boost::corosio::ip_address const& addr) const noexcept
352   { 324   {
353   // Family-guarded dispatch keeps the throwing conversions 325   // Family-guarded dispatch keeps the throwing conversions
354   // unreachable 326   // unreachable
HITCBC 355   25 return addr.is_v4() 327   25 return addr.is_v4()
HITCBC 356   25 ? hash<boost::corosio::ipv4_address>()(addr.to_v4()) 328   25 ? hash<boost::corosio::ipv4_address>()(addr.to_v4())
HITCBC 357   25 : hash<boost::corosio::ipv6_address>()(addr.to_v6()); 329   25 : hash<boost::corosio::ipv6_address>()(addr.to_v6());
358   } 330   }
359   }; 331   };
360   332  
361   } // namespace std 333   } // namespace std
362   334  
363   #endif 335   #endif