Commit 660cc9bd authored by Dominik Charousset's avatar Dominik Charousset

Add overloads to detail::parse for all IP types

parent 5ebeb85c
......@@ -82,6 +82,18 @@ void parse(parse_state& ps, timespan& x);
void parse(parse_state& ps, atom_value& x);
void parse(parse_state& ps, ipv4_address& x);
void parse(parse_state& ps, ipv4_subnet& x);
void parse(parse_state& ps, ipv4_endpoint& x);
void parse(parse_state& ps, ipv6_address& x);
void parse(parse_state& ps, ipv6_subnet& x);
void parse(parse_state& ps, ipv6_endpoint& x);
void parse(parse_state& ps, uri& x);
// -- STL types ----------------------------------------------------------------
......
......@@ -41,10 +41,10 @@ namespace parser {
struct read_ipv4_octet_consumer {
std::array<uint8_t, 4> bytes;
int octets = 0;
size_t octets = 0;
void value(uint8_t octet) {
bytes[static_cast<size_t>(octets++)] = octet;
bytes[octets++] = octet;
}
};
......@@ -73,14 +73,15 @@ void read_ipv4_octet(state<Iterator, Sentinel>& ps, Consumer& consumer) {
/// Reads a number, i.e., on success produces either an `int64_t` or a
/// `double`.
template <class Iterator, class Sentinel, class Consumer>
void read_ipv4_address(state<Iterator, Sentinel>& ps, Consumer& consumer) {
void read_ipv4_address(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
read_ipv4_octet_consumer f;
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character) {
ipv4_address result{f.bytes};
consumer.value(result);
consumer.value(std::move(result));
}
});
// clang-format off
start();
state(init) {
fsm_epsilon(read_ipv4_octet(ps, f), rd_dot, decimal_chars)
......@@ -90,12 +91,13 @@ void read_ipv4_address(state<Iterator, Sentinel>& ps, Consumer& consumer) {
}
state(rd_oct) {
fsm_epsilon_if(f.octets < 3, read_ipv4_octet(ps, f), rd_dot, decimal_chars)
fsm_epsilon_if(f.octets == 3, read_ipv4_octet(ps, f), done)
fsm_epsilon_if(f.octets == 3, read_ipv4_octet(ps, f), done, decimal_chars)
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
} // namespace parser
......
......@@ -174,7 +174,7 @@ read_ipv6_address_piece_consumer<F> make_read_ipv6_address_piece_consumer(F f) {
/// Reads a number, i.e., on success produces either an `int64_t` or a
/// `double`.
template <class Iterator, class Sentinel, class Consumer>
void read_ipv6_address(state<Iterator, Sentinel>& ps, Consumer& consumer) {
void read_ipv6_address(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
// IPv6 allows omitting blocks of zeros, splitting the string into a part
// before the zeros (prefix) and a part after the zeros (suffix). For example,
// ff::1 is 00FF0000000000000000000000000001
......@@ -194,7 +194,7 @@ void read_ipv6_address(state<Iterator, Sentinel>& ps, Consumer& consumer) {
for (size_t i = 0; i < ipv6_address::num_bytes; ++i)
bytes[i] = prefix[i] | suffix[i];
ipv6_address result{bytes};
consumer.value(result);
consumer.value(std::move(result));
}
});
// We need to parse 2-byte hexadecimal numbers (x) and also keep track of
......
......@@ -18,6 +18,9 @@
#pragma once
#include <cstdint>
#include <functional>
#include "caf/detail/comparable.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/meta/type_name.hpp"
......
......@@ -18,19 +18,26 @@
#pragma once
#include <cstdint>
#include <functional>
#include "caf/detail/comparable.hpp"
#include "caf/fwd.hpp"
#include "caf/ipv6_address.hpp"
#include "caf/meta/type_name.hpp"
namespace caf {
/// An IP endpoint that contains an ::ipv6_address and a port.
class ipv6_endpoint : detail::comparable<ipv6_endpoint> {
class ipv6_endpoint : detail::comparable<ipv6_endpoint>,
detail::comparable<ipv6_endpoint, ipv4_endpoint> {
public:
// -- constructors -----------------------------------------------------------
ipv6_endpoint(ipv6_address address, uint16_t port);
ipv6_endpoint(ipv4_address address, uint16_t port);
ipv6_endpoint() = default;
ipv6_endpoint(const ipv6_endpoint&) = default;
......@@ -67,6 +74,11 @@ public:
/// value otherwise.
long compare(ipv6_endpoint x) const noexcept;
/// Compares this endpoint to `x`.
/// @returns 0 if `*this == x`, a positive value if `*this > x` and a negative
/// value otherwise.
long compare(ipv4_endpoint x) const noexcept;
template <class Inspector>
friend typename Inspector::result_type inspect(Inspector& f,
ipv6_endpoint& x) {
......
......@@ -19,6 +19,8 @@
#include "caf/ipv6_endpoint.hpp"
#include "caf/detail/fnv_hash.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/ipv4_endpoint.hpp"
namespace caf {
......@@ -27,6 +29,11 @@ ipv6_endpoint::ipv6_endpoint(ipv6_address address, uint16_t port)
// nop
}
ipv6_endpoint::ipv6_endpoint(ipv4_address address, uint16_t port)
: address_(address), port_(port) {
// nop
}
size_t ipv6_endpoint::hash_code() const noexcept {
auto result = detail::fnv_hash(address_.data());
return detail::fnv_hash_append(result, port_);
......@@ -37,6 +44,11 @@ long ipv6_endpoint::compare(ipv6_endpoint x) const noexcept {
return res == 0 ? port_ - x.port() : res;
}
long ipv6_endpoint::compare(ipv4_endpoint x) const noexcept {
ipv6_endpoint y{x.address(), x.port()};
return compare(y);
}
std::string to_string(const ipv6_endpoint& x) {
std::string result;
auto addr = x.address();
......
......@@ -23,11 +23,19 @@
#include "caf/detail/parser/read_atom.hpp"
#include "caf/detail/parser/read_bool.hpp"
#include "caf/detail/parser/read_floating_point.hpp"
#include "caf/detail/parser/read_ipv4_address.hpp"
#include "caf/detail/parser/read_ipv6_address.hpp"
#include "caf/detail/parser/read_signed_integer.hpp"
#include "caf/detail/parser/read_string.hpp"
#include "caf/detail/parser/read_timespan.hpp"
#include "caf/detail/parser/read_unsigned_integer.hpp"
#include "caf/detail/parser/read_uri.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/ipv4_endpoint.hpp"
#include "caf/ipv4_subnet.hpp"
#include "caf/ipv6_address.hpp"
#include "caf/ipv6_endpoint.hpp"
#include "caf/ipv6_subnet.hpp"
#include "caf/uri_builder.hpp"
#define PARSE_IMPL(type, parser_name) \
......@@ -38,6 +46,42 @@
namespace caf {
namespace detail {
struct literal {
string_view str;
};
void parse(parse_state& ps, literal& x) {
CAF_ASSERT(x.str.size() > 0);
if (ps.current() != x.str[0]) {
ps.code = pec::unexpected_character;
return;
}
auto c = ps.next();
for (auto i = x.str.begin() + 1; i != x.str.end(); ++i) {
if (c != *i) {
ps.code = pec::unexpected_character;
return;
}
c = ps.next();
}
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
}
void parse_sequence(parse_state&) {
// End of recursion.
}
template <class T, class... Ts>
void parse_sequence(parse_state& ps, T&& x, Ts&&... xs) {
parse(ps, x);
// TODO: use `if constexpr` when switching to C++17
if (sizeof...(Ts) > 0) {
CAF_ASSERT(ps.code != pec::success);
if (ps.code == pec::trailing_character)
parse_sequence(ps, std::forward<Ts>(xs)...);
}
}
PARSE_IMPL(bool, bool)
PARSE_IMPL(int8_t, signed_integer)
......@@ -83,6 +127,74 @@ void parse(parse_state& ps, uri& x) {
x = builder.make();
}
PARSE_IMPL(ipv4_address, ipv4_address)
void parse(parse_state& ps, ipv4_subnet& x) {
ipv4_address addr;
uint8_t prefix_length;
parse_sequence(ps, addr, literal{"/"}, prefix_length);
if (ps.code <= pec::trailing_character) {
if (prefix_length > 32) {
ps.code = pec::integer_overflow;
return;
}
x = ipv4_subnet{addr, prefix_length};
}
}
void parse(parse_state& ps, ipv4_endpoint& x) {
ipv4_address addr;
uint16_t port;
parse_sequence(ps, addr, literal{":"}, port);
if (ps.code <= pec::trailing_character)
x = ipv4_endpoint{addr, port};
}
PARSE_IMPL(ipv6_address, ipv6_address)
void parse(parse_state& ps, ipv6_subnet& x) {
// TODO: this algorithm is currently not one-pass. The reason we need to
// check whether the input is a valid ipv4_subnet first is that "1.2.3.0" is
// a valid IPv6 address, but "1.2.3.0/16" results in the wrong subnet when
// blindly reading the address as an IPv6 address.
auto nested = ps;
ipv4_subnet v4_subnet;
parse(nested, v4_subnet);
if (nested.code <= pec::trailing_character) {
ps.i = nested.i;
ps.code = nested.code;
ps.line = nested.line;
ps.column = nested.column;
x = ipv6_subnet{v4_subnet};
return;
}
ipv6_address addr;
uint8_t prefix_length;
parse_sequence(ps, addr, literal{"/"}, prefix_length);
if (ps.code <= pec::trailing_character) {
if (prefix_length > 128) {
ps.code = pec::integer_overflow;
return;
}
x = ipv6_subnet{addr, prefix_length};
}
}
void parse(parse_state& ps, ipv6_endpoint& x) {
ipv6_address addr;
uint16_t port;
if (ps.consume('[')) {
parse_sequence(ps, addr, literal{"]:"}, port);
} else {
ipv4_address v4_addr;
parse_sequence(ps, v4_addr, literal{":"}, port);
if (ps.code <= pec::trailing_character)
addr = ipv6_address{v4_addr};
}
if (ps.code <= pec::trailing_character)
x = ipv6_endpoint{addr, port};
}
void parse(parse_state& ps, std::string& x) {
ps.skip_whitespaces();
if (ps.current() == '"') {
......
......@@ -30,6 +30,7 @@
#include "caf/actor_system_config.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/byte.hpp"
#include "caf/detail/parse.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/serializer_impl.hpp"
#include "caf/span.hpp"
......@@ -38,18 +39,11 @@ using namespace caf;
namespace {
ipv4_endpoint operator"" _ep(const char* str, size_t) {
std::array<uint8_t, 4> bytes;
char* next = nullptr;
bytes[0] = static_cast<uint8_t>(strtol(str, &next, 10));
for (size_t n = 1; n < 4; ++n) {
assert(*next == '.');
str = next + 1;
bytes[n] = static_cast<uint8_t>(strtol(str, &next, 10));
}
assert(*next == ':');
auto port = static_cast<uint16_t>(strtol(next + 1, nullptr, 10));
return ipv4_endpoint{ipv4_address{bytes}, port};
ipv4_endpoint operator"" _ep(const char* str, size_t size) {
ipv4_endpoint result;
if (auto err = detail::parse(string_view{str, size}, result))
CAF_FAIL("unable to parse input: " << err);
return result;
}
struct fixture {
......
......@@ -29,6 +29,8 @@
#include "caf/actor_system_config.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/byte.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/ipv4_endpoint.hpp"
#include "caf/ipv6_address.hpp"
#include "caf/serializer_impl.hpp"
#include "caf/span.hpp"
......@@ -37,47 +39,11 @@ using namespace caf;
namespace {
ipv6_endpoint operator"" _ep(const char* str, size_t) {
union {
std::array<uint8_t, 16> xs_bytes;
std::array<uint16_t, 8> xs;
};
union {
std::array<uint8_t, 16> ys_bytes;
std::array<uint16_t, 8> ys;
};
memset(xs_bytes.data(), 0, xs_bytes.size());
memset(ys_bytes.data(), 0, xs_bytes.size());
assert(*str == '[');
++str;
char* next = nullptr;
auto pull = [&] {
auto x = static_cast<uint16_t>(strtol(str, &next, 16));
return detail::to_network_order(x);
};
size_t n = 0;
do {
xs[n++] = pull();
assert(next != nullptr);
str = next + 1;
} while (*str != ':' && *str != ']');
if (*str == ':') {
++str;
do {
ys[0] = pull();
std::rotate(ys.begin(), ys.begin() + 1, ys.end());
assert(next != nullptr);
str = next + 1;
} while (*next != ']');
}
assert(*next == ']');
assert(*str == ':');
++str;
auto port = static_cast<uint16_t>(strtol(str, nullptr, 10));
std::array<uint8_t, 16> bytes;
for (size_t i = 0; i < 16; ++i)
bytes[i] = xs_bytes[i] | ys_bytes[i];
return ipv6_endpoint{ipv6_address{bytes}, port};
ipv6_endpoint operator"" _ep(const char* str, size_t size) {
ipv6_endpoint result;
if (auto err = detail::parse(string_view{str, size}, result))
CAF_FAIL("unable to parse input: " << err);
return result;
}
struct fixture {
......@@ -138,6 +104,12 @@ CAF_TEST(constructing assigning and hash_code) {
CAF_CHECK_EQUAL(ep1.hash_code(), ep2.hash_code());
}
CAF_TEST(comparison to IPv4) {
ipv4_endpoint v4{ipv4_address({127, 0, 0, 1}), 8080};
ipv6_endpoint v6{v4.address(), v4.port()};
CAF_CHECK_EQUAL(v4, v6);
}
CAF_TEST(to_string) {
CHECK_TO_STRING("[::1]:8888");
CHECK_TO_STRING("[4e::d00:0:ed00:0:1]:1234");
......
......@@ -23,6 +23,12 @@
#include "caf/test/dsl.hpp"
#include "caf/expected.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/ipv4_endpoint.hpp"
#include "caf/ipv4_subnet.hpp"
#include "caf/ipv6_address.hpp"
#include "caf/ipv6_endpoint.hpp"
#include "caf/ipv6_subnet.hpp"
#include "caf/string_view.hpp"
#include "caf/uri.hpp"
......@@ -216,3 +222,51 @@ CAF_TEST(uris) {
CAF_CHECK_EQUAL(ls[1].authority().host, std::string{"actor-framework.org"});
CAF_CHECK_EQUAL(ls[1].path(), "doc");
}
CAF_TEST(IPv4 address) {
CAF_CHECK_EQUAL(read<ipv4_address>("1.2.3.4"), ipv4_address({1, 2, 3, 4}));
CAF_CHECK_EQUAL(read<ipv4_address>("127.0.0.1"),
ipv4_address({127, 0, 0, 1}));
CAF_CHECK_EQUAL(read<ipv4_address>("256.0.0.1"), pec::integer_overflow);
}
CAF_TEST(IPv4 subnet) {
CAF_CHECK_EQUAL(read<ipv4_subnet>("1.2.3.0/24"),
ipv4_subnet(ipv4_address({1, 2, 3, 0}), 24));
CAF_CHECK_EQUAL(read<ipv4_subnet>("1.2.3.0/33"), pec::integer_overflow);
}
CAF_TEST(IPv4 endpoint) {
CAF_CHECK_EQUAL(read<ipv4_endpoint>("127.0.0.1:0"),
ipv4_endpoint(ipv4_address({127, 0, 0, 1}), 0));
CAF_CHECK_EQUAL(read<ipv4_endpoint>("127.0.0.1:65535"),
ipv4_endpoint(ipv4_address({127, 0, 0, 1}), 65535));
CAF_CHECK_EQUAL(read<ipv4_endpoint>("127.0.0.1:65536"),
pec::integer_overflow);
}
CAF_TEST(IPv6 address) {
CAF_CHECK_EQUAL(read<ipv6_address>("1.2.3.4"), ipv4_address({1, 2, 3, 4}));
CAF_CHECK_EQUAL(read<ipv6_address>("1::"), ipv6_address({{1}, {}}));
CAF_CHECK_EQUAL(read<ipv6_address>("::2"), ipv6_address({{}, {2}}));
CAF_CHECK_EQUAL(read<ipv6_address>("1::2"), ipv6_address({{1}, {2}}));
}
CAF_TEST(IPv6 subnet) {
CAF_CHECK_EQUAL(read<ipv6_subnet>("1.2.3.0/24"),
ipv6_subnet(ipv4_address({1, 2, 3, 0}), 24));
CAF_CHECK_EQUAL(read<ipv6_subnet>("1::/128"),
ipv6_subnet(ipv6_address({1}, {}), 128));
CAF_CHECK_EQUAL(read<ipv6_subnet>("1::/129"), pec::integer_overflow);
}
CAF_TEST(IPv6 endpoint) {
CAF_CHECK_EQUAL(read<ipv6_endpoint>("127.0.0.1:0"),
ipv6_endpoint(ipv4_address({127, 0, 0, 1}), 0));
CAF_CHECK_EQUAL(read<ipv6_endpoint>("127.0.0.1:65535"),
ipv6_endpoint(ipv4_address({127, 0, 0, 1}), 65535));
CAF_CHECK_EQUAL(read<ipv6_endpoint>("127.0.0.1:65536"),
pec::integer_overflow);
CAF_CHECK_EQUAL(read<ipv6_endpoint>("[1::2]:8080"),
ipv6_endpoint({{1}, {2}}, 8080));
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment