#pragma once #include "io/HostResolver.hpp" #include #include #include #include #include #include #include #include namespace tw::net { /** * IP Address */ struct Address { private: sockaddr_storage m_storage {}; public: Address(const std::optional& address, int port) { std::memset((char*)&this->m_storage, 0, sizeof(this->m_storage)); auto& addr = reinterpret_cast(m_storage); addr.sin_family = AF_INET; addr.sin_port = htons(static_cast(port)); addr.sin_addr.s_addr = address.has_value() ? inet_addr(address->c_str()) : INADDR_ANY; } Address(sockaddr_storage& storage) : m_storage(storage) { } Address(sockaddr_storage&& storage) : m_storage(storage) { } /** * Look a host up, accepting a name where the constructor above takes only * an address literal. * * `family` should be the family of the socket the address will be used * with. The default suits an address that is only being validated or * displayed, and takes whatever the name resolves to. */ static tl::expected resolve(const std::string& host, int port, sa_family_t family = AF_UNSPEC) { auto storage_r = resolve_host(host, port, family); if(!storage_r) { return tl::make_unexpected(storage_r.error()); } return Address(std::move(storage_r.value())); } /** Return a const pointer suitable for connect / sendto / bind. */ const struct sockaddr* sockaddr() const { return reinterpret_cast(&m_storage); } /** Return a mutable pointer suitable for recvfrom / accept. */ struct sockaddr* sockaddr_mut() { return reinterpret_cast(&m_storage); } /** Return the size of the active address (depends on family). */ socklen_t socklen() const { switch (m_storage.ss_family) { case AF_INET: return sizeof(sockaddr_in); case AF_INET6: return sizeof(sockaddr_in6); default: return sizeof(sockaddr_storage); } } /** Mutable reference to the raw storage — useful when you need to pass * a sockaddr_storage* to recvfrom together with a socklen_t. */ sockaddr_storage& storage() { return m_storage; } const sockaddr_storage& storage() const { return m_storage; } sa_family_t family() const { return m_storage.ss_family; } /** Returns the raw network-order IPv4 address, or 0 if not AF_INET. */ uint32_t ipv4_addr_raw() const { if (m_storage.ss_family == AF_INET) { return reinterpret_cast(m_storage).sin_addr.s_addr; } return 0; } /** Returns the raw network-order port (any family). */ uint16_t port_raw() const { switch (m_storage.ss_family) { case AF_INET: return reinterpret_cast(m_storage).sin_port; case AF_INET6: return reinterpret_cast(m_storage).sin6_port; default: return 0; } } uint16_t port() const { switch (m_storage.ss_family) { case AF_INET: return ntohs(reinterpret_cast(m_storage).sin_port); case AF_INET6: return ntohs(reinterpret_cast(m_storage).sin6_port); default: return 0; } } /** Return the IP portion only (no port). */ std::string ip_string() const { char buf[INET6_ADDRSTRLEN]{}; switch (m_storage.ss_family) { case AF_INET: { const auto& v4 = reinterpret_cast(m_storage); inet_ntop(AF_INET, &v4.sin_addr, buf, sizeof(buf)); break; } case AF_INET6: { const auto& v6 = reinterpret_cast(m_storage); inet_ntop(AF_INET6, &v6.sin6_addr, buf, sizeof(buf)); break; } default: return ""; } return std::string(buf); } /** Human-readable "ip:port" (or "[ip]:port" for IPv6). */ std::string to_string() const { if (m_storage.ss_family == AF_INET6) { return std::format("[{}]:{}", ip_string(), port()); } return std::format("{}:{}", ip_string(), port()); } bool operator==(const Address& other) const { if (m_storage.ss_family != other.m_storage.ss_family) return false; switch (m_storage.ss_family) { case AF_INET: { const auto& a = reinterpret_cast(m_storage); const auto& b = reinterpret_cast(other.m_storage); return a.sin_port == b.sin_port && a.sin_addr.s_addr == b.sin_addr.s_addr; } case AF_INET6: { const auto& a = reinterpret_cast(m_storage); const auto& b = reinterpret_cast(other.m_storage); return a.sin6_port == b.sin6_port && std::memcmp(&a.sin6_addr, &b.sin6_addr, sizeof(in6_addr)) == 0; } default: return std::memcmp(&m_storage, &other.m_storage, sizeof(m_storage)) == 0; } } bool operator!=(const Address& other) const { return !(*this == other); } /** * Retained for source compatibility. Prefer operator==. */ bool equals(const Address& other) const { return *this == other; } }; } template<> struct std::hash { std::size_t operator()(const tw::net::Address& addr) const noexcept { // FNV-style combine of family + port + address bytes std::size_t h = std::hash{}(addr.family()); h ^= std::hash{}(addr.port_raw()) + 0x9e3779b9 + (h << 6) + (h >> 2); switch (addr.family()) { case AF_INET: h ^= std::hash{}(addr.ipv4_addr_raw()) + 0x9e3779b9 + (h << 6) + (h >> 2); break; case AF_INET6: { const auto& s = reinterpret_cast(addr.storage()); const auto* bytes = reinterpret_cast(&s.sin6_addr); for (int i = 0; i < 16; ++i) { h ^= std::hash{}(bytes[i]) + 0x9e3779b9 + (h << 6) + (h >> 2); } break; } default: break; } return h; } };