net/addr/sock: Add classes for generating sockaddrs

This adds classes to generate IP socket information that can be
consumed by `connect()` and `bind()`.

Change-Id: I84c8fc3794d1113877f0f967291317152a0669d5
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index c541513..f9eff41 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -14,6 +14,7 @@
   'stdplus/hash/tuple.hpp',
   'stdplus/net/addr/ether.hpp',
   'stdplus/net/addr/ip.hpp',
+  'stdplus/net/addr/sock.hpp',
   'stdplus/net/addr/subnet.hpp',
   'stdplus/numeric/endian.hpp',
   'stdplus/numeric/str.hpp',
diff --git a/include/stdplus/net/addr/sock.hpp b/include/stdplus/net/addr/sock.hpp
new file mode 100644
index 0000000..9a0238d
--- /dev/null
+++ b/include/stdplus/net/addr/sock.hpp
@@ -0,0 +1,164 @@
+#pragma once
+#include <netinet/in.h>
+
+#include <stdplus/net/addr/ip.hpp>
+#include <stdplus/numeric/endian.hpp>
+#include <stdplus/variant.hpp>
+
+#include <array>
+#include <cstdint>
+
+namespace stdplus
+{
+
+struct SockAddrBuf : sockaddr
+{
+    static inline constexpr std::size_t maxLen =
+        std::max({sizeof(sockaddr_in), sizeof(sockaddr_in6)}) -
+        sizeof(sockaddr);
+    std::array<std::uint8_t, maxLen> buf;
+    std::uint8_t len;
+    static_assert(maxLen <= std::numeric_limits<decltype(len)>::max());
+};
+
+template <typename T>
+struct IsSockAddr : std::false_type
+{};
+
+template <typename T>
+concept SockAddr = IsSockAddr<T>::value;
+
+struct Sock4Addr
+{
+    In4Addr addr;
+    std::uint16_t port;
+
+    constexpr bool operator==(Sock4Addr rhs) const noexcept
+    {
+        return addr == rhs.addr && port == rhs.port;
+    }
+
+    constexpr sockaddr_in sockaddr() const noexcept
+    {
+        return {.sin_family = AF_INET,
+                .sin_port = stdplus::hton(port),
+                .sin_addr = addr,
+                .sin_zero = {}};
+    }
+    constexpr operator sockaddr_in() const noexcept
+    {
+        return sockaddr();
+    }
+
+    static constexpr std::size_t sockaddrLen() noexcept
+    {
+        return sizeof(sockaddr_in);
+    }
+
+    constexpr SockAddrBuf buf() const noexcept
+    {
+        SockAddrBuf ret;
+        reinterpret_cast<sockaddr_in&>(ret) = sockaddr();
+        ret.len = sockaddrLen();
+        return ret;
+    }
+    constexpr operator SockAddrBuf() const noexcept
+    {
+        return buf();
+    }
+};
+
+template <>
+struct IsSockAddr<Sock4Addr> : std::true_type
+{};
+
+struct Sock6Addr
+{
+    In6Addr addr;
+    std::uint16_t port;
+    std::uint32_t scope;
+
+    constexpr bool operator==(Sock6Addr rhs) const noexcept
+    {
+        return addr == rhs.addr && port == rhs.port && scope == rhs.scope;
+    }
+
+    constexpr sockaddr_in6 sockaddr() const noexcept
+    {
+        return {.sin6_family = AF_INET6,
+                .sin6_port = stdplus::hton(port),
+                .sin6_flowinfo = 0,
+                .sin6_addr = addr,
+                .sin6_scope_id = scope};
+    }
+    constexpr operator sockaddr_in6() const noexcept
+    {
+        return sockaddr();
+    }
+
+    static constexpr std::size_t sockaddrLen() noexcept
+    {
+        return sizeof(sockaddr_in6);
+    }
+
+    constexpr SockAddrBuf buf() const noexcept
+    {
+        SockAddrBuf ret;
+        reinterpret_cast<sockaddr_in6&>(ret) = sockaddr();
+        ret.len = sockaddrLen();
+        return ret;
+    }
+    constexpr operator SockAddrBuf() const noexcept
+    {
+        return buf();
+    }
+};
+
+template <>
+struct IsSockAddr<Sock6Addr> : std::true_type
+{};
+
+namespace detail
+{
+
+using SockAnyAddrV = std::variant<Sock4Addr, Sock6Addr>;
+
+} // namespace detail
+
+struct SockAnyAddr : detail::SockAnyAddrV
+{
+    constexpr SockAnyAddr(auto&&... a) noexcept :
+        detail::SockAnyAddrV(std::forward<decltype(a)>(a)...)
+    {}
+
+    template <SockAddr T>
+    constexpr bool operator==(const T& rhs) const noexcept
+    {
+        return variantEqFuzzy(*this, rhs);
+    }
+
+    constexpr SockAddrBuf sockaddr() const noexcept
+    {
+        return std::visit([](const auto& t) { return t.buf(); }, *this);
+    }
+
+    constexpr std::size_t sockaddrLen() const noexcept
+    {
+        return std::visit([](const auto& t) { return t.sockaddrLen(); }, *this);
+    }
+
+    constexpr SockAddrBuf buf() const noexcept
+    {
+        return sockaddr();
+    }
+    constexpr operator SockAddrBuf() const noexcept
+    {
+        return sockaddr();
+    }
+};
+
+template <>
+struct IsSockAddr<SockAnyAddr> : std::true_type
+{};
+
+} // namespace stdplus
diff --git a/src/meson.build b/src/meson.build
index 8dbd144..90afee1 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -54,6 +54,7 @@
   'hash/tuple.cpp',
   'net/addr/ether.cpp',
   'net/addr/ip.cpp',
+  'net/addr/sock.cpp',
   'net/addr/subnet.cpp',
   'numeric/endian.cpp',
   'numeric/str.cpp',
diff --git a/src/net/addr/sock.cpp b/src/net/addr/sock.cpp
new file mode 100644
index 0000000..9f714cf
--- /dev/null
+++ b/src/net/addr/sock.cpp
@@ -0,0 +1 @@
+#include <stdplus/net/addr/sock.hpp>
diff --git a/test/meson.build b/test/meson.build
index b43cf9b..378e666 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -9,6 +9,7 @@
   'hash/tuple': [stdplus_dep, gtest_main_dep],
   'net/addr/ether': [stdplus_dep, gtest_main_dep],
   'net/addr/ip': [stdplus_dep, gtest_main_dep],
+  'net/addr/sock': [stdplus_dep, gtest_main_dep],
   'net/addr/subnet': [stdplus_dep, gtest_main_dep],
   'numeric/endian': [stdplus_dep, gtest_main_dep],
   'numeric/str': [stdplus_dep, gtest_main_dep],
diff --git a/test/net/addr/sock.cpp b/test/net/addr/sock.cpp
new file mode 100644
index 0000000..4aee5c5
--- /dev/null
+++ b/test/net/addr/sock.cpp
@@ -0,0 +1,54 @@
+#include <stdplus/net/addr/sock.hpp>
+
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+TEST(Sock4Addr, Basic)
+{
+    constexpr Sock4Addr addr1{.addr = In4Addr{255}, .port = 3959};
+    Sock4Addr addr2{.addr = In4Addr{255}, .port = 2000};
+    Sock4Addr addr3{.addr = In4Addr{0}, .port = 3959};
+
+    EXPECT_EQ(addr1, addr1);
+    EXPECT_NE(addr1, addr2);
+    EXPECT_NE(addr3, addr1);
+
+    auto addr = addr1.sockaddr();
+    EXPECT_EQ(AF_INET, addr.sin_family);
+    EXPECT_EQ(In4Addr{255}, addr.sin_addr);
+    EXPECT_EQ(3959, stdplus::ntoh(addr.sin_port));
+    EXPECT_EQ(sizeof(sockaddr_in), addr1.sockaddrLen());
+}
+
+TEST(Sock6Addr, Basic)
+{
+    constexpr Sock6Addr addr1{.addr = In6Addr{255}, .port = 3959, .scope = 0};
+    Sock6Addr addr2{.addr = In6Addr{255}, .port = 2000, .scope = 0};
+    Sock6Addr addr3{.addr = In6Addr{0}, .port = 3959, .scope = 0};
+    Sock6Addr addr4{.addr = In6Addr{255}, .port = 3959, .scope = 1};
+
+    EXPECT_EQ(addr1, addr1);
+    EXPECT_NE(addr1, addr2);
+    EXPECT_NE(addr3, addr1);
+    EXPECT_NE(addr4, addr1);
+
+    auto addr = addr1.sockaddr();
+    EXPECT_EQ(AF_INET6, addr.sin6_family);
+    EXPECT_EQ(In6Addr{255}, addr.sin6_addr);
+    EXPECT_EQ(3959, stdplus::ntoh(addr.sin6_port));
+    EXPECT_EQ(sizeof(sockaddr_in6), addr1.sockaddrLen());
+}
+
+TEST(SockAnyAddr, Basic)
+{
+    constexpr SockAnyAddr addr1(std::in_place_type<Sock4Addr>, In4Addr{255},
+                                3959);
+    EXPECT_EQ(addr1, addr1);
+    auto buf = addr1.buf();
+    EXPECT_EQ(buf.len, sizeof(sockaddr_in));
+    EXPECT_EQ(addr1.sockaddrLen(), sizeof(sockaddr_in));
+}
+
+} // namespace stdplus