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