Commit 115830b1 authored by Dominik Charousset's avatar Dominik Charousset

Add IPv6 address abstraction

parent dcb6f5ca
...@@ -67,6 +67,7 @@ set(LIBCAF_CORE_SRCS ...@@ -67,6 +67,7 @@ set(LIBCAF_CORE_SRCS
src/invoke_result_visitor.cpp src/invoke_result_visitor.cpp
src/ipv4_address.cpp src/ipv4_address.cpp
src/ipv4_subnet.cpp src/ipv4_subnet.cpp
src/ipv6_address.cpp
src/local_actor.cpp src/local_actor.cpp
src/logger.cpp src/logger.cpp
src/mailbox_element.cpp src/mailbox_element.cpp
......
This diff is collapsed.
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 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. *
******************************************************************************/
#pragma once
#include "caf/fwd.hpp"
namespace caf {
using ip_address = ipv6_address;
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 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. *
******************************************************************************/
#pragma once
#include <array>
#include <cstdint>
#include <string>
#include "caf/byte_address.hpp"
#include "caf/detail/comparable.hpp"
#include "caf/fwd.hpp"
namespace caf {
class ipv6_address : public byte_address<ipv6_address>,
detail::comparable<ipv6_address, ipv4_address> {
public:
// -- constants --------------------------------------------------------------
static constexpr size_t num_bytes = 16;
// -- member types -----------------------------------------------------------
using super = byte_address<ipv6_address>;
using array_type = std::array<uint8_t, num_bytes>;
// -- constructors, destructors, and assignment operators --------------------
ipv6_address();
explicit ipv6_address(ipv4_address addr);
ipv6_address(array_type bytes);
// -- comparison -------------------------------------------------------------
using super::compare;
/// Returns a negative number if `*this < other`, zero if `*this == other`
/// and a positive number if `*this > other`.
int compare(ipv4_address other) const noexcept;
// -- properties -------------------------------------------------------------
/// Returns whether this address embeds an IPv4 address.
bool embeds_v4() const noexcept;
/// Returns an embedded IPv4 address.
/// @pre `embeds_v4()`
ipv4_address embedded_v4() const noexcept;
/// Returns whether this is a loopback address.
bool is_loopback() const noexcept;
/// Returns the bytes of the IP address as array.
inline array_type& bytes() noexcept {
return bytes_;
}
/// Returns the bytes of the IP address as array.
inline const array_type& bytes() const noexcept {
return bytes_;
}
/// Alias for `bytes()`.
inline array_type& data() noexcept {
return bytes_;
}
/// Alias for `bytes()`.
inline const array_type& data() const noexcept {
return bytes_;
}
/// Returns the number of bytes of an IPv6 address.
inline size_t size() const noexcept {
return bytes_.size();
}
/// Returns whether this address contains only zeros, i.e., equals `::`.
inline bool zero() const noexcept {
return half_segments_[0] == 0 && half_segments_[1] == 0;
}
// -- inspection -------------------------------------------------------------
template <class Inspector>
friend typename Inspector::result_type inspect(Inspector& f,
ipv6_address& x) {
return f(x.bytes_);
}
friend std::string to_string(ipv6_address x);
private:
// -- member variables -------------------------------------------------------
union {
std::array<uint64_t, 2> half_segments_;
std::array<uint32_t, 4> quad_segments_;
std::array<uint16_t, 8> oct_segments_;
array_type bytes_;
};
};
error parse(string_view str, ipv6_address& dest);
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 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. *
******************************************************************************/
#include "caf/ipv6_address.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/detail/parser/read_ipv6_address.hpp"
#include "caf/error.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/pec.hpp"
#include "caf/string_view.hpp"
namespace caf {
namespace {
struct ipv6_address_consumer {
ipv6_address& dest;
ipv6_address_consumer(ipv6_address& ref) : dest(ref) {
// nop
}
void value(ipv6_address val) {
dest = val;
}
};
// It's good practice when printing IPv6 addresses to:
// - remove leading zeros in all segments
// - use lowercase hexadecimal characters
// @param pointer to an uint16_t segment converted to uint8_t*
void append_v6_hex(std::string& result, const uint8_t* xs) {
auto tbl = "0123456789abcdef";
size_t j = 0;
std::array<char, (sizeof(uint16_t) * 2) + 1> buf;
buf.fill(0);
for (size_t i = 0; i < sizeof(uint16_t); ++i) {
auto c = xs[i];
buf[j++] = tbl[c >> 4];
buf[j++] = tbl[c & 0x0F];
}
auto pred = [](char c) {
return c != '0';
};
auto first_non_zero = std::find_if(buf.begin(), buf.end(), pred);
CAF_ASSERT(first_non_zero != buf.end());
if (*first_non_zero != '\0')
result += &(*first_non_zero);
else
result += '0';
}
inline uint32_t net_order_32(uint32_t value) {
return detail::to_network_order(value);
}
inline uint64_t net_order_64(uint64_t value) {
return detail::to_network_order(value);
}
std::array<uint32_t, 3> v4_prefix{{0, 0, net_order_32(0x0000FFFFu)}};
} // namespace <anonymous>
// -- constructors, destructors, and assignment operators ----------------------
ipv6_address::ipv6_address() {
half_segments_[0] = 0;
half_segments_[1] = 0;
}
ipv6_address::ipv6_address(ipv4_address addr) {
std::copy(v4_prefix.begin(), v4_prefix.end(), quad_segments_.begin());
quad_segments_.back() = addr.bits();
}
ipv6_address::ipv6_address(array_type bytes) {
memcpy(bytes_.data(), bytes.data(), bytes.size());
}
int ipv6_address::compare(ipv4_address other) const noexcept {
ipv6_address tmp{other};
return compare(tmp);
}
// -- properties ---------------------------------------------------------------
bool ipv6_address::embeds_v4() const noexcept {
using std::begin;
using std::end;
return std::equal(begin(v4_prefix), end(v4_prefix), quad_segments_.begin());
}
ipv4_address ipv6_address::embedded_v4() const noexcept {
ipv4_address result;
result.bits(quad_segments_.back());
return result;
}
bool ipv6_address::is_loopback() const noexcept {
// IPv6 defines "::1" as the loopback address, when embedding a v4 we
// dispatch accordingly.
return embeds_v4()
? embedded_v4().is_loopback()
: half_segments_[0] == 0 && half_segments_[1] == net_order_64(1u);
}
// -- related free functions ---------------------------------------------------
namespace {
using u16_iterator = const uint16_t*;
using u16_range = std::pair<u16_iterator, u16_iterator>;
u16_range longest_streak(u16_iterator first, u16_iterator last) {
auto zero = [](uint16_t x, uint16_t y) {
return x == 0 && y == 0;
};
auto not_zero = [](uint16_t x) {
return x != 0;
};
u16_range result;
result.first = std::adjacent_find(first, last, zero);
if (result.first == last)
return {last, last};
result.second = std::find_if(result.first + 2, last, not_zero);
if (result.second == last)
return result;
auto next_streak = longest_streak(result.second, last);
auto distance = [](u16_range x) {
return std::distance(x.first, x.second);
};
return distance(result) >= distance(next_streak) ? result : next_streak;
}
} // namespace <anonymous>
std::string to_string(ipv6_address x) {
// Shortcut for embedded v4 addresses.
if (x.embeds_v4())
return to_string(x.embedded_v4());
// Shortcut for all-zero addresses.
if (x.zero())
return "::";
// Output buffer.
std::string result;
// Utility for appending chunks to the result.
auto add_chunk = [&](uint16_t x) {
append_v6_hex(result, reinterpret_cast<uint8_t*>(&x));
};
auto add_chunks = [&](u16_iterator i, u16_iterator e) {
if (i != e) {
add_chunk(*i++);
for (; i != e; ++i) {
result += ':';
add_chunk(*i);
}
}
};
// Scan for the longest streak of 0 16-bit chunks for :: shorthand.
auto first = x.oct_segments_.begin();
auto last = x.oct_segments_.end();
auto streak = longest_streak(first, last);
// Put it all together.
if (streak.first == last) {
add_chunks(first, last);
} else {
add_chunks(first, streak.first);
result += "::";
add_chunks(streak.second, last);
}
return result;
}
error parse(string_view str, ipv6_address& dest) {
using namespace detail;
parser::state<string_view::iterator> res{str.begin(), str.end()};
ipv6_address_consumer f{dest};
parser::read_ipv6_address(res, f);
if (res.code == pec::success)
return none;
return make_error(res.code, res.line, res.column);
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 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. *
******************************************************************************/
#include <initializer_list>
#include "caf/config.hpp"
#define CAF_SUITE ipv6_address
#include "caf/test/dsl.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/ipv6_address.hpp"
using namespace caf;
namespace {
using array_type = ipv6_address::array_type;
void addr_fill(ipv6_address::array_type& arr,
std::initializer_list<uint16_t> chunks) {
union {
uint16_t i;
std::array<uint8_t, 2> a;
} tmp;
size_t p = 0;
for (auto chunk : chunks) {
tmp.i = detail::to_network_order(chunk);
arr[p++] = tmp.a[0];
arr[p++] = tmp.a[1];
}
}
ipv6_address addr(std::initializer_list<uint16_t> prefix,
std::initializer_list<uint16_t> suffix) {
CAF_ASSERT((prefix.size() + suffix.size()) <= 8);
ipv6_address::array_type bytes;
bytes.fill(0);
addr_fill(bytes, suffix);
std::rotate(bytes.begin(), bytes.begin() + suffix.size() * 2, bytes.end());
addr_fill(bytes, prefix);
return ipv6_address{bytes};
}
ipv6_address addr(std::initializer_list<uint16_t> segments) {
ipv6_address::array_type bytes;
bytes.fill(0);
addr_fill(bytes, segments);
return ipv6_address{bytes};
}
} // namespace <anonymous>
CAF_TEST(constructing) {
ipv6_address localhost({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1});
CAF_CHECK_EQUAL(localhost.data(),
array_type({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}));
CAF_CHECK_EQUAL(localhost, addr({}, {0x01}));
}
CAF_TEST(comparison) {
CAF_CHECK_EQUAL(addr({1, 2, 3}), addr({1, 2, 3}));
CAF_CHECK_NOT_EQUAL(addr({3, 2, 1}), addr({1, 2, 3}));
CAF_CHECK_EQUAL(addr({}, {0xFFFF, 0x7F00, 0x0001}),
ipv4_address({127, 0, 0, 1}));
}
CAF_TEST(from string) {
auto from_string = [](string_view str) {
ipv6_address result;
auto err = parse(str, result);
if (err)
CAF_FAIL("error while parsing " << str << ": " << to_string(err));
return result;
};
CAF_CHECK_EQUAL(from_string("::1"), addr({}, {0x01}));
CAF_CHECK_EQUAL(from_string("::11"), addr({}, {0x11}));
CAF_CHECK_EQUAL(from_string("::112"), addr({}, {0x0112}));
CAF_CHECK_EQUAL(from_string("::1122"), addr({}, {0x1122}));
CAF_CHECK_EQUAL(from_string("::1:2"), addr({}, {0x01, 0x02}));
CAF_CHECK_EQUAL(from_string("::1:2"), addr({}, {0x01, 0x02}));
CAF_CHECK_EQUAL(from_string("1::1"), addr({0x01}, {0x01}));
CAF_CHECK_EQUAL(from_string("1::"), addr({0x01}, {}));
CAF_CHECK_EQUAL(from_string("0.1.0.1"), addr({}, {0xFFFF, 0x01, 0x01}));
CAF_CHECK_EQUAL(from_string("::ffff:127.0.0.1"),
addr({}, {0xFFFF, 0x7F00, 0x0001}));
CAF_CHECK_EQUAL(from_string("1:2:3:4:5:6:7:8"), addr({1,2,3,4,5,6,7,8}));
CAF_CHECK_EQUAL(from_string("1:2:3:4::5:6:7:8"), addr({1,2,3,4,5,6,7,8}));
CAF_CHECK_EQUAL(from_string("1:2:3:4:5:6:0.7.0.8"), addr({1,2,3,4,5,6,7,8}));
auto invalid = [](string_view str) {
ipv6_address result;
auto err = parse(str, result);
return err != none;
};
CAF_CHECK(invalid("1:2:3:4:5:6:7:8:9"));
CAF_CHECK(invalid("1:2:3:4::5:6:7:8:9"));
CAF_CHECK(invalid("1:2:3::4:5:6::7:8:9"));
}
CAF_TEST(to string) {
CAF_CHECK_EQUAL(to_string(addr({}, {0x01})), "::1");
CAF_CHECK_EQUAL(to_string(addr({0x01}, {0x01})), "1::1");
CAF_CHECK_EQUAL(to_string(addr({0x01})), "1::");
CAF_CHECK_EQUAL(to_string(addr({}, {0xFFFF, 0x01, 0x01})), "0.1.0.1");
}
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