diff --git a/src/net/address.hpp b/src/net/address.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3c7c95af716645cf719b0cfe8626b2f7cad803e0 --- /dev/null +++ b/src/net/address.hpp @@ -0,0 +1,523 @@ +#pragma once + +// Based on https://github.com/gdelugre/literal_ipaddr by Guillaume Delugré + +#include <array> +#include <cstdint> +#include <type_traits> + +#include <arpa/inet.h> + +namespace rmrf::net { + +namespace details { + +template <typename T> +static constexpr T host_to_net(T hostval) +{ + static_assert(std::is_integral<T>::value && (sizeof(T) == 2 || sizeof(T) == 4)); + + if constexpr(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) { + return hostval; + } + + if constexpr(sizeof(T) == 2) { + return __builtin_bswap16(hostval); + } else if constexpr(sizeof(T) == 4) { + return __builtin_bswap32(hostval); + } +} + +template <typename T> +static constexpr T net_to_host(T netval) +{ + return host_to_net(netval); +} + +static constexpr bool isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static constexpr bool isxdigit(char c) +{ + return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +static constexpr char islower(char c) +{ + return (c >= 'a' && c <= 'z'); +} + +static constexpr char toupper(char c) +{ + if (!islower(c)) { + return c; + } + + return c ^ 0x20; +} + +template <size_t N> +static constexpr ssize_t rfind_chr(const char (&str)[N], size_t from, char c) +{ + for (ssize_t i = from; i >= 0; i--) { + if (str[i] == c) { + return i; + } + } + + return -1; +} + +template <size_t N> +static constexpr ssize_t find_chr(const char (&str)[N], size_t from, char c) +{ + for (size_t i = from; i < N; i++) { + if (str[i] == c) { + return i; + } + } + + return -1; +} + +template <int base> +static constexpr bool is_valid_digit(char c) +{ + static_assert(base == 8 || base == 10 || base == 16, "Invalid base parameter"); + + if constexpr(base == 8) { + return (c >= '0' && c <= '7'); + } else if constexpr(base == 10) { + return isdigit(c); + } else if constexpr(base == 16) { + return isxdigit(c); + } +} + +template <int base> +static constexpr int convert_digit(char c) +{ + static_assert(base == 8 || base == 10 || base == 16, "Invalid base parameter"); + + if (!is_valid_digit<base>(c)) { + return -1; + } + + if constexpr(base == 8 || base == 10) { + return c - '0'; + } else if constexpr(base == 16) { + if (isdigit(c)) { + return convert_digit<10>(c); + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else { + return c - 'a' + 10; + } + } +} + +template <int base, char sep, unsigned max_value, size_t max_length = 0, size_t N> +static constexpr long long parse_address_component(const char (&str)[N], size_t idx) +{ + long long res = 0; + + if (N - 1 - idx <= 0 || str[idx] == sep) { + return -1; + } + + for (size_t i = idx; i < N - 1 && str[i] != sep; i++) { + if (max_length > 0 && (i - idx + 1) > max_length) { + return -1; + } + + if (!is_valid_digit<base>(str[i])) { + return -1; + } + + res *= base; + res += convert_digit<base>(str[i]); + + if (res > max_value) { + return -1; + } + } + + return res; +} + +template <int base, unsigned max_value, size_t N> +static constexpr int parse_inet_component_base(const char (&str)[N], size_t idx) +{ + return parse_address_component<base, '.', max_value>(str, idx); +} + +template <unsigned max_value, size_t N> +static constexpr int parse_inet_component_oct(const char (&str)[N], size_t idx) +{ + return parse_inet_component_base<8, max_value>(str, idx); +} + +template <unsigned max_value, size_t N> +static constexpr int parse_inet_component_dec(const char (&str)[N], size_t idx) +{ + return parse_inet_component_base<10, max_value>(str, idx); +} + +template <unsigned max_value, size_t N> +static constexpr int parse_inet_component_hex(const char (&str)[N], size_t idx) +{ + return parse_inet_component_base<16, max_value>(str, idx); +} + +// +// Parse a component of an IPv4 address. +// +template <unsigned max_value = 255, size_t N> +static constexpr int parse_inet_component(const char (&str)[N], size_t idx) +{ + if ((N - idx) > 2 && str[idx] == '0' && (toupper(str[idx + 1]) == 'X')) { + return parse_inet_component_hex<max_value>(str, idx + 2); + } else if ((N - idx) > 2 && str[idx] == '0' && isdigit(str[idx + 1]) && str[idx + 1] != '0') { + return parse_inet_component_oct<max_value>(str, idx + 1); + } else { + return parse_inet_component_dec<max_value>(str, idx); + } +} + +// +// Parse a component of an IPv4 address in its canonical form. +// Leading zeros are not allowed, and component must be expressed in decimal form. +// +template <size_t N> +static constexpr int parse_inet_component_canonical(const char (&str)[N], size_t idx) +{ + if ((N - idx) > 2 && str[idx] == '0' && isdigit(str[idx + 1])) { + return -1; + } + + return parse_address_component<10, '.', 255, 3>(str, idx); +} + +// +// Parse a component of an IPv6 address. +// +template <size_t N> +static constexpr int parse_inet6_hexlet(const char (&str)[N], size_t idx) +{ + return parse_address_component<16, ':', 0xFFFF, 4>(str, idx); +} + +template <size_t N> +static constexpr int inet_addr_canonical_at(const char (&str)[N], ssize_t idx, in_addr_t &s_addr) +{ + // Split and parse each component according to POSIX rules. + ssize_t sep3 = rfind_chr(str, N - 1, '.'); + ssize_t sep2 = rfind_chr(str, sep3 - 1, '.'); + ssize_t sep1 = rfind_chr(str, sep2 - 1, '.'); + + if (sep3 < idx + 1 || sep2 < idx + 1 || sep1 < idx + 1 || rfind_chr(str, sep1 - 1, '.') >= idx) { + return -1; + } + + long long c1 = parse_inet_component_canonical(str, idx); + long long c2 = parse_inet_component_canonical(str, sep1 + 1); + long long c3 = parse_inet_component_canonical(str, sep2 + 1); + long long c4 = parse_inet_component_canonical(str, sep3 + 1); + + if (c1 < 0 || c1 < 0 || c2 < 0 || c3 < 0) { + return -1; + } + + s_addr = host_to_net(static_cast<uint32_t>((c1 << 24) | (c2 << 16) | (c3 << 8) | c4)); + return 0; +} + +template <size_t N> +static constexpr int inet_addr_canonical(const char (&str)[N], in_addr_t &s_addr) +{ + return inet_addr_canonical_at(str, 0, s_addr); +} + +// +// Parse an IPv4 address. +// We split the address into its different components and parse them separately. +// Supported syntax: a, a.b, a.b.c, a.b.c.d +// +// Individual components can be expressed in decimal, octal and hexadecimal. +// +template <size_t N> +static constexpr int inet_addr_impl(const char (&str)[N], in_addr_t &s_addr) +{ + long long c1 = 0, c2 = 0, c3 = 0, c4 = 0; + + // The address string cannot be empty or start/end with a separator. + if (N == 0 || str[0] == '.' || str[N - 1] == '.') { + return -1; + } + + // Split and parse each component according to standard rules. + ssize_t sep3 = rfind_chr(str, N - 1, '.'); + + if (sep3 > 0) { + c1 = parse_inet_component(str, 0); + c4 = parse_inet_component(str, sep3 + 1); + + ssize_t sep2 = rfind_chr(str, sep3 - 1, '.'); + + if (sep2 > 0) { + ssize_t sep1 = rfind_chr(str, sep2 - 1, '.'); + + if (sep1 > 0) { + // Cannot have more than three separators. + if (rfind_chr(str, sep1 - 1, '.') != -1) { + return -1; + } + + c2 = parse_inet_component(str, sep1 + 1); + c3 = parse_inet_component(str, sep2 + 1); + } else { + c2 = parse_inet_component(str, sep2 + 1); + } + } + } else { + c4 = parse_inet_component<0xFFFFFFFF>(str, 0); + } + + if (c1 < 0 || c2 < 0 || c3 < 0 || c4 < 0) { + return -1; + } + + s_addr = host_to_net(static_cast<uint32_t>((c1 << 24) | (c2 << 16) | (c3 << 8) | c4)); + return 0; +} + +template <size_t N> +static constexpr int inet_aton_impl(const char (&str)[N], struct in_addr &in) +{ + return inet_addr_impl(str, in.s_addr); +} + +template <size_t N> +static constexpr int inet_aton_canonical(const char (&str)[N], struct in_addr &in) +{ + return inet_addr_canonical(str, in.s_addr); +} + +static constexpr void inet6_array_to_saddr(std::array<uint16_t, 8> const &ip6_comps, struct in6_addr &in6) +{ + for (size_t i = 0; i < ip6_comps.size(); i++) { + uint16_t hexlet = ip6_comps[i]; + + in6.s6_addr[i * 2] = hexlet >> 8; + in6.s6_addr[i * 2 + 1] = hexlet & 0xff; + } +} + +template <typename T, size_t N> +static constexpr void rshift_array(std::array<T, N> &a, size_t from, size_t shift) +{ + if (from > N - 1) { + return; + } + + for (ssize_t pos = N - 1; pos >= static_cast<ssize_t>(from + shift); pos--) { + if (pos - shift >= 0) { + a[pos] = a[pos - shift]; + a[pos - shift] = 0; + } else { + a[pos] = 0; + } + } +} + +// +// Parse an IPv6 address. +// Format can be: +// 1. x:x:x:x:x:x:x:x with each component being a 16-bit hexadecimal number +// 2. Contiguous zero components can be compacted as "::", allowed to appear only once in the address. +// 3. First 96 bits in above representation and last 32 bits represented as an IPv4 address. +// +template <size_t N> +static constexpr int inet6_aton(const char (&str)[N], struct in6_addr &in6) +{ + std::array<uint16_t, 8> comps = { 0 }; + int shortener_pos = -1; + size_t idx = 0; + in_addr_t v4_addr = -1; + auto remaining_chars = [](size_t pos) constexpr { return N - 1 - pos; }; + + // The address must contain at least two chars, cannot start/end with a separator alone. + if (N < 3 || (str[0] == ':' && str[1] != ':') || (str[N - 1] == ':' && str[N - 2] != ':')) { + return -1; + } + + for (unsigned i = 0; i < comps.size(); i++) { + // We have reached the end of the string before parsing all the components. + // That is possible only if we have previously encountered a shortener token. + if (idx == N - 1) { + if (shortener_pos == -1) { + return -1; + } else { + rshift_array(comps, shortener_pos, comps.size() - i); + break; + } + } + + // Check if we have an embedded IPv4 address. + if ((i == 6 || (i < 6 && shortener_pos != -1)) && inet_addr_canonical_at(str, idx, v4_addr) != -1) { + v4_addr = net_to_host(v4_addr); + + comps[i++] = (v4_addr >> 16) & 0xffff; + comps[i++] = v4_addr & 0xffff; + + if (shortener_pos != -1) { + rshift_array(comps, shortener_pos, comps.size() - i); + } + + idx = N - 1; + break; + } + + // A shortener token (::) is encountered. + if (remaining_chars(idx) >= 2 && str[idx] == ':' && str[idx + 1] == ':') { + // The address shortener syntax can only appear once. + if (shortener_pos != -1) { + return -1; + } + + // It cannot be followed by another separator token. + if (remaining_chars(idx) >= 3 && str[idx + 2] == ':') { + return -1; + } + + // Save the component position where the token was found. + shortener_pos = i; + + idx += 2; + } else { + int hexlet = parse_inet6_hexlet(str, idx); + + if (hexlet == -1) { + return -1; + } + + comps[i] = hexlet; + + ssize_t next_sep = find_chr(str, idx, ':'); + + if (next_sep == -1) { + idx = N - 1; + } else if (remaining_chars(next_sep) >= 2 && str[next_sep + 1] == ':') { + idx = next_sep; + } else { + idx = next_sep + 1; + } + } + } + + // Once all components have been parsed, we must be pointing at the end of the string. + if (idx != N - 1) { + return -1; + } + + inet6_array_to_saddr(comps, in6); + return 0; +} +} + +template <size_t N> +static constexpr in_addr_t inet_addr(const char (&str)[N]) +{ + in_addr_t addr = -1; + + details::inet_addr_impl(str, addr); + return addr; +} + +template <size_t N> +static constexpr struct in_addr inet_aton(const char (&str)[N]) +{ + struct in_addr in = { 0xFFFFFFFF }; + + details::inet_aton_impl(str, in); + return in; +} + +template <int AddressF, size_t N> +static constexpr auto inet_pton(const char (&str)[N]) +{ + static_assert(AddressF == AF_INET || AddressF == AF_INET6, "Unsupported address family."); + + if constexpr(AddressF == AF_INET) { + struct in_addr in = {}; + details::inet_aton_canonical(str, in); + + return in; + } else { + struct in6_addr in6 = {}; + details::inet6_aton(str, in6); + + return in6; + } +} + +template <size_t N> +static constexpr bool is_valid_ip4addr(const char (&str)[N]) +{ + struct in_addr in = {}; + + return details::inet_aton_impl(str, in) != -1; +} + +template <size_t N> +static constexpr bool is_valid_ip6addr(const char (&str)[N]) +{ + struct in6_addr in6 = {}; + + return details::inet6_aton(str, in6) != -1; +} + +} + +template <typename CharT, CharT... Cs> +static constexpr auto operator"" _ipaddr() +{ + constexpr char str[] = { Cs..., 0 }; + + static_assert(rmrf::net::is_valid_ip4addr(str) || rmrf::net::is_valid_ip6addr(str), "Invalid IP address format."); + + if constexpr(rmrf::net::is_valid_ip4addr(str)) { + return rmrf::net::inet_aton(str); + } else { + return rmrf::net::inet_pton<AF_INET6>(str); + } +} + +template <typename CharT, CharT... Cs> +static constexpr auto operator"" _ip4() +{ + constexpr char str[] = {Cs..., 0}; + + static_assert(rmrf::net::is_valid_ip4addr(str), "Invalid IPv4 address format."); + return rmrf::net::inet_aton(str); +} + +template <typename CharT, CharT... Cs> +static constexpr auto operator"" _ip6() +{ + constexpr char str[] = {Cs..., 0}; + + static_assert(rmrf::net::is_valid_ip6addr(str), "Invalid IPv6 address format."); + return rmrf::net::inet_pton<AF_INET6>(str); +} + +static constexpr uint16_t operator "" _ipport(unsigned long long port) +{ + if (port > 65535) { + return 0; + } + + return rmrf::net::details::host_to_net(static_cast<uint16_t>(port)); +}