Skip to content
Snippets Groups Projects
Verified Commit 7f977128 authored by Benny Baumann's avatar Benny Baumann
Browse files

add: Compile-time IP address parsing

parent 0e6994b2
No related branches found
No related tags found
No related merge requests found
#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));
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment