diff --git a/lib/swoc/include/swoc/IPAddr.h b/lib/swoc/include/swoc/IPAddr.h index 0d544553904..44198f3df21 100644 --- a/lib/swoc/include/swoc/IPAddr.h +++ b/lib/swoc/include/swoc/IPAddr.h @@ -50,7 +50,7 @@ class IP4Addr { explicit IP4Addr(sockaddr_in const *s); /// Construct from text representation. - /// If the @a text is invalid the result is an invalid instance. + /// If the @a text is invalid the result is @c INADDR_ANY IP4Addr(string_view const &text); /// Self assignment. @@ -83,8 +83,21 @@ class IP4Addr { /// Apply @a mask to address, creating the broadcast address. self_type &operator|=(IPMask const &mask); - /// Write this adddress and @a host_order_port to the sockaddr @a sa. - sockaddr_in *copy_to(sockaddr_in *sa, in_port_t port = 0) const; + /** Update socket address with this address. + * + * @param sa Socket address. + * @return @sa + * + * @a sa is assumed to be large enough to hold an IPv4 address. + */ + sockaddr *copy_to(sockaddr *sa) const; + + /** Update socket address with this address. + * + * @param sin IPv4 socket address. + * @return @sin + */ + sockaddr_in *copy_to(sockaddr_in *sin) const; /// @return The address in network order. in_addr_t network_order() const; @@ -92,11 +105,13 @@ class IP4Addr { /// @return The address in host order. in_addr_t host_order() const; - /** Parse @a text as IPv4 address. - The address resulting from the parse is copied to this object if the conversion is successful, - otherwise this object is invalidated. - - @return @c true on success, @c false otherwise. + /** Parse IPv4 address. + * + * @param text Text to parse. + * + * @return @c true if @a text is a valid IPv4 address, @c false otherwise. + * + * Whitespace is trimmed from @text before parsing. If the parse fails @a this is set to @c INADDR_ANY. */ bool load(string_view const &text); @@ -281,15 +296,42 @@ class IP6Addr { */ constexpr uint8_t operator [] (int idx) const; - /// Write to @c sockaddr using network order and @a host_order_port. - sockaddr *copy_to(sockaddr *sa, in_port_t port = 0) const; + /** Update socket address with this address. + * + * @param sin IPv6 socket address. + * @return @sin + */ + sockaddr_in6 *copy_to(sockaddr_in6 *sin6) const; + + /** Update socket address with this address. + * + * @param sa Socket address. + * @return @sin + * + * @a sa is assumed to be large enough to hold an IPv6 address. + */ + sockaddr *copy_to(sockaddr *sa) const; - /// Copy address to @a addr in network order. - in6_addr ©_to(in6_addr &addr) const; + /// Return the address in host order. + in6_addr host_order() const; + + /** Copy the address in host order. + * + * @param dst Destination for host order address. + * @return @a dst + */ + in6_addr& host_order(in6_addr & dst) const; /// Return the address in network order. in6_addr network_order() const; + /** Copy the address in network order. + * + * @param dst Destination for network order address. + * @return @a dst + */ + in6_addr &network_order(in6_addr &dst) const; + /** Parse a string for an IP address. The address resuling from the parse is copied to this object if the conversion is successful, @@ -403,11 +445,13 @@ class IP6Addr { /// These are in sort of host order - @a _store elements are host order, but the /// MSW and LSW are swapped (big-endian). This makes various bits of the implementation /// easier. Conversion to and from network order is via the @c reorder method. - union { + union Addr { word_store_type _store = {0}; ///< 0 is MSW, 1 is LSW. quad_store_type _quad; ///< By quad. raw_type _raw; ///< By byte. + in6_addr _in6; ///< By networking type (but in host order!) } _addr; + static_assert(sizeof(in6_addr) == sizeof(raw_type)); static constexpr unsigned LSW = 1; ///< Least significant word index. static constexpr unsigned MSW = 0; ///< Most significant word index. @@ -417,7 +461,7 @@ class IP6Addr { static constexpr std::array QUAD_IDX = {3, 2, 1, 0, 7, 6, 5, 4}; /// Index of bytes in @a _addr._raw - /// This converts MSB (0) to LSB (15) indicies to the bytes in the binary format. + /// This converts MSB (0) to LSB (15) indices to the bytes in the binary format. static constexpr std::array RAW_IDX = { 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8 }; /// Convert between network and host order. @@ -516,6 +560,13 @@ class IPAddr { self_type &operator|=(IPMask const &mask); + /** Copy the address to a socket address. + * + * @param sa Destination. + * @return @a sa + */ + sockaddr * copy_to(sockaddr * sa); + /** Parse a string and load the result in @a this. * * @param text Text to parse. @@ -764,6 +815,12 @@ IP4Addr::operator=(in_addr_t ip) -> self_type & { return *this; } +inline sockaddr * +IP4Addr::copy_to(sockaddr *sa) const { + this->copy_to(reinterpret_cast(sa)); + return sa; +} + /// Equality. inline bool operator==(IP4Addr const &lhs, IP4Addr const &rhs) { @@ -909,16 +966,30 @@ inline bool IP6Addr::is_private() const { return (_addr._raw[RAW_IDX[0]]& 0xFE) == 0xFC; // fc00::/7 } +inline in6_addr +IP6Addr::host_order() const { + Addr zret { {_addr._store[LSW], _addr._store[MSW] }}; + return zret._in6; +} + inline in6_addr & -IP6Addr::copy_to(in6_addr &addr) const { - self_type::reorder(addr, _addr._raw); - return addr; +IP6Addr::host_order(in6_addr & dst) const { + Addr * addr = reinterpret_cast(&dst); + addr->_store[0] = _addr._store[LSW]; + addr->_store[1] = _addr._store[MSW]; + return dst; } inline in6_addr IP6Addr::network_order() const { in6_addr zret; - return this->copy_to(zret); + return this->network_order(zret); +} + +inline in6_addr & +IP6Addr::network_order(in6_addr & dst) const { + self_type::reorder(dst, _addr._raw); + return dst; } inline auto @@ -1004,6 +1075,12 @@ operator>=(IP6Addr const &lhs, IP6Addr const &rhs) { return rhs <= lhs; } +inline sockaddr * +IP6Addr::copy_to(sockaddr *sa) const { + this->copy_to(reinterpret_cast(sa)); + return sa; +} + inline IP6Addr & IP6Addr::operator&=(IPMask const &mask) { if (mask._cidr < WORD_WIDTH) { diff --git a/lib/swoc/include/swoc/IPEndpoint.h b/lib/swoc/include/swoc/IPEndpoint.h index a1dafaf72d1..858fdfa9a64 100644 --- a/lib/swoc/include/swoc/IPEndpoint.h +++ b/lib/swoc/include/swoc/IPEndpoint.h @@ -14,15 +14,18 @@ #include "swoc/swoc_version.h" #include "swoc/MemSpan.h" #include "swoc/TextView.h" +#include "swoc/string_view_util.h" namespace swoc { inline namespace SWOC_VERSION_NS { -using ::std::string_view; - class IPAddr; class IP4Addr; class IP6Addr; +class IPSrv; +class IP4Srv; +class IP6Srv; + namespace detail { extern void *const pseudo_nullptr; } @@ -38,6 +41,7 @@ extern void *const pseudo_nullptr; */ union IPEndpoint { using self_type = IPEndpoint; ///< Self reference type. + using string_view = std::string_view; struct sockaddr sa; ///< Generic address. struct sockaddr_in sa4; ///< IPv4 @@ -51,11 +55,20 @@ union IPEndpoint { /// Construct from the @a text representation of an address. explicit IPEndpoint(string_view const &text); - // Construct from @c IPAddr + /// Construct from an address. explicit IPEndpoint(IPAddr const &addr); - // Construct from @c sockaddr - IPEndpoint(sockaddr const *sa); + /// Construct from address and port. + explicit IPEndpoint(IPSrv const& srv); + + /// Construct from generic socket address. + IPEndpoint(sockaddr const *addr); + + /// Construct from @a sockaddr_in + IPEndpoint(sockaddr_in const* sin); + + /// Construct from @a sockaddr_in6 + IPEndpoint(sockaddr_in6 const * sin6); /// Copy assignment. self_type &operator=(self_type const &that); @@ -106,8 +119,31 @@ union IPEndpoint { */ self_type &assign(sockaddr const *addr); - /// Assign from an @a addr and @a host_order_port. - self_type &assign(IPAddr const &addr, in_port_t port = 0); + /** Assign from IPv4 socket address. + * + * @param sin IPv4 socket address. + * @return @a this + */ + self_type &assign(sockaddr_in const * sin); + + /** Assign from IPv6 socket address. + * + * @param sin6 IPv6 socket address. + * @return @a this + */ + self_type &assign(sockaddr_in6 const * sin6); + + /// Assign from IP address. + self_type &assign(IPAddr const &addr); + + /// Assign from IPv4 address. + self_type &assign(IP4Addr const& addr); + + /// Assign from IPv4 address. + self_type &assign(IP6Addr const& addr); + + /// Assign from IP service (address and port). + self_type &assign(IPSrv const& srv); /// Copy to @a sa. const self_type ©_to(sockaddr *addr) const; @@ -130,6 +166,18 @@ union IPEndpoint { /// @return The IP address family. sa_family_t family() const; + /// @return A pointer to a @c sockaddr_in or @c nullptr if not IPv4. + sockaddr_in * ip4(); + + /// @return A pointer to a @c sockaddr_in or @c nullptr if not IPv4. + sockaddr_in const * ip4() const; + + /// @return A pointer to a @c sockaddr_in6 or @c nullptr if not IPv6. + sockaddr_in6 * ip6(); + + /// @return A pointer to a @c sockaddr_in6 or @c nullptr if not IPv6. + sockaddr_in6 const * ip6() const; + /// Set to be the ANY address for family @a family. /// @a family must be @c AF_INET or @c AF_INET6. /// @return This object. @@ -190,8 +238,12 @@ inline IPEndpoint::IPEndpoint(IPAddr const &addr) { this->assign(addr); } -inline IPEndpoint::IPEndpoint(sockaddr const *socketaddr) { - this->assign(socketaddr); +inline IPEndpoint::IPEndpoint(IPSrv const& srv) { + this->assign(srv); +} + +inline IPEndpoint::IPEndpoint(sockaddr const *addr) { + this->assign(addr); } inline IPEndpoint::IPEndpoint(IPEndpoint::self_type const &that) : self_type(&that.sa) {} @@ -218,6 +270,18 @@ IPEndpoint::operator=(self_type const &that) { return *this; } +inline IPEndpoint & +IPEndpoint::assign(sockaddr_in const * sin) { + std::memcpy(&sa4, sin, sizeof(sockaddr_in)); + return *this; +} + +inline IPEndpoint & +IPEndpoint::assign(sockaddr_in6 const * sin6) { + std::memcpy(&sa6, sin6, sizeof(sockaddr_in6)); + return *this; +} + inline IPEndpoint & IPEndpoint::assign(sockaddr const *src) { self_type::assign(&sa, src); @@ -245,6 +309,26 @@ IPEndpoint::family() const { return sa.sa_family; } +inline sockaddr_in * +IPEndpoint::ip4() { + return this->is_ip4() ? &sa4 : nullptr; +} + +inline sockaddr_in const * +IPEndpoint::ip4() const { + return this->is_ip4() ? &sa4 : nullptr; +} + +inline sockaddr_in6 * +IPEndpoint::ip6() { + return this->is_ip6() ? &sa6 : nullptr; +} + +inline sockaddr_in6 const * +IPEndpoint::ip6() const { + return this->is_ip6() ? &sa6 : nullptr; +} + inline in_port_t & IPEndpoint::network_order_port() { return self_type::port(&sa); diff --git a/lib/swoc/include/swoc/IPRange.h b/lib/swoc/include/swoc/IPRange.h index cf7dc25947a..89b69cd718b 100644 --- a/lib/swoc/include/swoc/IPRange.h +++ b/lib/swoc/include/swoc/IPRange.h @@ -6,6 +6,9 @@ #pragma once +#include +#include // for std::monostate + #include #include diff --git a/lib/swoc/include/swoc/bwf_base.h b/lib/swoc/include/swoc/bwf_base.h index b4ee36e560b..df3924066b3 100644 --- a/lib/swoc/include/swoc/bwf_base.h +++ b/lib/swoc/include/swoc/bwf_base.h @@ -1086,11 +1086,14 @@ bwformat(BufferWriter &w, bwf::Spec const &spec, bool f) { template std::string & bwprint_v(std::string &s, TextView fmt, std::tuple const &args) { - auto len = s.size(); // remember initial size - size_t n = FixedBufferWriter(s.data(), s.size()).print_v(fmt, args).extent(); + auto const len = s.size(); // remember initial size + auto printer = [&]() { + return FixedBufferWriter(s.data(), s.capacity()).print_v(fmt, args).extent(); + }; + size_t n = printer(); s.resize(n); // always need to resize - if shorter, must clip pre-existing text. if (n > len) { // dropped data, try again. - FixedBufferWriter(s.data(), s.size()).print_v(fmt, args); + printer(); } return s; } @@ -1103,7 +1106,8 @@ bwprint_v(std::string &s, TextView fmt, std::tuple const &args) { * @param args Arguments for format string. * @return @a s * - * The output is generated to @a s as is. If @a s does not have sufficient space for the output + * The output is generated to @a s without resizing, completely replacing any existing text. + * If @a s does not have sufficient space for the output * it is resized to be sufficient and the output formatted again. The result is that @a s will * contain exactly the formatted output. * @@ -1116,6 +1120,36 @@ bwprint(std::string &s, TextView fmt, Args &&... args) { return bwprint_v(s, fmt, std::forward_as_tuple(args...)); } +/** Generate formatted output to a @c std::string @a s using format @a fmt with arguments @a args. + * + * @tparam Args Format argument types. + * @param s Output string. + * @param fmt Format string. + * @param args Arguments for format string. + * @return @a s + * + * The output is appended to @a s without resizing. If @a s does not have sufficient space for the output + * it is resized to be sufficient and the output formatted again. The result is that @a s will + * contain any previous text and the formatted output. + */ +template +std::string & +bwappend(std::string &s, TextView fmt, Args &&... args) { + auto const len = s.length(); // Text to preserve. + auto const capacity = s.capacity(); // Working space. + auto printer = [&]() { + return FixedBufferWriter(s.data()+len, s.capacity()-len).print(fmt, args...).extent(); + }; + // Resize first, otherwise capacity past @a len is cleared on @c resize. + s.resize(capacity); + auto n = printer() + len; // Get the final length. + s.resize(n); // Adjust to correct string length. + if (n > capacity) { // dropped data, write it again. + printer(); + } + return s; +} + /// @cond COVARY template auto diff --git a/lib/swoc/src/swoc_ip.cc b/lib/swoc/src/swoc_ip.cc index 9434221cd56..c9a5564d2a3 100644 --- a/lib/swoc/src/swoc_ip.cc +++ b/lib/swoc/src/swoc_ip.cc @@ -66,26 +66,66 @@ IPEndpoint::assign(sockaddr *dst, sockaddr const *src) { } IPEndpoint & -IPEndpoint::assign(IPAddr const &src, in_port_t port) { +IPEndpoint::assign(IP4Addr const &addr) { + memset(&sa4, 0, sizeof sa4); + sa4.sin_family = AF_INET; + sa4.sin_addr.s_addr = addr.network_order(); + Set_Sockaddr_Len(&sa4); + return *this; +} + +IPEndpoint & +IPEndpoint::assign(IP6Addr const &addr) { + memset(&sa6, 0, sizeof sa6); + sa6.sin6_family = AF_INET6; + addr.network_order(sa6.sin6_addr); + Set_Sockaddr_Len(&sa6); + return *this; +} + +IPEndpoint & +IPEndpoint::assign(IPAddr const &src) { switch (src.family()) { case AF_INET: { memset(&sa4, 0, sizeof sa4); sa4.sin_family = AF_INET; sa4.sin_addr.s_addr = src.ip4().network_order(); - sa4.sin_port = port; Set_Sockaddr_Len(&sa4); } break; case AF_INET6: { memset(&sa6, 0, sizeof sa6); sa6.sin6_family = AF_INET6; sa6.sin6_addr = src.ip6().network_order(); - sa6.sin6_port = port; Set_Sockaddr_Len(&sa6); } break; } return *this; } +IPEndpoint & +IPEndpoint::assign(IPSrv const &src) { + switch (src.family()) { + case AF_INET: + memset(&sa4, 0, sizeof sa4); + sa4.sin_family = AF_INET; + sa4.sin_addr.s_addr = src.ip4().addr().network_order(); + sa4.sin_port = src.network_order_port(); + Set_Sockaddr_Len(&sa4); + break; + case AF_INET6: + memset(&sa6, 0, sizeof sa6); + sa6.sin6_family = AF_INET6; + sa6.sin6_addr = src.ip6().addr().network_order(); + sa6.sin6_port = src.network_order_port(); + Set_Sockaddr_Len(&sa6); + break; + default: + memset(&sa, 0, sizeof sa); + sa.sa_family = AF_UNSPEC; + } + return *this; +} + bool IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_view *port, std::string_view *rest) { TextView src(str); /// Easier to work with for parsing. @@ -146,24 +186,9 @@ IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_v bool IPEndpoint::parse(std::string_view const &str) { - TextView addr_str, port_str, rest; - TextView src{TextView{str}.trim_if(&isspace)}; - - if (this->tokenize(src, &addr_str, &port_str, &rest)) { - if (rest.empty()) { - if (IPAddr addr; addr.load(addr_str)) { - in_port_t port = 0; - if (!port_str.empty()) { - uintmax_t n{swoc::svto_radix<10>(port_str)}; - if (!port_str.empty() || n > std::numeric_limits::max()) { - return false; - } - port = n; - } - this->assign(addr, htons(port)); - return true; - } - } + if (IPSrv srv; srv.load(TextView(str).trim_if(&isspace))) { + this->assign(srv); + return true; } return false; } @@ -261,6 +286,7 @@ IP4Addr::load(std::string_view const &text) { _addr = INADDR_ANY; // clear to zero. // empty or trailing dot or empty brackets or unmatched brackets. + src.trim_if(&isspace); if (src.empty() || src.back() == '.' || ('[' == *src && ((++src).empty() || src.back() != ']'))) { return false; } @@ -305,20 +331,21 @@ IP4Addr::operator=(sockaddr_in const *sa) -> self_type & { } sockaddr_in * -IP4Addr::copy_to(sockaddr_in *sa, in_port_t port) const { - sa->sin_addr.s_addr = this->network_order(); - sa->sin_port = port; - return sa; +IP4Addr::copy_to(sockaddr_in *sin) const { + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = this->network_order(); + Set_Sockaddr_Len(sin); + return sin; } // --- IPv6 -sockaddr * -IP6Addr::copy_to(sockaddr *sa, in_port_t port) const { - IPEndpoint addr(sa); - self_type::reorder(addr.sa6.sin6_addr, _addr._raw); - addr.network_order_port() = port; - return sa; +sockaddr_in6 * +IP6Addr::copy_to(sockaddr_in6 *sin6) const { + sin6->sin6_family = AF_INET6; + self_type::reorder(sin6->sin6_addr, _addr._raw); + Set_Sockaddr_Len(sin6); + return sin6; } int @@ -377,6 +404,7 @@ IP6Addr::load(std::string_view const &str) { int empty_idx = -1; auto quad = _addr._quad.data(); + src.trim_if(&isspace); if (src && '[' == *src) { ++src; if (src.empty() || src.back() != ']') { @@ -474,6 +502,16 @@ IPAddr::operator=(IPEndpoint const &addr) { return this->assign(&addr.sa); } +sockaddr * +IPAddr::copy_to(sockaddr *sa) { + if (this->is_ip4()) { + _addr._ip4.copy_to(sa); + } else if (this->is_ip6()) { + _addr._ip6.copy_to(sa); + } + return sa; +} + bool IPAddr::load(const std::string_view &text) { TextView src{text};