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