Commit 73bd12e6 authored by Dominik Charousset's avatar Dominik Charousset

Add parse function for node_id

parent ab7b9114
......@@ -249,6 +249,7 @@ set(CAF_CORE_TEST_SOURCES
test/mixin/sender.cpp
test/mock_streaming_classes.cpp
test/native_streaming_classes.cpp
test/node_id.cpp
test/optional.cpp
test/or_else.cpp
test/pipeline_streaming.cpp
......
......@@ -103,6 +103,8 @@ public:
static bool valid(const host_id_type& x) noexcept;
static bool can_parse(string_view str) noexcept;
// -- interface implementation ---------------------------------------------
bool valid() const noexcept override;
......@@ -230,6 +232,9 @@ public:
/// @endcond
/// Returns whether `parse` would produce a valid node ID.
static bool can_parse(string_view str) noexcept;
/// @relates node_id
friend CAF_CORE_EXPORT error inspect(serializer& sink, node_id& x);
......@@ -327,8 +332,11 @@ CAF_CORE_EXPORT node_id make_node_id(
/// @param process_id System-wide unique process identifier.
/// @param host_hash Unique node ID as hexadecimal string representation.
/// @relates node_id
CAF_CORE_EXPORT optional<node_id>
make_node_id(uint32_t process_id, const std::string& host_hash);
CAF_CORE_EXPORT optional<node_id> make_node_id(uint32_t process_id,
string_view host_hash);
/// @relates node_id
CAF_CORE_EXPORT error parse(string_view str, node_id& dest);
} // namespace caf
......
......@@ -100,6 +100,38 @@ struct parser_state {
}
return false;
}
/// Consumes the next character if it satisfies given predicate (skips any
/// whitespaces).
template<class Predicate>
bool consume_if(Predicate predicate) noexcept {
skip_whitespaces();
if (predicate(current())) {
next();
return true;
}
return false;
}
/// Tries to read `x` as the next character, not allowing any whitespaces.
bool consume_strict(char x) noexcept {
if (current() == x) {
next();
return true;
}
return false;
}
/// Consumes the next character if it satisfies given predicate, not allowing
/// any whitespaces.
template<class Predicate>
bool consume_strict_if(Predicate predicate) noexcept {
if (predicate(current())) {
next();
return true;
}
return false;
}
};
/// Returns an error object from the current code in `ps` as well as its
......
......@@ -20,6 +20,7 @@
#include <cstdio>
#include <cstring>
#include <ctype.h>
#include <iterator>
#include <random>
#include <sstream>
......@@ -31,10 +32,13 @@
#include "caf/detail/get_mac_addresses.hpp"
#include "caf/detail/get_process_id.hpp"
#include "caf/detail/get_root_uuid.hpp"
#include "caf/detail/parse.hpp"
#include "caf/detail/parser/ascii_to_int.hpp"
#include "caf/detail/ripemd_160.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/make_counted.hpp"
#include "caf/parser_state.hpp"
#include "caf/sec.hpp"
#include "caf/serializer.hpp"
#include "caf/string_algorithms.hpp"
......@@ -93,6 +97,24 @@ bool node_id::default_data::valid(const host_id_type& x) noexcept {
return !std::all_of(x.begin(), x.end(), is_zero);
}
bool node_id::default_data::can_parse(string_view str) noexcept {
// Our format is "<20-byte-hex>#<pid>". With 2 characters per byte, this means
// a valid node ID has at least 42 characters.
if (str.size() < 42)
return false;
string_parser_state ps{str.begin(), str.end()};
for (size_t i = 0; i < 40; ++i)
if (!ps.consume_strict_if(isxdigit))
return false;
if (!ps.consume_strict('#'))
return false;
// We don't care for the value, but we invoke the actual number parser to make
// sure the value is in bounds.
uint32_t dummy;
detail::parse(ps, dummy);
return ps.code == pec::success;
}
bool node_id::default_data::valid() const noexcept {
return pid_ != 0 && valid(host_);
}
......@@ -228,6 +250,10 @@ void node_id::swap(node_id& x) {
data_.swap(x.data_);
}
bool node_id::can_parse(string_view str) noexcept {
return default_data::can_parse(str) || uri::can_parse(str);
}
namespace {
template <class Serializer>
......@@ -307,8 +333,7 @@ node_id make_node_id(uint32_t process_id,
return node_id{std::move(ptr)};
}
optional<node_id> make_node_id(uint32_t process_id,
const std::string& host_hash) {
optional<node_id> make_node_id(uint32_t process_id, string_view host_hash) {
using node_data = node_id::default_data;
if (host_hash.size() != node_data::host_id_size * 2)
return none;
......@@ -328,4 +353,27 @@ optional<node_id> make_node_id(uint32_t process_id,
return make_node_id(process_id, host_id);
}
error parse(string_view str, node_id& dest) {
if (node_id::default_data::can_parse(str)) {
CAF_ASSERT(str.size() >= 42);
auto host_hash = str.substr(0, 40);
auto pid_str = str.substr(41);
uint32_t pid_val = 0;
if (auto err = detail::parse(pid_str, pid_val))
return err;
if (auto nid = make_node_id(pid_val, host_hash)) {
dest = std::move(*nid);
return none;
}
CAF_LOG_ERROR("make_node_id failed after can_parse returned true");
return sec::invalid_argument;
}
if (auto nid_uri = make_uri(str)) {
dest = make_node_id(std::move(*nid_uri));
return none;
} else {
return std::move(nid_uri.error());
}
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE node_id
#include "caf/node_id.hpp"
#include "caf/test/dsl.hpp"
using namespace caf;
#define CHECK_PARSE_OK(str, ...) \
do { \
CAF_CHECK(node_id::can_parse(str)); \
node_id nid; \
CAF_CHECK_EQUAL(parse(str, nid), none); \
CAF_CHECK_EQUAL(nid, make_node_id(__VA_ARGS__)); \
} while (false)
CAF_TEST(node IDs are convertible from string) {
node_id::default_data::host_id_type hash{{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
}};
auto uri_id = unbox(make_uri("ip://foo:8080"));
CHECK_PARSE_OK("0102030405060708090A0B0C0D0E0F1011121314#1", 1, hash);
CHECK_PARSE_OK("0102030405060708090A0B0C0D0E0F1011121314#123", 123, hash);
CHECK_PARSE_OK("ip://foo:8080", uri_id);
}
#define CHECK_PARSE_FAIL(str) CAF_CHECK(!node_id::can_parse(str))
CAF_TEST(node IDs are not convertible from malformed strings) {
// not URIs
CHECK_PARSE_FAIL("foobar");
CHECK_PARSE_FAIL("CAF#1");
// uint32_t overflow on the process ID
CHECK_PARSE_FAIL("0102030405060708090A0B0C0D0E0F1011121314#42949672950");
}
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