#include "common/endpoint.h" #include #include #include #include namespace ovg { std::optional IpAddress::parse(const std::string &text) { IpAddress out; uint8_t buf[16]; if (text.find(':') == std::string::npos) { if (inet_pton(AF_INET, text.c_str(), buf) == 1) { std::memcpy(out.bytes_.data(), buf, 4); out.v4_ = true; out.valid_ = true; return out; } return std::nullopt; } if (inet_pton(AF_INET6, text.c_str(), buf) == 1) { std::memcpy(out.bytes_.data(), buf, 16); out.v4_ = false; out.valid_ = true; return out; } return std::nullopt; } IpAddress IpAddress::from_v4(uint32_t host_order) { IpAddress a; a.bytes_[0] = static_cast((host_order >> 24) & 0xff); a.bytes_[1] = static_cast((host_order >> 16) & 0xff); a.bytes_[2] = static_cast((host_order >> 8) & 0xff); a.bytes_[3] = static_cast(host_order & 0xff); a.v4_ = true; a.valid_ = true; return a; } IpAddress IpAddress::from_bytes_v4(const uint8_t bytes[4]) { IpAddress a; std::memcpy(a.bytes_.data(), bytes, 4); a.v4_ = true; a.valid_ = true; return a; } IpAddress IpAddress::from_bytes_v6(const uint8_t bytes[16]) { IpAddress a; std::memcpy(a.bytes_.data(), bytes, 16); a.v4_ = false; a.valid_ = true; return a; } uint32_t IpAddress::v4_host_order() const { return (static_cast(bytes_[0]) << 24) | (static_cast(bytes_[1]) << 16) | (static_cast(bytes_[2]) << 8) | static_cast(bytes_[3]); } std::string IpAddress::to_string() const { if (!valid_) return ""; char buf[INET6_ADDRSTRLEN] = {}; if (v4_) { inet_ntop(AF_INET, bytes_.data(), buf, sizeof(buf)); } else { inet_ntop(AF_INET6, bytes_.data(), buf, sizeof(buf)); } return buf; } Endpoint::Endpoint(IpAddress addr, uint16_t port) : kind_(addr.is_v4() ? Kind::Ipv4 : Kind::Ipv6), addr_(std::move(addr)), port_(port) {} Endpoint::Endpoint(std::string domain, uint16_t port) : kind_(Kind::Domain), domain_(std::move(domain)), port_(port) {} std::optional Endpoint::parse(const std::string &text) { if (text.empty()) return std::nullopt; std::string host; std::string port_str; if (text.front() == '[') { // Bracketed IPv6 literal: [::1]:80 const auto close = text.find(']'); if (close == std::string::npos) return std::nullopt; host = text.substr(1, close - 1); if (close + 1 >= text.size() || text[close + 1] != ':') return std::nullopt; port_str = text.substr(close + 2); } else { const auto colon = text.rfind(':'); if (colon == std::string::npos) return std::nullopt; // A bare IPv6 literal has several colons and no port; reject it as // ambiguous rather than silently truncating the address. if (text.find(':') != colon) return std::nullopt; host = text.substr(0, colon); port_str = text.substr(colon + 1); } if (host.empty() || port_str.empty()) return std::nullopt; unsigned long port_val = 0; const char *begin = port_str.data(); const char *end = begin + port_str.size(); const auto res = std::from_chars(begin, end, port_val); if (res.ec != std::errc{} || res.ptr != end || port_val == 0 || port_val > 65535) return std::nullopt; if (auto ip = IpAddress::parse(host)) return Endpoint(*ip, static_cast(port_val)); return Endpoint(std::move(host), static_cast(port_val)); } std::string Endpoint::host_string() const { return kind_ == Kind::Domain ? domain_ : addr_.to_string(); } std::string Endpoint::to_string() const { const std::string h = host_string(); if (kind_ == Kind::Ipv6) return "[" + h + "]:" + std::to_string(port_); return h + ":" + std::to_string(port_); } size_t EndpointHash::operator()(const Endpoint &e) const noexcept { size_t h = std::hash{}(e.port()); h ^= std::hash{}(static_cast(e.kind())) + 0x9e3779b9 + (h << 6) + (h >> 2); if (e.is_domain()) { h ^= std::hash{}(e.domain()) + 0x9e3779b9 + (h << 6) + (h >> 2); } else { const auto &b = e.address().bytes(); for (size_t i = 0; i < e.address().byte_len(); ++i) h ^= std::hash{}(b[i]) + 0x9e3779b9 + (h << 6) + (h >> 2); } return h; } } // namespace ovg