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