Files
Towards/modules/network/include/Address.hpp
T
2026-08-05 17:14:04 +02:00

204 lines
6.7 KiB
C++

#pragma once
#include "io/HostResolver.hpp"
#include <arpa/inet.h>
#include <spdlog/spdlog.h>
#include <string>
#include <cstring>
#include <optional>
#include <sys/socket.h>
#include <format>
#include <tl/expected.hpp>
namespace tw::net {
/**
* IP Address
*/
struct Address {
private:
sockaddr_storage m_storage {};
public:
Address(const std::optional<std::string>& address, int port) {
std::memset((char*)&this->m_storage, 0, sizeof(this->m_storage));
auto& addr = reinterpret_cast<sockaddr_in&>(m_storage);
addr.sin_family = AF_INET;
addr.sin_port = htons(static_cast<uint16_t>(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<Address, ResolutionError>
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<const struct sockaddr*>(&m_storage);
}
/** Return a mutable pointer suitable for recvfrom / accept. */
struct sockaddr* sockaddr_mut() {
return reinterpret_cast<struct sockaddr*>(&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<const sockaddr_in&>(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<const sockaddr_in&>(m_storage).sin_port;
case AF_INET6:
return reinterpret_cast<const sockaddr_in6&>(m_storage).sin6_port;
default:
return 0;
}
}
uint16_t port() const {
switch (m_storage.ss_family) {
case AF_INET:
return ntohs(reinterpret_cast<const sockaddr_in&>(m_storage).sin_port);
case AF_INET6:
return ntohs(reinterpret_cast<const sockaddr_in6&>(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<const sockaddr_in&>(m_storage);
inet_ntop(AF_INET, &v4.sin_addr, buf, sizeof(buf));
break;
}
case AF_INET6: {
const auto& v6 = reinterpret_cast<const sockaddr_in6&>(m_storage);
inet_ntop(AF_INET6, &v6.sin6_addr, buf, sizeof(buf));
break;
}
default:
return "<unknown>";
}
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<const sockaddr_in&>(m_storage);
const auto& b = reinterpret_cast<const sockaddr_in&>(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<const sockaddr_in6&>(m_storage);
const auto& b = reinterpret_cast<const sockaddr_in6&>(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<tw::net::Address> {
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<uint16_t>{}(addr.family());
h ^= std::hash<uint16_t>{}(addr.port_raw()) + 0x9e3779b9 + (h << 6) + (h >> 2);
switch (addr.family()) {
case AF_INET:
h ^= std::hash<uint32_t>{}(addr.ipv4_addr_raw()) + 0x9e3779b9 + (h << 6) + (h >> 2);
break;
case AF_INET6: {
const auto& s = reinterpret_cast<const sockaddr_in6&>(addr.storage());
const auto* bytes = reinterpret_cast<const uint8_t*>(&s.sin6_addr);
for (int i = 0; i < 16; ++i) {
h ^= std::hash<uint8_t>{}(bytes[i]) + 0x9e3779b9 + (h << 6) + (h >> 2);
}
break;
}
default:
break;
}
return h;
}
};