]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1661] Fixed build on last Fedora
authorFrancis Dupont <fdupont@isc.org>
Mon, 22 Feb 2021 09:06:45 +0000 (10:06 +0100)
committerFrancis Dupont <fdupont@isc.org>
Fri, 26 Mar 2021 14:39:56 +0000 (15:39 +0100)
103 files changed:
src/lib/asiolink/asiolink/Makefile.am [new file with mode: 0644]
src/lib/asiolink/asiolink/README [new file with mode: 0644]
src/lib/asiolink/asiolink/addr_utilities.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/addr_utilities.h [new file with mode: 0644]
src/lib/asiolink/asiolink/asio_wrapper.h [new file with mode: 0644]
src/lib/asiolink/asiolink/asiolink.dox [new file with mode: 0644]
src/lib/asiolink/asiolink/asiolink.h [new file with mode: 0644]
src/lib/asiolink/asiolink/botan_tls.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/botan_tls.h [new file with mode: 0644]
src/lib/asiolink/asiolink/common_tls.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/common_tls.h [new file with mode: 0644]
src/lib/asiolink/asiolink/crypto_tls.h [new file with mode: 0644]
src/lib/asiolink/asiolink/dummy_io_cb.h [new file with mode: 0644]
src/lib/asiolink/asiolink/interval_timer.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/interval_timer.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_acceptor.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_address.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/io_address.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_asio_socket.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_endpoint.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/io_endpoint.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_error.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_service.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/io_service.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_service_signal.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/io_service_signal.h [new file with mode: 0644]
src/lib/asiolink/asiolink/io_socket.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/io_socket.h [new file with mode: 0644]
src/lib/asiolink/asiolink/openssl_tls.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/openssl_tls.h [new file with mode: 0644]
src/lib/asiolink/asiolink/process_spawn.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/process_spawn.h [new file with mode: 0644]
src/lib/asiolink/asiolink/tcp_acceptor.h [new file with mode: 0644]
src/lib/asiolink/asiolink/tcp_endpoint.h [new file with mode: 0644]
src/lib/asiolink/asiolink/tcp_socket.h [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/.gitignore [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/Makefile.am [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/addr_utilities_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/dummy_io_callback_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/hash_address_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/interval_timer_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/io_address_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/io_endpoint_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/io_service_signal_unittests.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/io_service_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/io_socket_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/process_spawn_app.sh.in [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/process_spawn_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/run_unittests.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/tcp_acceptor_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/tcp_endpoint_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/tcp_socket_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/tls_acceptor_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/tls_socket_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/tls_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/udp_endpoint_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/udp_socket_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/tests/unix_domain_socket_unittest.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/.gitignore [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/Makefile.am [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/00af7a28.0 [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/0c7eedb9.0 [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/28f5a777.0 [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/2eefa08b.0 [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/7a5b785e.0 [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/ad950210.0 [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/doc.txt [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/ext-addr-conf.cnf [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/ext-conf.cnf [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-ca.crt [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-ca.key [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-client.crt [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-client.csr [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-client.key [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-client.p12 [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-other.crt [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-other.key [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-self.crt [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-self.key [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-server-addr.crt [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-server-addr.csr [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-server.crt [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-server.csr [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/kea-server.key [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/server-addr-conf.cnf [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/ca/server-conf.cnf [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/openssl_sample_client.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/openssl_sample_server.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/test_server_unix_socket.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/test_server_unix_socket.h [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/test_tls.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/test_tls.h [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/timed_signal.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/testutils/timed_signal.h [new file with mode: 0644]
src/lib/asiolink/asiolink/tls_acceptor.h [new file with mode: 0644]
src/lib/asiolink/asiolink/tls_socket.h [new file with mode: 0644]
src/lib/asiolink/asiolink/udp_endpoint.h [new file with mode: 0644]
src/lib/asiolink/asiolink/udp_socket.h [new file with mode: 0644]
src/lib/asiolink/asiolink/unix_domain_socket.cc [new file with mode: 0644]
src/lib/asiolink/asiolink/unix_domain_socket.h [new file with mode: 0644]
src/lib/asiolink/asiolink/unix_domain_socket_acceptor.h [new file with mode: 0644]
src/lib/asiolink/asiolink/unix_domain_socket_endpoint.h [new file with mode: 0644]
src/lib/asiolink/testutils/Makefile.am

diff --git a/src/lib/asiolink/asiolink/Makefile.am b/src/lib/asiolink/asiolink/Makefile.am
new file mode 100644 (file)
index 0000000..235b57b
--- /dev/null
@@ -0,0 +1,90 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS) -Wno-non-virtual-dtor
+
+EXTRA_DIST = asiolink.dox
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-asiolink.la
+
+libkea_asiolink_la_LDFLAGS = -no-undefined -version-info 23:0:0
+libkea_asiolink_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+
+libkea_asiolink_la_SOURCES  = asiolink.h
+libkea_asiolink_la_SOURCES += asio_wrapper.h
+libkea_asiolink_la_SOURCES += addr_utilities.cc addr_utilities.h
+libkea_asiolink_la_SOURCES += botan_tls.h
+libkea_asiolink_la_SOURCES += common_tls.cc common_tls.h
+libkea_asiolink_la_SOURCES += crypto_tls.h
+libkea_asiolink_la_SOURCES += dummy_io_cb.h
+libkea_asiolink_la_SOURCES += interval_timer.cc interval_timer.h
+libkea_asiolink_la_SOURCES += io_acceptor.h
+libkea_asiolink_la_SOURCES += io_address.cc io_address.h
+libkea_asiolink_la_SOURCES += io_asio_socket.h
+libkea_asiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
+libkea_asiolink_la_SOURCES += io_error.h
+libkea_asiolink_la_SOURCES += io_service.h io_service.cc
+libkea_asiolink_la_SOURCES += io_service_signal.cc io_service_signal.h
+libkea_asiolink_la_SOURCES += io_socket.h io_socket.cc
+libkea_asiolink_la_SOURCES += openssl_tls.h
+libkea_asiolink_la_SOURCES += process_spawn.h process_spawn.cc
+libkea_asiolink_la_SOURCES += tcp_acceptor.h
+libkea_asiolink_la_SOURCES += tcp_endpoint.h
+libkea_asiolink_la_SOURCES += tcp_socket.h
+libkea_asiolink_la_SOURCES += tls_acceptor.h
+libkea_asiolink_la_SOURCES += tls_socket.h
+libkea_asiolink_la_SOURCES += udp_endpoint.h
+libkea_asiolink_la_SOURCES += udp_socket.h
+libkea_asiolink_la_SOURCES += unix_domain_socket.cc unix_domain_socket.h
+libkea_asiolink_la_SOURCES += unix_domain_socket_acceptor.h
+libkea_asiolink_la_SOURCES += unix_domain_socket_endpoint.h
+
+if HAVE_BOTAN
+libkea_asiolink_la_SOURCES += botan_tls.cc
+endif
+if HAVE_OPENSSL
+libkea_asiolink_la_SOURCES += openssl_tls.cc
+endif
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+libkea_asiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_asiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_asiolink_la_LIBADD  = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_asiolink_la_LIBADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_asiolink_includedir = $(pkgincludedir)/asiolink
+libkea_asiolink_include_HEADERS = \
+       addr_utilities.h \
+       asio_wrapper.h \
+       asiolink.h \
+       botan_tls.h \
+       common_tls.h \
+       crypto_tls.h \
+       dummy_io_cb.h \
+       interval_timer.h \
+       io_acceptor.h \
+       io_address.h \
+       io_asio_socket.h \
+       io_endpoint.h \
+       io_error.h \
+       io_service.h \
+       io_service_signal.h \
+       io_socket.h \
+       openssl_tls.h \
+       process_spawn.h \
+       tcp_acceptor.h \
+       tcp_endpoint.h \
+       tcp_socket.h \
+       tls_acceptor.h \
+       tls_socket.h \
+       udp_endpoint.h \
+       udp_socket.h \
+       unix_domain_socket.h \
+       unix_domain_socket_acceptor.h \
+       unix_domain_socket_endpoint.h
diff --git a/src/lib/asiolink/asiolink/README b/src/lib/asiolink/asiolink/README
new file mode 100644 (file)
index 0000000..83f66b8
--- /dev/null
@@ -0,0 +1,28 @@
+The asiolink library is intended to provide an abstraction layer between
+Kea modules and the socket I/O subsystem we are using (currently, the
+headers-only version of ASIO included in Boost).  This has several benefits,
+including:
+
+  - Simple interface
+
+  - Back-end flexibility: It would be relatively easy to switch to any
+    other asynchronous I/O system.
+
+  - Cleaner compilation:  The ASIO headers include code which can
+    generate warnings in some compilers due to unused parameters and
+    such.  Including ASIO header files throughout the Kea tree would
+    require us to relax the strictness of our error checking.  Including
+    them in only one place allows us to relax strictness here, while
+    leaving it in place elsewhere.
+
+Some of the classes defined here--for example, IOSocket, IOEndpoint,
+and IOAddress--are to be used by Kea modules as wrappers around
+ASIO-specific classes.
+
+
+Logging
+-------
+
+At this point, nothing is logged by this low-level library. We may
+revisit that in the future, if we find suitable messages to log, but
+right now there are also no loggers initialized or called.
diff --git a/src/lib/asiolink/asiolink/addr_utilities.cc b/src/lib/asiolink/asiolink/addr_utilities.cc
new file mode 100644 (file)
index 0000000..04b44dc
--- /dev/null
@@ -0,0 +1,430 @@
+// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/addr_utilities.h>
+#include <exceptions/exceptions.h>
+
+#include <vector>
+#include <limits>
+#include <string.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief mask used for first/last address calculation in a IPv4 prefix
+///
+/// Using a static mask is faster than calculating it dynamically every time.
+const uint32_t bitMask4[] = { 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
+                              0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
+                              0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
+                              0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
+                              0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
+                              0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
+                              0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
+                              0x0000000f, 0x00000007, 0x00000003, 0x00000001,
+                              0x00000000 };
+
+/// @brief mask used for first/last address calculation in a IPv6 prefix
+const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+
+/// @brief mask used for IPv6 prefix calculation
+const uint8_t revMask6[]= { 0xff, 0x7f, 0x3f, 0x1f, 0xf, 0x7, 0x3, 0x1 };
+
+
+/// @brief calculates the first IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix
+/// @param len prefix length
+IOAddress firstAddrInPrefix6(const IOAddress& prefix, uint8_t len) {
+    if (len > 128) {
+        isc_throw(isc::BadValue,
+                  "Too large netmask. 0..128 is allowed in IPv6");
+    }
+
+    // First we copy the whole address as 16 bytes.
+    // We don't check that it is a valid IPv6 address and thus has
+    // the required length because it is already checked by
+    // the calling function.
+    uint8_t packed[V6ADDRESS_LEN];
+    memcpy(packed, &prefix.toBytes()[0], V6ADDRESS_LEN);
+
+    // If the length is divisible by 8, it is simple. We just zero out the host
+    // part. Otherwise we need to handle the byte that has to be partially
+    // zeroed.
+    if (len % 8 != 0) {
+
+        // Get the appropriate mask. It has relevant bits (those that should
+        // stay) set and irrelevant (those that should be wiped) cleared.
+        uint8_t mask = bitMask6[len % 8];
+
+        // Let's leave only whatever the mask says should not be cleared.
+        packed[len / 8] = packed[len / 8] & mask;
+
+        // Since we have just dealt with this byte, let's move the prefix length
+        // to the beginning of the next byte (len is expressed in bits).
+        len = (len / 8 + 1) * 8;
+    }
+
+    // Clear out the remaining bits.
+    for (int i = len / 8; i < sizeof(packed); ++i) {
+        packed[i] = 0x0;
+    }
+
+    // Finally, let's wrap this into nice and easy IOAddress object.
+    return (IOAddress::fromBytes(AF_INET6, packed));
+}
+
+/// @brief calculates the first IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix
+/// @param len netmask length (0-32)
+IOAddress firstAddrInPrefix4(const IOAddress& prefix, uint8_t len) {
+    if (len > 32) {
+        isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+    }
+
+    // We don't check that it is a valid IPv4 address and thus has
+    // a required length of 4 bytes because it has been already
+    // checked by the calling function.
+    uint32_t addr = prefix.toUint32();
+    return (IOAddress(addr & (~bitMask4[len])));
+}
+
+/// @brief calculates the last IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix that we calculate first address for
+/// @param len netmask length (0-32)
+IOAddress lastAddrInPrefix4(const IOAddress& prefix, uint8_t len) {
+    if (len > 32) {
+        isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+    }
+
+    uint32_t addr = prefix.toUint32();
+    return (IOAddress(addr | bitMask4[len]));
+}
+
+/// @brief calculates the last IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use lastAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix that we calculate first address for
+/// @param len netmask length (0-128)
+IOAddress lastAddrInPrefix6(const IOAddress& prefix, uint8_t len) {
+    if (len > 128) {
+        isc_throw(isc::BadValue,
+                  "Too large netmask. 0..128 is allowed in IPv6");
+    }
+
+    // First we copy the whole address as 16 bytes.
+    uint8_t packed[V6ADDRESS_LEN];
+    memcpy(packed, &prefix.toBytes()[0], 16);
+
+    // if the length is divisible by 8, it is simple. We just fill the host part
+    // with ones. Otherwise we need to handle the byte that has to be partially
+    // zeroed.
+    if (len % 8 != 0) {
+        // Get the appropriate mask. It has relevant bits (those that should
+        // stay) set and irrelevant (those that should be set to 1) cleared.
+        uint8_t mask = bitMask6[len % 8];
+
+        // Let's set those irrelevant bits with 1. It would be perhaps
+        // easier to not use negation here and invert bitMask6 content. However,
+        // with this approach, we can use the same mask in first and last
+        // address calculations.
+        packed[len / 8] = packed[len / 8] | ~mask;
+
+        // Since we have just dealt with this byte, let's move the prefix length
+        // to the beginning of the next byte (len is expressed in bits).
+        len = (len / 8 + 1) * 8;
+    }
+
+    // Finally set remaining bits to 1.
+    for (int i = len / 8; i < sizeof(packed); ++i) {
+        packed[i] = 0xff;
+    }
+
+    // Finally, let's wrap this into nice and easy IOAddress object.
+    return (IOAddress::fromBytes(AF_INET6, packed));
+}
+
+}; // end of anonymous namespace
+
+namespace isc {
+namespace asiolink {
+
+IOAddress firstAddrInPrefix(const IOAddress& prefix, uint8_t len) {
+    if (prefix.isV4()) {
+        return (firstAddrInPrefix4(prefix, len));
+
+    } else {
+        return (firstAddrInPrefix6(prefix, len));
+
+    }
+}
+
+IOAddress lastAddrInPrefix(const IOAddress& prefix, uint8_t len) {
+    if (prefix.isV4()) {
+        return (lastAddrInPrefix4(prefix, len));
+
+    } else {
+        return (lastAddrInPrefix6(prefix, len));
+
+    }
+}
+
+IOAddress getNetmask4(uint8_t len) {
+    if (len > 32) {
+        isc_throw(BadValue, "Invalid netmask size "
+                  << static_cast<unsigned>(len) << ", allowed range is 0..32");
+    }
+    uint32_t x = ~bitMask4[len];
+
+    return (IOAddress(x));
+}
+
+uint64_t
+addrsInRange(const IOAddress& min, const IOAddress& max) {
+    if (min.getFamily() != max.getFamily()) {
+        isc_throw(BadValue, "Both addresses have to be the same family");
+    }
+
+    if (max < min) {
+        isc_throw(BadValue, min.toText() << " must not be greater than "
+                  << max.toText());
+    }
+
+    if (min.isV4()) {
+        // Let's explicitly cast last_ and first_ (IOAddress). This conversion is
+        // automatic, but let's explicitly cast it show that we moved to integer
+        // domain and addresses are now substractable.
+        uint64_t max_numeric = static_cast<uint64_t>(max.toUint32());
+        uint64_t min_numeric = static_cast<uint64_t>(min.toUint32());
+
+        // We can simply subtract the values. We need to increase the result
+        // by one, as both min and max are included in the range. So even if
+        // min == max, there's one address.
+        return (max_numeric - min_numeric + 1);
+    } else {
+
+        // Calculating the difference in v6 is more involved. Let's subtract
+        // one from the other. By subtracting min from max, we move the
+        // [a, b] range to the [0, (b-a)] range. We don't care about the beginning
+        // of the new range (it's always zero). The upper bound now specifies
+        // the number of addresses minus one.
+        IOAddress count = IOAddress::subtract(max, min);
+
+        // There's one very special case. Someone is trying to check how many
+        // IPv6 addresses are in IPv6 address space. He called this method
+        // with ::, ffff:ffff:ffff:fffff:ffff:ffff:ffff:ffff. The diff is also
+        // all 1s. Had we increased it by one, the address would flip to all 0s.
+        // This will not happen in a real world. Apparently, unit-tests are
+        // sometimes nastier then a real world.
+        static IOAddress max6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        if (count == max6) {
+            return (std::numeric_limits<uint64_t>::max());
+        }
+
+        // Increase it by one (a..a range still contains one address, even though
+        // a subtracted from a is zero).
+        count = IOAddress::increase(count);
+
+        // We don't have uint128, so for anything greater than 2^64, we'll just
+        // assume numeric_limits<uint64_t>::max. Let's do it the manual way.
+        const std::vector<uint8_t>& bin(count.toBytes());
+
+        // If any of the most significant 64 bits is set, we have more than
+        // 2^64 addresses and can't represent it even on uint64_t.
+        for (int i = 0 ; i < 8; i++) {
+            if (bin[i]) {
+                return (std::numeric_limits<uint64_t>::max());
+            }
+        }
+
+        // Ok, we're good. The pool is sanely sized. It may be huge, but at least
+        // that's something we can represent on uint64_t.
+        uint64_t numeric = 0;
+        for (int i = 8; i < 16; i++) {
+            numeric <<= 8;
+            numeric += bin[i];
+        }
+
+        return (numeric);
+    }
+}
+
+int
+prefixLengthFromRange(const IOAddress& min, const IOAddress& max) {
+    if (min.getFamily() != max.getFamily()) {
+        isc_throw(BadValue, "Both addresses have to be the same family");
+    }
+
+    if (max < min) {
+        isc_throw(BadValue, min.toText() << " must not be greater than "
+                  << max.toText());
+    }
+
+    if (min.isV4()) {
+        // Get addresses as integers
+        uint32_t max_numeric = max.toUint32();
+        uint32_t min_numeric = min.toUint32();
+
+        // Get the exclusive or which must be one of the bit masks
+        // and the min must be at the beginning of the prefix
+        // so it does not contribute to trailing ones.
+        uint32_t xor_numeric = max_numeric ^ min_numeric;
+        if ((min_numeric & ~xor_numeric) != min_numeric) {
+            return (-1);
+        }
+        for (uint8_t prefix_len = 0; prefix_len <= 32; ++prefix_len) {
+            if (xor_numeric == bitMask4[prefix_len]) {
+                // Got it: the wanted value is also the index
+                return (static_cast<int>(prefix_len));
+            }
+        }
+
+        // If it was not found the range is not from a prefix / prefix_len
+        return (-1);
+    } else {
+        // Get addresses as 16 bytes
+        uint8_t min_packed[V6ADDRESS_LEN];
+        memcpy(min_packed, &min.toBytes()[0], 16);
+        uint8_t max_packed[V6ADDRESS_LEN];
+        memcpy(max_packed, &max.toBytes()[0], 16);
+
+        // Scan the exclusive or of addresses to find a difference
+        int candidate = 128;
+        bool zeroes = true;
+        for (uint8_t i = 0; i < 16; ++i) {
+            uint8_t xor_byte = min_packed[i] ^ max_packed[i];
+            // The min must be at the beginning of the prefix
+            // so it does not contribute to trailing ones.
+            if ((min_packed[i] & ~xor_byte) != min_packed[i]) {
+                return (-1);
+            }
+            if (zeroes) {
+                // Skipping zero bits searching for one bits
+                if (xor_byte == 0) {
+                    continue;
+                }
+                // Found a one bit: note the fact
+                zeroes = false;
+                // Compare the exclusive or to masks
+                for (uint8_t j = 0; j < 8; ++j) {
+                    if (xor_byte == revMask6[j]) {
+                        // Got it the prefix length: note it
+                        candidate = static_cast<int>((i * 8) + j);
+                    }
+                }
+                if (candidate == 128) {
+                    // Not found? The range is not from a prefix / prefix_len
+                    return (-1);
+                }
+            } else {
+                // Checking that trailing bits are on bits
+                if (xor_byte == 0xff) {
+                    continue;
+                }
+                // Not all ones is bad
+                return (-1);
+            }
+        }
+        return (candidate);
+    }
+}
+
+uint64_t prefixesInRange(const uint8_t pool_len, const uint8_t delegated_len) {
+    if (delegated_len < pool_len) {
+        return (0);
+    }
+
+    uint64_t count = delegated_len - pool_len;
+
+    if (count == 0) {
+        // If we want to delegate /64 out of /64 pool, we have only
+        // one prefix.
+        return (1);
+    } else if (count >= 64) {
+        // If the difference is greater than or equal 64, e.g. we want to
+        // delegate /96 out of /16 pool, the number is bigger than we can
+        // express, so we'll stick with maximum value of uint64_t.
+        return (std::numeric_limits<uint64_t>::max());
+    } else {
+        // Now count specifies the exponent (e.g. if the difference between the
+        // delegated and pool length is 4, we have 16 prefixes), so we need
+        // to calculate 2^(count - 1)
+        return ((static_cast<uint64_t>(2)) << (count - 1));
+    }
+}
+
+IOAddress offsetAddress(const IOAddress& addr, uint64_t offset) {
+    // There is nothing to do if the offset is 0.
+    if (offset == 0) {
+        return (addr);
+    }
+
+    // If this is an IPv4 address, then we utilize the conversion to uint32_t.
+    if (addr.isV4()) {
+        auto addr_uint32 = static_cast<uint64_t>(addr.toUint32());
+        // If the result would exceed the maximum possible IPv4 address, let's return
+        // the maximum IPv4 address.
+        if (static_cast<uint64_t>(std::numeric_limits<uint32_t>::max() - addr_uint32) < offset) {
+            return (IOAddress(std::numeric_limits<uint32_t>::max()));
+        }
+        return (IOAddress(static_cast<uint32_t>(addr_uint32 + offset)));
+    }
+
+    // This is IPv6 address. Let's first convert the offset value to network
+    // byte order and store within the vector.
+    std::vector<uint8_t> offset_bytes(8);
+    for (int offset_idx = offset_bytes.size() - 1; offset_idx >= 0; --offset_idx) {
+        offset_bytes[offset_idx] = static_cast<uint8_t>(offset & 0x00000000000000ff);
+        offset = offset >> 8;
+    }
+
+    // Convert the IPv6 address to vector.
+    auto addr_bytes = addr.toBytes();
+
+    // Sum up the bytes.
+
+    uint16_t carry = 0;
+    for (int i = offset_bytes.size() - 1; (i >= 0) || (carry > 0); --i) {
+        // Sum the bytes of the address, offset and the carry.
+        uint16_t sum = static_cast<uint16_t>(addr_bytes[i+8]) + carry;
+
+        // Protect against the case when we went beyond the offset vector and
+        // we have only carry to add.
+        if (i >= 0 ) {
+            sum += static_cast<uint16_t>(offset_bytes[i]);
+        }
+
+        // Update the address byte.
+        addr_bytes[i+8] = sum % 256;
+
+        // Calculate the carry value.
+        carry = sum / 256;
+    }
+
+    // Reconstruct IPv6 address from the vector.
+    return (IOAddress::fromBytes(AF_INET6, &addr_bytes[0]));
+}
+
+
+};
+};
diff --git a/src/lib/asiolink/asiolink/addr_utilities.h b/src/lib/asiolink/asiolink/addr_utilities.h
new file mode 100644 (file)
index 0000000..bd78dee
--- /dev/null
@@ -0,0 +1,98 @@
+// Copyright (C) 2012-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ADDR_UTILITIES_H
+#define ADDR_UTILITIES_H
+
+#include <asiolink/io_address.h>
+
+namespace isc {
+namespace asiolink {
+
+/// This code is based on similar code from the Dibbler project. I, Tomasz Mrugalski,
+/// as a sole creator of that code hereby release it under BSD license for the benefit
+/// of the Kea project.
+
+/// @brief returns a first address in a given prefix
+///
+/// Example: For 2001:db8:1\::deaf:beef and length /120 the function will return
+/// 2001:db8:1\::dead:be00. See also @ref lastAddrInPrefix.
+///
+/// @param prefix and address that belongs to a prefix
+/// @param len prefix length
+///
+/// @return first address from a prefix
+IOAddress firstAddrInPrefix(const IOAddress& prefix, uint8_t len);
+
+/// @brief returns a last address in a given prefix
+///
+/// Example: For 2001:db8:1\::deaf:beef and length /112 the function will return
+/// 2001:db8:1\::dead:ffff. See also @ref firstAddrInPrefix.
+///
+/// @param prefix and address that belongs to a prefix
+/// @param len prefix length
+///
+/// @return first address from a prefix
+IOAddress lastAddrInPrefix(const IOAddress& prefix, uint8_t len);
+
+/// @brief Generates an IPv4 netmask of specified length
+/// @throw BadValue if len is greater than 32
+/// @return netmask
+IOAddress getNetmask4(uint8_t len);
+
+
+/// @brief Returns number of available addresses in the specified range (min - max).
+///
+/// Note that for min equal max case, the number of addresses is one.
+///
+/// @throw BadValue if min and max are not of the same family (both must be
+///        either IPv4 or IPv6) or if max < min.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return number of addresses in range
+uint64_t addrsInRange(const IOAddress& min, const IOAddress& max);
+
+/// @brief Returns prefix length from the specified range (min - max).
+///
+/// This can be considered as log2(addrsInRange)
+///
+/// @throw BadValue if min and max do not define a prefix.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return the prefix length or -1 if the range is not from a prefix
+int prefixLengthFromRange(const IOAddress& min, const IOAddress& max);
+
+/// @brief Returns number of available IPv6 prefixes in the specified prefix.
+///
+/// Note that if the answer is bigger than uint64_t can hold, it will return
+/// the max value of uint64_t type.
+///
+/// Example: prefixesInRange(48, 64) returns 65536, as there are /48 pool
+/// can be split into 65536 prefixes, each /64 large.
+///
+/// @param pool_len length of the pool in bits
+/// @param delegated_len length of the prefixes to be delegated from the pool
+/// @return number of prefixes in range
+uint64_t prefixesInRange(const uint8_t pool_len, const uint8_t delegated_len);
+
+/// @brief Finds the address increased by offset.
+///
+/// Adds offset to the IPv4 or iPv6 address and finds the resulting address.
+/// Note that the current limitation is the maximum value of the offset,
+/// i.e. max uint64_t. If the sum of the IPv4 address and the offset exceeds
+/// the maximum value of uint32_t type, the 255.255.255.255 is returned.
+///
+/// @param addr input address
+/// @param offset distance of the returned address from the input address.
+/// @return address being offset greater than the input address
+IOAddress offsetAddress(const IOAddress& addr, uint64_t offset);
+
+};
+};
+
+#endif // ADDR_UTILITIES_H
diff --git a/src/lib/asiolink/asiolink/asio_wrapper.h b/src/lib/asiolink/asiolink/asio_wrapper.h
new file mode 100644 (file)
index 0000000..1cedd09
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#ifndef ASIO_WRAPPER_H
+#define ASIO_WRAPPER_H 1
+
+/// !!! IMPORTANT THIS IS A HACK FOR BOOST HEADERS ONLY BUILDING !!!!
+///
+/// As of #5215 (Kea 1.3) The default build configuration is to link with
+/// Boost's system library (boost_system) rather than build with Boost's
+/// headers only. Linking with the boost_system eliminates the issue as
+/// detailed below. This file exists solely for the purpose of allowing
+/// people to attempt to build headers only.  ISC DOES NOT RECOMMEND
+/// building Kea with Boost headers only.
+///
+/// This file must be included anywhere one would normally have included
+/// boost/asio.hpp.  Until the issue described below is resolved in some
+/// other fashion, (or we abandon support for headers only building)
+/// asio.hpp MUST NOT be included other than through this file.
+///
+/// The optimizer as of gcc 5.2.0, may not reliably ensure a single value
+/// returned by boost::system::system_category() within a translation unit
+/// when building the header only version of the boost error handling.
+/// See Trac #4243 for more details. For now we turn off optimization for
+/// header only builds the under the suspect GCC versions.
+///
+/// The issue arises from in-lining the above function, which returns a
+/// reference to a local static variable, system_category_const.  This leads
+/// to situations where a construct such as the following:
+///
+/// {{{
+///    if (ec == boost::asio::error::would_block
+///       || ec == boost::asio::error::try_again)
+///      return false;
+/// }}}
+///
+/// which involve implicit conversion of enumerates to error_code instances
+/// to not evaluate correctly.  During the implicit conversion the error_code
+/// instances may be assigned differing values error_code:m_cat. This
+/// causes two instances of error_code which should have been equal to
+/// to not be equal.
+///
+/// The problem disappears if either error handling code is not built header
+/// only as this results in a single definition of system_category() supplied
+/// by libboost_system; or the error handling code is not optimized.
+///
+/// We're doing the test here, rather than in configure to guard against the
+/// user supplying the header only flag via environment variables.
+///
+/// We opened bugs with GNU and BOOST:
+///
+/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69789
+/// https://svn.boost.org/trac/boost/ticket/11989
+///
+/// @todo Version 6.0 will need to be tested.
+///
+/// As of 2016-08-19, the version 5.4.0 from Ubuntu 16.04 is affected. Updated
+/// the check to cover everything that is not 6.0, hoping that 6.0 solves the
+/// problem.
+
+#define GNU_CC_VERSION (__GNUC__ * 10000 \
+                     + __GNUC_MINOR__ * 100 \
+                     + __GNUC_PATCHLEVEL__)
+
+#if (defined(__GNUC__) && \
+    ((GNU_CC_VERSION >= 50200) && (GNU_CC_VERSION < 60000)) \
+    && defined(BOOST_ERROR_CODE_HEADER_ONLY))
+#pragma GCC push_options
+#pragma GCC optimize ("O0")
+#include <boost/asio.hpp>
+#pragma GCC pop_options
+#else
+#include <boost/asio.hpp>
+#endif
+
+#endif // ASIO_WRAPPER_H
diff --git a/src/lib/asiolink/asiolink/asiolink.dox b/src/lib/asiolink/asiolink/asiolink.dox
new file mode 100644 (file)
index 0000000..0c01b12
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ @page libasiolink libkea-asiolink - Kea Boost ASIO Library
+
+@section asiolinkUtilities Boost ASIO Utilities
+
+The asiolink library (libkea-asiolink) encapsulates Boost ASIO tools:
+
+ - addr utilities: prefix (IOAddress and length pair) tools.
+
+ - dummy I/O callback.
+
+ - interval timer.
+
+ - I/O acceptor: asynchronous server ASIO socket (base class).
+
+ - I/O address: ASIO IP address.
+
+ - I/O ASIO socket (derived from I/O socket).
+
+ - I/O endpoint: ASIO IP endpoint (abstraction of a socket address).
+
+ - I/O error: @c isc::asiolink::IOError exception declaration.
+
+ - I/O service: ASIO I/O service (named I/O context in recent versions).
+
+ - I/O socket: ASIO I/O socket base class.
+
+ - TCP acceptor: TCP derivation of I/O acceptor.
+
+ - TCP endpoint: TCP derivation of I/O endpoint.
+
+ - TCP socket: TCP derivation of I/O socket.
+
+ - UDP endpoint: UDP derivation of I/O endpoint.
+
+ - UDP socket: UDP derivation of I/O socket.
+
+ - Unix domain socket: Unix socket (AF_LOCAL) derivation of I/O socket.
+
+ - Unix domain acceptor: Unix socket (AF_LOCAL) derivation of I/O acceptor.
+
+ - Unix domain endpoint: Unix socket (AF_LOCAL) derivation of I/O endpoint.
+
+@section asiolinkMTConsiderations Multi-Threading Consideration for Boost ASIO Utilities
+
+By default Boost ASIO utilities are not thread safe even if Boost ASIO tools
+themselves are. When there is no state and the encapsulation is direct
+the thread safety property is preserved. Exceptions to the by default
+no thread safe are:
+
+ - I/O address (direct encapsulation) is thread safe.
+
+ - interval timer setup and cancel methods are thread safe.
+
+*/
diff --git a/src/lib/asiolink/asiolink/asiolink.h b/src/lib/asiolink/asiolink/asiolink.h
new file mode 100644 (file)
index 0000000..dfe31a2
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ASIOLINK_H
+#define ASIOLINK_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file.  In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_error.h>
+
+/// \namespace asiolink
+/// \brief A wrapper interface for the ASIO library.
+///
+/// The \c asiolink namespace is used to define a set of wrapper interfaces
+/// for the ASIO library.
+///
+/// Kea uses the Boost version of ASIO in its header-only mode,
+/// i.e., does not require a separate library object to be linked, and thus
+/// lowers the bar for introduction.
+///
+/// But the advantage comes with its own costs: since the header-only version
+/// includes more definitions in public header files, it tends to trigger
+/// more compiler warnings for our own sources, and, depending on the
+/// compiler options, may make the build fail.
+///
+/// We also found it may be tricky to use ASIO and standard C++ libraries
+/// in a single translation unit, i.e., a .cc file: depending on the order
+/// of including header files, ASIO may or may not work on some platforms.
+///
+/// This wrapper interface is intended to centralize these
+/// problematic issues in a single sub module.  Other Kea modules should
+/// simply include \c asiolink.h and use the wrapper API instead of
+/// including ASIO header files and using ASIO-specific classes directly.
+///
+/// This wrapper may be used for other IO libraries if and when we want to
+/// switch, but generality for that purpose is not the primary goal of
+/// this module.  The resulting interfaces are thus straightforward mapping
+/// to the ASIO counterparts.
+///
+/// One obvious drawback of this approach is performance overhead
+/// due to the additional layer.  We should eventually evaluate the cost
+/// of the wrapper abstraction in benchmark tests. Another drawback is
+/// that the wrapper interfaces don't provide all features of ASIO
+/// (at least for the moment).  We should also re-evaluate the
+/// maintenance overhead of providing necessary wrappers as we develop
+/// more.
+///
+/// On the other hand, we may be able to exploit the wrapper approach to
+/// simplify the interfaces (by limiting the usage) and unify performance
+/// optimization points.
+///
+/// As for optimization, we may want to provide a custom allocator for
+/// the placeholder of callback handlers:
+/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
+
+#endif // ASIOLINK_H
diff --git a/src/lib/asiolink/asiolink/botan_tls.cc b/src/lib/asiolink/asiolink/botan_tls.cc
new file mode 100644 (file)
index 0000000..33ad20c
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file botan_tls.cc Botan fake implementation of the TLS API.
+
+#include <config.h>
+
+#ifdef WITH_BOTAN
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+
+namespace isc {
+namespace asiolink {
+
+TlsContext::TlsContext(TlsRole role)
+    : TlsContextBase(role), cert_required_(true) {
+}
+
+void
+TlsContext::setCertRequired(bool cert_required) {
+    cert_required_ = cert_required;
+}
+
+bool
+TlsContext::getCertRequired() const {
+    return (cert_required_);
+}
+
+void
+TlsContext::loadCaFile(const std::string&) {
+    isc_throw(NotImplemented, "Botan TLS is not yet supported");
+}
+
+void
+TlsContext::loadCaPath(const std::string&) {
+    isc_throw(NotImplemented, "loadCaPath is not implemented by Botan");
+}
+
+void
+TlsContext::loadCertFile(const std::string&) {
+    isc_throw(NotImplemented, "Botan TLS is not yet supported");
+}
+
+void
+TlsContext::loadKeyFile(const std::string&) {
+    isc_throw(NotImplemented, "Botan TLS is not yet supported");
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_BOTAN
diff --git a/src/lib/asiolink/asiolink/botan_tls.h b/src/lib/asiolink/asiolink/botan_tls.h
new file mode 100644 (file)
index 0000000..1ad6af6
--- /dev/null
@@ -0,0 +1,177 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef BOTAN_TLS_H
+#define BOTAN_TLS_H
+
+/// @file botan_tls.h Botan fake implementation of the TLS API.
+
+#ifdef WITH_BOTAN
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/common_tls.h>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Botan TLS context.
+class TlsContext : public TlsContextBase {
+public:
+
+    /// @brief Destructor.
+    virtual ~TlsContext() { }
+
+    /// @brief Create a fresh context.
+    ///
+    /// @param role The TLS role client or server.
+    explicit TlsContext(TlsRole role);
+
+    /// @brief Get the peer certificate requirement mode.
+    ///
+    /// @return True if peer certificates are required, false if they
+    /// are optional.
+    virtual bool getCertRequired() const;
+
+protected:
+    /// @brief Set the peer certificate requirement mode.
+    ///
+    /// @param cert_required True if peer certificates are required,
+    /// false if they are optional.
+    virtual void setCertRequired(bool cert_required);
+
+    /// @brief Load the trust anchor aka certification authority.
+    ///
+    /// @param ca_file The certificate file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCaFile(const std::string& ca_file);
+
+    /// @brief Load the trust anchor aka certification authority.
+    ///
+    /// @param ca_path The certificate directory name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCaPath(const std::string& ca_path);
+
+    /// @brief Load the certificate file.
+    ///
+    /// @param cert_file The certificate file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCertFile(const std::string& cert_file);
+
+    /// @brief Load the private key from a file.
+    ///
+    /// @param key_file The private key file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadKeyFile(const std::string& key_file);
+
+    /// @brief Cached cert_required value.
+    bool cert_required_;
+
+    /// @brief Allow access to protected methods by the base class.
+    friend class TlsContextBase;
+};
+
+/// @brief The type of Botan TLS streams (in fact pure TCP streams).
+typedef boost::asio::ip::tcp::socket TlsStreamImpl;
+
+/// @brief TlsStreamBase constructor.
+///
+/// @tparam Callback The type of callbacks.
+/// @tparam TlsStreamImpl The type of underlying TLS streams.
+/// @param service I/O Service object used to manage the stream.
+/// @param context Pointer to the TLS context.
+/// @note The caller must not provide a null pointer to the TLS context.
+template <typename Callback, typename TlsStreamImpl>
+TlsStreamBase<Callback, TlsStreamImpl>::
+TlsStreamBase(IOService& service, TlsContextPtr context)
+    : TlsStreamImpl(service.get_io_service()), role_(context->getRole()) {
+}
+
+/// @brief Botan fake TLS stream.
+///
+/// @tparam callback The callback.
+template <typename Callback>
+class TlsStream : public TlsStreamBase<Callback, TlsStreamImpl> {
+public:
+
+    /// @brief Type of the base.
+    typedef TlsStreamBase<Callback, TlsStreamImpl> Base;
+
+    /// @brief Constructor.
+    ///
+    /// @param service I/O Service object used to manage the stream.
+    /// @param context Pointer to the TLS context.
+    /// @note The caller must not provide a null pointer to the TLS context.
+    TlsStream(IOService& service, TlsContextPtr context)
+        : Base(service, context) {
+    }
+
+    /// @brief Destructor.
+    virtual ~TlsStream() { }
+
+    /// @brief TLS Handshake.
+    ///
+    /// @param callback Callback object.
+    virtual void handshake(Callback& callback) {
+        isc_throw(NotImplemented, "Botan TLS is not yet supported");
+    }
+
+    /// @brief TLS shutdown.
+    ///
+    /// @param callback Callback object.
+    virtual void shutdown(Callback& callback) {
+        isc_throw(NotImplemented, "Botan TLS is not yet supported");
+    }
+
+    /// @brief Clear the TLS state.
+    virtual void clear() {
+        isc_throw(NotImplemented, "Botan TLS is not yet supported");
+    }
+
+    /// @brief Return the commonName part of the subjectName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    /// RFC 3280 provides as a commonName example "Susan Housley",
+    /// to idea to give access to this come from the Role Based
+    /// Access Control experiment.
+    ///
+    ///
+    /// @return The commonName part of the subjectName or the empty string.
+    std::string getSubject() {
+        return ("");
+    }
+
+    /// @brief Return the commonName part of the issuerName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    /// The issuerName is the subjectName of the signing certificate
+    /// (the issue in PKIX terms). The idea is to encode a group as
+    /// members of an intermediate certification authority.
+    ///
+    ///
+    /// @return The commonName part of the issuerName or the empty string.
+    std::string getIssuer() {
+        return ("");
+    }
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_BOTAN
+
+#endif // BOTAN_TLS_H
diff --git a/src/lib/asiolink/asiolink/common_tls.cc b/src/lib/asiolink/asiolink/common_tls.cc
new file mode 100644 (file)
index 0000000..ee0d0bc
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file common_tls.cc Common part of implementations of the TLS API.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+
+#include <sys/stat.h>
+
+using namespace isc::cryptolink;
+
+namespace { // anonymous namespace
+
+// C++17 has this function but Kea is still C++11 so provide it.
+bool
+isDir(const std::string& name) {
+    struct stat stats;
+    if (::stat(name.c_str(), &stats) < 0) {
+        return (false);
+    }
+    return ((stats.st_mode & S_IFMT) == S_IFDIR);
+}
+
+} // end of namespace
+
+namespace isc {
+namespace asiolink {
+
+void
+TlsContextBase::configure(TlsContextPtr& context,
+                          TlsRole role,
+                          const std::string& ca_file,
+                          const std::string& cert_file,
+                          const std::string& key_file,
+                          bool cert_required) {
+    try {
+        context.reset(new TlsContext(role));
+        if (isDir(ca_file)) {
+            context->loadCaPath(ca_file);
+        } else {
+            context->loadCaFile(ca_file);
+        }
+        context->loadCertFile(cert_file);
+        context->loadKeyFile(key_file);
+        context->setCertRequired(cert_required);
+    } catch (...) {
+        context.reset();
+        throw;
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/common_tls.h b/src/lib/asiolink/asiolink/common_tls.h
new file mode 100644 (file)
index 0000000..aac01b6
--- /dev/null
@@ -0,0 +1,188 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef COMMON_TLS_H
+#define COMMON_TLS_H
+
+/// @file common_tls.h Common TLS API.
+
+// Verify that this file was not directly included.
+#ifndef CRYPTO_TLS_H
+#error crypto_tls.h must be included in place of common_tls.h
+#endif
+
+#include <cryptolink/cryptolink.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Client and server roles.
+enum TlsRole { CLIENT, SERVER };
+
+/// @brief Forward declaration of backend TLS context.
+class TlsContext;
+
+/// @brief The type of shared pointers to TlsContext objects.
+///
+/// @note Not clear we need shared pointers but they cover more use cases...
+typedef boost::shared_ptr<TlsContext> TlsContextPtr;
+
+/// @brief TLS context base class.
+class TlsContextBase : private boost::noncopyable {
+public:
+    /// @brief Destructor.
+    virtual ~TlsContextBase() { }
+
+    /// @brief Create a fresh context.
+    ///
+    /// @param role The TLS role client or server.
+    explicit TlsContextBase(TlsRole role) : role_(role) { }
+
+    /// @brief Returns the role.
+    TlsRole getRole() const {
+        return (role_);
+    }
+
+    /// @note No need for a role set method.
+
+    /// @brief Configure.
+    ///
+    /// @param context The TLS context to configure.
+    /// @param role The TLS role client or server.
+    /// @param ca_file The certificate file or directory name.
+    /// @param cert_file The certificate file name.
+    /// @param key_file The private key file name.
+    /// @param cert_required True if peer certificates are required,
+    /// false if they are optional.
+    static void configure(TlsContextPtr& context,
+                          TlsRole role,
+                          const std::string& ca_file,
+                          const std::string& cert_file,
+                          const std::string& key_file,
+                          bool cert_required);
+
+    /// @brief Get the peer certificate requirement mode.
+    ///
+    /// @return True if peer certificates are required, false if they
+    /// are optional.
+    virtual bool getCertRequired() const = 0;
+
+protected:
+    /// @brief Set the peer certificate requirement mode.
+    ///
+    /// @param cert_required True if peer certificates are required,
+    /// false if they are optional.
+    virtual void setCertRequired(bool cert_required) = 0;
+
+    /// @brief Load the trust anchor aka certification authority.
+    ///
+    /// @param ca_file The certificate file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCaFile(const std::string& ca_file) = 0;
+
+    /// @brief Load the trust anchor aka certification authority.
+    ///
+    /// @param ca_path The certificate directory name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCaPath(const std::string& ca_path) = 0;
+
+    /// @brief Load the certificate file.
+    ///
+    /// @param cert_file The certificate file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCertFile(const std::string& cert_file) = 0;
+
+    /// @brief Load the private key from a file.
+    ///
+    /// @param key_file The private key file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadKeyFile(const std::string& key_file) = 0;
+
+public:
+    /// @brief The role i.e. client or server.
+    TlsRole role_;
+};
+
+/// @brief TLS stream base class.
+///
+/// @tparam Callback The type of callbacks.
+/// @tparam TlsStreamImpl The type of underlying TLS streams.
+template <typename Callback, typename TlsStreamImpl>
+class TlsStreamBase : public TlsStreamImpl {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param service I/O Service object used to manage the stream.
+    /// @param context Pointer to the TLS context.
+    /// @note The caller must not provide a null pointer to the TLS context.
+    TlsStreamBase(IOService& service, TlsContextPtr context);
+
+    /// @brief Destructor.
+    virtual ~TlsStreamBase() { }
+
+    /// @brief Returns the role.
+    TlsRole getRole() const {
+        return (role_);
+    }
+
+    /// @brief TLS Handshake.
+    ///
+    /// @param callback Callback object.
+    virtual void handshake(Callback& callback) = 0;
+
+    /// @brief TLS shutdown.
+    ///
+    /// @param callback Callback object.
+    virtual void shutdown(Callback& callback) = 0;
+
+    /// @brief Clear the TLS state.
+    ///
+    /// @note For some unit tests only.
+    virtual void clear() = 0;
+
+    /// @brief Return the commonName part of the subjectName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    /// RFC 3280 provides as a commonName example "Susan Housley",
+    /// to idea to give access to this come from the Role Based
+    /// Access Control experiment.
+    ///
+    /// @return The commonName part of the subjectName or the empty string.
+    virtual std::string getSubject() = 0;
+
+    /// @brief Return the commonName part of the issuerName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    /// The issuerName is the subjectName of the signing certificate
+    /// (the issue in PKIX terms). The idea is to encode a group as
+    /// members of an intermediate certification authority.
+    ///
+    /// @return The commonName part of the issuerName or the empty string.
+    virtual std::string getIssuer() = 0;
+
+    /// @brief The role i.e. client or server.
+    TlsRole role_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // COMMON_TLS_H
diff --git a/src/lib/asiolink/asiolink/crypto_tls.h b/src/lib/asiolink/asiolink/crypto_tls.h
new file mode 100644 (file)
index 0000000..256c476
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CRYPTO_TLS_H
+#define CRYPTO_TLS_H
+
+/// @file crypto_tls.h TLS API.
+
+// Verify that config.h was included.
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before crypto_tls.h
+#endif
+
+// Include different versions.
+#include <asiolink/botan_tls.h>
+#include <asiolink/openssl_tls.h>
+
+// Verify that one version matched.
+#ifndef COMMON_TLS_H
+#error no TLS backend was found
+#endif
+
+#endif // CRYPTO_TLS_H
diff --git a/src/lib/asiolink/asiolink/dummy_io_cb.h b/src/lib/asiolink/asiolink/dummy_io_cb.h
new file mode 100644 (file)
index 0000000..795fad9
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DUMMY_IO_CB_H
+#define DUMMY_IO_CB_H
+
+#include <iostream>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/asio/error.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief Asynchronous I/O Completion Callback
+///
+/// The two socket classes (UDPSocket and TCPSocket) require that the I/O
+/// completion callback function have an operator() method with the appropriate
+/// signature.  The classes are templates, any class with that method and
+/// signature can be passed as the callback object - there is no need for a
+/// base class defining the interface.  However, some users of the socket
+/// classes do not use the asynchronous I/O operations, yet have to supply a
+/// template parameter.  This is the reason for this class - it is the dummy
+/// template parameter.
+
+class DummyIOCallback {
+public:
+
+    /// \brief Asynchronous I/O callback method
+    ///
+    /// Should never be called, as this class is a convenience class provided
+    /// for instances where a socket is required but it is known that no
+    /// asynchronous operations will be carried out.
+    void operator()(boost::system::error_code) {
+        // If the function is called, there is a serious logic error in
+        // the program (this class should not be used as the callback
+        // class).  As the asiolink module is too low-level for logging
+        // errors, throw an exception.
+        isc_throw(isc::Unexpected,
+                  "DummyIOCallback::operator() must not be called");
+    }
+
+    /// \brief Asynchronous I/O callback method
+    ///
+    /// Should never be called, as this class is a convenience class provided
+    /// for instances where a socket is required but it is known that no
+    /// asynchronous operations will be carried out.
+    void operator()(boost::system::error_code, size_t) {
+        // If the function is called, there is a serious logic error in
+        // the program (this class should not be used as the callback
+        // class).  As the asiolink module is too low-level for logging
+        // errors, throw an exception.
+        isc_throw(isc::Unexpected,
+                  "DummyIOCallback::operator() must not be called");
+    }
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // DUMMY_IO_CB_H
diff --git a/src/lib/asiolink/asiolink/interval_timer.cc b/src/lib/asiolink/asiolink/interval_timer.cc
new file mode 100644 (file)
index 0000000..2cda9b0
--- /dev/null
@@ -0,0 +1,201 @@
+// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <atomic>
+#include <functional>
+#include <mutex>
+
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// This class holds a call back function of asynchronous operations.
+/// To ensure the object is alive while an asynchronous operation refers
+/// to it, we use shared_ptr and enable_shared_from_this.
+/// The object will be destructed in case IntervalTimer has been destructed
+/// and no asynchronous operation refers to it.
+/// Please follow the link to get an example:
+/// http://think-async.com/asio/asio-1.4.8/doc/asio/tutorial/tutdaytime3.html#asio.tutorial.tutdaytime3.the_tcp_connection_class
+class IntervalTimerImpl :
+    public boost::enable_shared_from_this<IntervalTimerImpl>,
+    public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service The IO service used to handle events.
+    IntervalTimerImpl(IOService& io_service);
+
+    /// @brief Destructor.
+    ~IntervalTimerImpl();
+
+    /// @brief Setup function to register callback and start timer.
+    ///
+    /// @param cbfunc The callback function registered on timer.
+    /// @param interval The interval used to start the timer.
+    /// @param interval_mode The interval mode used by the timer.
+    void setup(const IntervalTimer::Callback& cbfunc, const long interval,
+               const IntervalTimer::Mode& interval_mode
+               = IntervalTimer::REPEATING);
+
+    /// @brief Callback function which calls the registerd callback.
+    ///
+    /// @param error The error code retrieved from the timer.
+    void callback(const boost::system::error_code& error);
+
+    /// @brief Cancel timer.
+    void cancel() {
+        lock_guard<mutex> lk (mutex_);
+        timer_.cancel();
+        interval_ = 0;
+    }
+
+    /// @brief Get the timer interval.
+    ///
+    /// @return The timer interval.
+    long getInterval() const { return (interval_); }
+
+private:
+
+    /// @brief Update function to update timer_ when it expires.
+    ///
+    /// Should be called in a thread safe context.
+    void update();
+
+    /// @brief The callback function to call when timer_ expires.
+    IntervalTimer::Callback cbfunc_;
+
+    /// @brief The interval in milliseconds.
+    std::atomic<long> interval_;
+
+    /// @brief The asio timer.
+    boost::asio::deadline_timer timer_;
+
+    /// @brief Controls how the timer behaves after expiration.
+    IntervalTimer::Mode mode_;
+
+    /// @brief Mutex to protect the internal state.
+    std::mutex mutex_;
+
+    /// @brief Invalid interval value.
+    ///
+    /// @ref interval_ will be set to this value in destructor in order to
+    /// detect use-after-free type of bugs.
+    static const long INVALIDATED_INTERVAL = -1;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+    interval_(0), timer_(io_service.get_io_service()),
+    mode_(IntervalTimer::REPEATING) {
+}
+
+IntervalTimerImpl::~IntervalTimerImpl() {
+    interval_ = INVALIDATED_INTERVAL;
+}
+
+void
+IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
+                         const long interval,
+                         const IntervalTimer::Mode& mode) {
+    // Interval should not be less than 0.
+    if (interval < 0) {
+        isc_throw(isc::BadValue, "Interval should not be less than or "
+                                 "equal to 0");
+    }
+    // Call back function should not be empty.
+    if (!cbfunc) {
+        isc_throw(isc::InvalidParameter, "Callback function is empty");
+    }
+
+    lock_guard<mutex> lk(mutex_);
+    cbfunc_ = cbfunc;
+    interval_ = interval;
+    mode_ = mode;
+
+    // Set initial expire time.
+    // At this point the timer is not running yet and will not expire.
+    // After calling IOService::run(), the timer will expire.
+    update();
+}
+
+void
+IntervalTimerImpl::update() {
+    try {
+        // Update expire time to (current time + interval_).
+        timer_.expires_from_now(boost::posix_time::millisec(long(interval_)));
+        // Reset timer.
+        // Pass a function bound with a shared_ptr to this.
+        timer_.async_wait(std::bind(&IntervalTimerImpl::callback,
+                                    shared_from_this(),
+                                    ph::_1)); //error
+    } catch (const boost::system::system_error& e) {
+        isc_throw(isc::Unexpected, "Failed to update timer: " << e.what());
+    } catch (const boost::bad_weak_ptr&) {
+        // Can't happen. It means a severe internal bug.
+    }
+}
+
+void
+IntervalTimerImpl::callback(const boost::system::error_code& ec) {
+    if (interval_ == INVALIDATED_INTERVAL) {
+        isc_throw(isc::BadValue, "Interval internal state");
+    }
+    if (interval_ == 0 || ec) {
+        // timer has been canceled. Do nothing.
+    } else {
+        {
+            lock_guard<mutex> lk(mutex_);
+            // If we should repeat, set next expire time.
+            if (mode_ == IntervalTimer::REPEATING) {
+                update();
+            }
+        }
+
+        // Invoke the call back function.
+        cbfunc_();
+    }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) :
+    impl_(new IntervalTimerImpl(io_service)) {
+}
+
+IntervalTimer::~IntervalTimer() {
+    // Cancel the timer to make sure cbfunc_() will not be called any more.
+    cancel();
+}
+
+void
+IntervalTimer::setup(const Callback& cbfunc, const long interval,
+                     const IntervalTimer::Mode& mode) {
+    return (impl_->setup(cbfunc, interval, mode));
+}
+
+void
+IntervalTimer::cancel() {
+    impl_->cancel();
+}
+
+long
+IntervalTimer::getInterval() const {
+    return (impl_->getInterval());
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/interval_timer.h b/src/lib/asiolink/asiolink/interval_timer.h
new file mode 100644 (file)
index 0000000..5dc8b71
--- /dev/null
@@ -0,0 +1,142 @@
+// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ASIOLINK_INTERVAL_TIMER_H
+#define ASIOLINK_INTERVAL_TIMER_H 1
+
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+#include <asiolink/io_service.h>
+
+namespace isc {
+namespace asiolink {
+
+class IntervalTimerImpl;
+
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c boost::asio::deadline_timer class.
+///
+/// This class is implemented to use \c boost::asio::deadline_timer as interval
+/// timer.
+///
+/// \c setup() sets a timer to expire on (now + interval), a call back
+/// function, and an interval mode.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when it expires.
+///
+/// The function calls the call back function set by \c setup() and if the
+/// the interval mode indicates a repeating interval, will reschedule the
+/// timer to expire in (now + interval) milliseconds.
+///
+/// The type of call back function is \c void(void).
+///
+/// The call back function will not be called if the instance of this class is
+/// destroyed before the timer is expired.
+///
+/// Sample code:
+/// \code
+///  void function_to_call_back() {
+///      // this function will be called periodically
+///  }
+///  int interval_in_milliseconds = 1000;
+///  IOService io_service;
+///
+///  IntervalTimer intervalTimer(io_service);
+///  intervalTimer.setup(function_to_call_back, interval_in_milliseconds);
+///  io_service.run();
+/// \endcode
+class IntervalTimer {
+public:
+    /// \name The type of timer callback function
+    typedef std::function<void()> Callback;
+
+    /// \brief Defines possible timer modes used to setup a timer.
+    /// - REPEATING - Timer will reschedule itself after each expiration
+    /// - ONE_SHOT - Timer will expire after one interval and not reschedule.
+    enum Mode
+    {
+        REPEATING,
+        ONE_SHOT
+    };
+
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IntervalTimer(const IntervalTimer& source);
+    IntervalTimer& operator=(const IntervalTimer& source);
+public:
+    /// \brief The constructor with \c IOService.
+    ///
+    /// This constructor may throw a standard exception if
+    /// memory allocation fails inside the method.
+    /// This constructor may also throw \c boost::system::system_error.
+    ///
+    /// \param io_service A reference to an instance of IOService
+    IntervalTimer(IOService& io_service);
+
+    /// \brief The destructor.
+    ///
+    /// This destructor never throws an exception.
+    ///
+    /// On the destruction of this class the timer will be canceled
+    /// inside \c boost::asio::deadline_timer.
+    ~IntervalTimer();
+    //@}
+
+    /// \brief Register timer callback function and interval.
+    ///
+    /// This function sets callback function and interval in milliseconds.
+    /// Timer will actually start after calling \c IOService::run().
+    ///
+    /// \param cbfunc A reference to a function \c void(void) to call back
+    /// when the timer is expired (should not be an empty functor)
+    /// \param interval Interval in milliseconds (greater than 0)
+    /// \param mode Determines if the timer will automatically reschedule after
+    /// each expiration (the default) or behave as a one-shot which will run
+    /// for a single interval and not reschedule.
+    ///
+    /// Note: IntervalTimer will not pass \c boost::system::error_code to
+    /// call back function. In case the timer is canceled, the function
+    /// will not be called.
+    ///
+    /// \throw isc::InvalidParameter cbfunc is empty
+    /// \throw isc::BadValue interval is less than or equal to 0
+    /// \throw isc::Unexpected internal runtime error
+    void setup(const Callback& cbfunc, const long interval,
+                    const Mode& mode = REPEATING);
+
+    /// Cancel the timer.
+    ///
+    /// If the timer has been set up, this method cancels any asynchronous
+    /// events waiting on the timer and stops the timer itself.
+    /// If the timer has already been canceled, this method effectively does
+    /// nothing.
+    ///
+    /// This method never throws an exception.
+    void cancel();
+
+    /// Return the timer interval.
+    ///
+    /// This method returns the timer interval in milliseconds if it's running;
+    /// if the timer has been canceled it returns 0.
+    ///
+    /// This method never throws an exception.
+    long getInterval() const;
+
+private:
+    boost::shared_ptr<IntervalTimerImpl> impl_;
+};
+
+typedef boost::shared_ptr<isc::asiolink::IntervalTimer> IntervalTimerPtr;
+
+} // namespace asiolink
+} // namespace isc
+#endif // ASIOLINK_INTERVAL_TIMER_H
diff --git a/src/lib/asiolink/asiolink/io_acceptor.h b/src/lib/asiolink/asiolink/io_acceptor.h
new file mode 100644 (file)
index 0000000..eab0d83
--- /dev/null
@@ -0,0 +1,136 @@
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_ACCEPTOR_H
+#define IO_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Base class for acceptor services in Kea.
+///
+/// This is a wrapper class for ASIO acceptor service. Classes implementing
+/// services for specific protocol types should derive from this class.
+///
+/// Acceptor is an IO object which accepts incoming connections into a socket
+/// object. This socket is then used for data transmission from the client
+/// to server and back. The acceptor is continued to be used to accept new
+/// connections while the accepted connection is active.
+///
+/// @tparam ProtocolType ASIO protocol type, e.g. stream_protocol
+/// @tparam CallbackType Callback function type which should have the following
+/// signature: @c void(const boost::system::error_code&).
+template<typename ProtocolType, typename CallbackType>
+class IOAcceptor : public IOSocket {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service Reference to the IO service.
+    explicit IOAcceptor(IOService& io_service)
+        : IOSocket(),
+          acceptor_(new typename ProtocolType::acceptor(io_service.get_io_service())) {
+    }
+
+    /// @brief Destructor.
+    virtual ~IOAcceptor() { }
+
+    /// @brief Returns file descriptor of the underlying socket.
+    virtual int getNative() const {
+#if BOOST_VERSION < 106600
+        return (acceptor_->native());
+#else
+        return (acceptor_->native_handle());
+#endif
+    }
+
+    /// @brief Opens acceptor socket given the endpoint.
+    ///
+    /// @param endpoint Reference to the endpoint object defining local
+    /// acceptor endpoint.
+    ///
+    /// @tparam EndpointType Endpoint type.
+    template<typename EndpointType>
+    void open(const EndpointType& endpoint) {
+        acceptor_->open(endpoint.getASIOEndpoint().protocol());
+    }
+
+    /// @brief Binds socket to an endpoint.
+    ///
+    /// @param endpoint Reference to the endpoint object defining local
+    /// acceptor endpoint.
+    ///
+    /// @tparam EndpointType Endpoint type.
+    template<typename EndpointType>
+    void bind(const EndpointType& endpoint) {
+        acceptor_->bind(endpoint.getASIOEndpoint());
+    }
+
+    /// @brief Sets socket option.
+    ///
+    /// @param socket_option Reference to the object encapsulating an option to
+    /// be set for the socket.
+    /// @tparam SettableSocketOption Type of the object encapsulating socket option
+    /// being set.
+    template<typename SettableSocketOption>
+    void setOption(const SettableSocketOption& socket_option) {
+        acceptor_->set_option(socket_option);
+    }
+
+    /// @brief Starts listening new connections.
+    void listen() {
+        acceptor_->listen();
+    }
+
+    /// @brief Checks if the acceptor is open.
+    ///
+    /// @return true if acceptor is open.
+    bool isOpen() const {
+        return (acceptor_->is_open());
+    }
+
+    /// @brief Closes the acceptor.
+    void close() const {
+        acceptor_->close();
+    }
+
+protected:
+
+    /// @brief Asynchronously accept new connection.
+    ///
+    /// This method accepts new connection into the specified socket. When the
+    /// new connection arrives or an error occurs the specified callback
+    /// function is invoked.
+    ///
+    /// @param socket Socket into which connection should be accepted.
+    /// @param callback Callback function to be invoked when the new connection
+    /// arrives.
+    /// @tparam SocketType Socket type, e.g. @ref UnixDomainSocket. It must
+    /// implement @c getASIOSocket method.
+    template<typename SocketType>
+    void asyncAcceptInternal(const SocketType& socket,
+                             const CallbackType& callback) {
+        acceptor_->async_accept(socket.getASIOSocket(), callback);
+    }
+
+
+    /// @brief Underlying ASIO acceptor implementation.
+    boost::shared_ptr<typename ProtocolType::acceptor> acceptor_;
+
+};
+
+
+} // end of namespace asiolink
+} // end of isc
+
+#endif // IO_ACCEPTOR_H
diff --git a/src/lib/asiolink/asiolink/io_address.cc b/src/lib/asiolink/asiolink/io_address.cc
new file mode 100644 (file)
index 0000000..7f7134d
--- /dev/null
@@ -0,0 +1,189 @@
+// Copyright (C) 2010-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/static_assert.hpp>
+// moved to container_hash on recent boost versions (backward compatible)
+#include <boost/functional/hash.hpp>
+
+#include <unistd.h>             // for some IPC/network system calls
+#include <stdint.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+using namespace boost::asio;
+using boost::asio::ip::udp;
+using boost::asio::ip::tcp;
+
+using namespace std;
+
+namespace isc {
+namespace asiolink {
+
+// XXX: we cannot simply construct the address in the initialization list,
+// because we'd like to throw our own exception on failure.
+IOAddress::IOAddress(const std::string& address_str) {
+    boost::system::error_code err;
+    asio_address_ = ip::address::from_string(address_str, err);
+    if (err) {
+        isc_throw(IOError, "Failed to convert string to address '"
+                  << address_str << "': " << err.message());
+    }
+}
+
+IOAddress::IOAddress(const boost::asio::ip::address& asio_address) :
+    asio_address_(asio_address)
+{}
+
+IOAddress::IOAddress(uint32_t v4address):
+    asio_address_(boost::asio::ip::address_v4(v4address)) {
+
+}
+
+string
+IOAddress::toText() const {
+    return (asio_address_.to_string());
+}
+
+IOAddress
+IOAddress::fromBytes(short family, const uint8_t* data) {
+    if (data == NULL) {
+        isc_throw(BadValue, "NULL pointer received.");
+    } else
+    if ( (family != AF_INET) && (family != AF_INET6) ) {
+        isc_throw(BadValue, "Invalid family type. Only AF_INET and AF_INET6"
+                  << "are supported");
+    }
+
+    BOOST_STATIC_ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN);
+    char addr_str[INET6_ADDRSTRLEN];
+    inet_ntop(family, data, addr_str, INET6_ADDRSTRLEN);
+    return IOAddress(string(addr_str));
+}
+
+std::vector<uint8_t>
+IOAddress::toBytes() const {
+    if (asio_address_.is_v4()) {
+        const boost::asio::ip::address_v4::bytes_type bytes4 =
+            asio_address_.to_v4().to_bytes();
+        return (std::vector<uint8_t>(bytes4.begin(), bytes4.end()));
+    }
+
+    // Not V4 address, so must be a V6 address (else we could never construct
+    // this object).
+    const boost::asio::ip::address_v6::bytes_type bytes6 =
+        asio_address_.to_v6().to_bytes();
+    return (std::vector<uint8_t>(bytes6.begin(), bytes6.end()));
+}
+
+short
+IOAddress::getFamily() const {
+    if (asio_address_.is_v4()) {
+        return (AF_INET);
+    } else {
+        return (AF_INET6);
+    }
+}
+
+bool
+IOAddress::isV6LinkLocal() const {
+    if (!asio_address_.is_v6()) {
+        return (false);
+    }
+    return (asio_address_.to_v6().is_link_local());
+}
+
+bool
+IOAddress::isV6Multicast() const {
+    if (!asio_address_.is_v6()) {
+        return (false);
+    }
+    return (asio_address_.to_v6().is_multicast());
+}
+
+uint32_t
+IOAddress::toUint32() const {
+    if (asio_address_.is_v4()) {
+        return (asio_address_.to_v4().to_ulong());
+    } else {
+        isc_throw(BadValue, "Can't convert " << toText()
+                  << " address to IPv4.");
+    }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const IOAddress& address) {
+    os << address.toText();
+    return (os);
+}
+
+IOAddress
+IOAddress::subtract(const IOAddress& a, const IOAddress& b) {
+    if (a.getFamily() != b.getFamily()) {
+        isc_throw(BadValue, "Both addresses have to be the same family");
+    }
+    if (a.isV4()) {
+        // Subtracting v4 is easy. We have a conversion function to uint32_t.
+        return (IOAddress(a.toUint32() - b.toUint32()));
+    } else {
+        // v6 is more involved.
+
+        // Let's extract the raw data first.
+        vector<uint8_t> a_vec = a.toBytes();
+        vector<uint8_t> b_vec = b.toBytes();
+
+        // ... and prepare the result
+        vector<uint8_t> result(V6ADDRESS_LEN,0);
+
+        // Carry is a boolean, but to avoid its frequent casting, let's
+        // use uint8_t. Also, some would prefer to call it borrow, but I prefer
+        // carry. Somewhat relevant discussion here:
+        // http://en.wikipedia.org/wiki/Carry_flag#Carry_flag_vs._Borrow_flag
+        uint8_t carry = 0;
+
+        // Now perform subtraction with borrow.
+        for (int i = a_vec.size() - 1; i >= 0; --i) {
+            result[i] = a_vec[i] - b_vec[i] - carry;
+            carry = (a_vec[i] < b_vec[i] + carry);
+        }
+
+        return (fromBytes(AF_INET6, &result[0]));
+    }
+}
+
+IOAddress
+IOAddress::increase(const IOAddress& addr) {
+    std::vector<uint8_t> packed(addr.toBytes());
+
+    // Start increasing the least significant byte
+    for (int i = packed.size() - 1; i >= 0; --i) {
+        // if we haven't overflowed (0xff -> 0x0), than we are done
+        if (++packed[i] != 0) {
+            break;
+        }
+    }
+
+    return (IOAddress::fromBytes(addr.getFamily(), &packed[0]));
+}
+
+size_t
+hash_value(const IOAddress& address) {
+    if (address.isV4()) {
+        boost::hash<uint32_t> hasher;
+        return (hasher(address.toUint32()));
+    } else {
+        boost::hash<std::vector<uint8_t> > hasher;
+        return (hasher(address.toBytes()));
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/io_address.h b/src/lib/asiolink/asiolink/io_address.h
new file mode 100644 (file)
index 0000000..fb1d67b
--- /dev/null
@@ -0,0 +1,318 @@
+// Copyright (C) 2010-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_ADDRESS_H
+#define IO_ADDRESS_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file.  In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h>             // for some network system calls
+#include <stdint.h>             // for uint32_t
+#include <boost/asio/ip/address.hpp>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+    /// Defines length of IPv6 address (in binary format).
+    static constexpr size_t V6ADDRESS_LEN = 16;
+
+    /// Defines length of IPv4 address (in binary format).
+    static constexpr size_t V4ADDRESS_LEN = 4;
+
+    /// @brief Maximum size of an IPv4 address represented as a text string. 12
+    ///     digits plus 3 full stops (dots).
+    static constexpr size_t V4ADDRESS_TEXT_MAX_LEN = 15u;
+
+    /// @brief Maximum size of an IPv6 address represented as a text string. 32
+    ///     hexadecimal characters written in 8 groups of four, plus 7 colon
+    ///     separators.
+    static constexpr size_t V6ADDRESS_TEXT_MAX_LEN = 39u;
+
+/// \brief The \c IOAddress class represents an IP addresses (version
+/// agnostic)
+///
+/// This class is a wrapper for the ASIO \c ip::address class.
+class IOAddress {
+public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// This class is copyable.  We use default versions of copy constructor
+    /// and the assignment operator.
+    /// We use the default destructor.
+    //@{
+    /// \brief Constructor from string.
+    ///
+    /// This constructor converts a textual representation of IPv4 and IPv6
+    /// addresses into an IOAddress object.
+    /// If \c address_str is not a valid representation of any type of
+    /// address, an exception of class \c IOError will be thrown.
+    /// This constructor allocates memory for the object, and if that fails
+    /// a corresponding standard exception will be thrown.
+    ///
+    /// \param address_str Textual representation of address.
+    IOAddress(const std::string& address_str);
+
+    /// \brief Constructor from an ASIO \c ip::address object.
+    ///
+    /// This constructor is intended to be used within the wrapper
+    /// implementation; user applications of the wrapper API won't use it.
+    ///
+    /// This constructor never throws an exception.
+    ///
+    /// \param asio_address The ASIO \c ip::address to be converted.
+    IOAddress(const boost::asio::ip::address& asio_address);
+    //@}
+
+    /// @brief Constructor for ip::address_v4 object.
+    ///
+    /// This constructor is intended to be used when constructing
+    /// IPv4 address out of uint32_t type. Passed value must be in
+    /// network byte order
+    ///
+    /// @param v4address IPv4 address represented by uint32_t
+    IOAddress(uint32_t v4address);
+
+    /// \brief Convert the address to a string.
+    ///
+    /// This method is basically expected to be exception free, but
+    /// generating the string will involve resource allocation,
+    /// and if it fails the corresponding standard exception will be thrown.
+    ///
+    /// \return A string representation of the address.
+    std::string toText() const;
+
+    /// \brief Returns the address family
+    ///
+    /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
+    short getFamily() const;
+
+    /// \brief Convenience function to check for an IPv4 address
+    ///
+    /// \return true if the address is a V4 address
+    bool isV4() const {
+        return (asio_address_.is_v4());
+    }
+
+    /// \brief Convenience function to check if it is an IPv4 zero address.
+    ///
+    /// \return true if the address is the zero IPv4 address.
+    bool isV4Zero() const {
+        return (equals(IPV4_ZERO_ADDRESS()));
+    }
+
+    /// \brief Convenience function to check if it is an IPv4 broadcast
+    ///        address.
+    ///
+    /// \return true if the address is the broadcast IPv4 address.
+    bool isV4Bcast() const {
+        return (equals(IPV4_BCAST_ADDRESS()));
+    }
+
+    /// \brief Convenience function to check for an IPv6 address
+    ///
+    /// \return true if the address is a V6 address
+    bool isV6() const {
+        return (asio_address_.is_v6());
+    }
+
+    /// \brief Convenience function to check if it is an IPv4 zero address.
+    ///
+    /// \return true if the address is the zero IPv4 address.
+    bool isV6Zero() const {
+        return (equals(IPV6_ZERO_ADDRESS()));
+    }
+
+    /// \brief checks whether and address is IPv6 and is link-local
+    ///
+    /// \return true if the address is IPv6 link-local, false otherwise
+    bool isV6LinkLocal() const;
+
+    /// \brief checks whether and address is IPv6 and is multicast
+    ///
+    /// \return true if the address is IPv6 multicast, false otherwise
+    bool isV6Multicast() const;
+
+    /// \brief Creates an address from over wire data.
+    ///
+    /// \param family AF_INET for IPv4 or AF_INET6 for IPv6.
+    /// \param data pointer to first char of data
+    ///
+    /// \return Created IOAddress object
+    static IOAddress fromBytes(short family, const uint8_t* data);
+
+    /// \brief Return address as set of bytes
+    ///
+    /// \return Contents of the address as a set of bytes in network-byte
+    ///         order.
+    std::vector<uint8_t> toBytes() const;
+
+    /// \brief Compare addresses for equality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return true if addresses are equal, false if not.
+    bool equals(const IOAddress& other) const {
+        return (asio_address_ == other.asio_address_);
+    }
+
+    /// \brief Compare addresses for equality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return true if addresses are equal, false if not.
+    bool operator==(const IOAddress& other) const {
+        return equals(other);
+    }
+
+    /// \brief Compare addresses for inequality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return false if addresses are equal, true if not.
+    bool nequals(const IOAddress& other) const {
+        return (!equals(other));
+    }
+
+    /// \brief Checks if one address is smaller than the other
+    ///
+    /// \param other Address to compare against.
+    bool operator<(const IOAddress& other) const {
+        return (asio_address_ < other.asio_address_);
+    }
+
+    /// \brief Checks if one address is smaller or equal than the other
+    ///
+    /// \param other Address to compare against.
+    bool operator<=(const IOAddress& other) const {
+        return (asio_address_ <= other.asio_address_);
+    }
+
+    /// \brief Compare addresses for inequality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return false if addresses are equal, true if not.
+    bool operator!=(const IOAddress& other) const {
+        return (nequals(other));
+    }
+
+    /// @brief Subtracts one address from another (a - b)
+    ///
+    /// Treats addresses as integers and subtracts them. For example:
+    /// @code
+    /// 192.0.2.5 - 192.0.2.0 = 0.0.0.5
+    /// fe80::abcd - fe80:: = ::abcd
+    /// @endcode
+    ///
+    /// It is possible to subtract greater from lesser address, e.g.
+    /// 192.168.56.10 - 192.168.67.20, but please do understand that
+    /// the address space is a finite field in mathematical sense, so
+    /// you may end up with a result that is greater then any of the
+    /// addresses you specified. Also, subtraction is not commutative,
+    /// so a - b != b - a.
+    ///
+    /// This operation is essential for calculating the number of
+    /// leases in a pool, where we need to calculate (max - min).
+    /// @throw BadValue if addresses are of different family
+    /// @param a address to be subtracted from
+    /// @param b address to be subtracted
+    /// @return IOAddress object that represents the difference
+    static IOAddress subtract(const IOAddress& a, const IOAddress& b);
+
+    /// @brief Returns an address increased by one
+    ///
+    /// This method works for both IPv4 and IPv6 addresses. For example,
+    /// increase 192.0.2.255 will become 192.0.3.0.
+    ///
+    /// Address space is a finite field in the mathematical sense, so keep
+    /// in mind that the address space "loops". 255.255.255.255 increased
+    /// by one gives 0.0.0.0. The same is true for maximum value of IPv6
+    /// (all 1's) looping to ::.
+    ///
+    /// @todo Determine if we have a use-case for increasing the address
+    /// by more than one. Increase by one is used in AllocEngine. This method
+    /// could take extra parameter that specifies the value by which the
+    /// address should be increased.
+    ///
+    /// @param addr address to be increased
+    /// @return address increased by one
+    static IOAddress
+    increase(const IOAddress& addr);
+
+    /// \brief Converts IPv4 address to uint32_t
+    ///
+    /// Will throw BadValue exception if that is not IPv4
+    /// address.
+    ///
+    /// \return uint32_t that represents IPv4 address in
+    ///         network byte order
+    uint32_t toUint32() const;
+
+    /// @name Methods returning @c IOAddress objects encapsulating typical addresses.
+    ///
+    //@{
+    /// @brief Returns an address set to all zeros.
+    static const IOAddress& IPV4_ZERO_ADDRESS() {
+        static IOAddress address(0);
+        return (address);
+    }
+
+    /// @brief Returns a "255.255.255.255" broadcast address.
+    static const IOAddress& IPV4_BCAST_ADDRESS() {
+        static IOAddress address(0xFFFFFFFF);
+        return (address);
+    }
+
+    /// @brief Returns an IPv6 zero address.
+    static const IOAddress& IPV6_ZERO_ADDRESS() {
+        static IOAddress address("::");
+        return (address);
+    }
+
+    //@}
+
+private:
+    boost::asio::ip::address asio_address_;
+};
+
+/// \brief Insert the IOAddress as a string into stream.
+///
+/// This method converts the \c address into a string and inserts it
+/// into the output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described
+/// in ostream::operator<< but applied to \c IOAddress objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param address The \c IOAddress object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const IOAddress& address);
+
+/// \brief Hash the IOAddress.
+///
+/// This method allows boost multi-index hashed indexes on IOAddresses.
+/// It follows the requirement with equality: if two addresses are equal
+/// their hashes are equal, if two addresses are not equal their hashes
+/// are almost surely not equal.
+///
+/// \param address A \c IOAddress to hash.
+/// \return The hash of the IOAddress.
+size_t hash_value(const IOAddress& address);
+
+} // namespace asiolink
+} // namespace isc
+#endif // IO_ADDRESS_H
diff --git a/src/lib/asiolink/asiolink/io_asio_socket.h b/src/lib/asiolink/asiolink/io_asio_socket.h
new file mode 100644 (file)
index 0000000..29ca97f
--- /dev/null
@@ -0,0 +1,381 @@
+// Copyright (C) 2010-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_ASIO_SOCKET_H
+#define IO_ASIO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file.  In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <config.h>
+
+#include <unistd.h>             // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <asiolink/io_error.h>
+#include <asiolink/io_socket.h>
+
+// We want to use coroutine.hpp from the system's boost headers if possible.
+// However, very old Boost versions (provided by RHEL 7 or CentOS 7) didn't have
+// this header. So we can resort to our bundled version, but only if necessary.
+#ifndef HAVE_BOOST_ASIO_COROUTINE_HPP
+#include <ext/coroutine/coroutine.hpp>
+#else
+#include <boost/asio/coroutine.hpp>
+#endif
+
+namespace isc {
+namespace asiolink {
+
+/// \brief Socket not open
+///
+/// Thrown on an attempt to do read/write to a socket that is not open.
+class SocketNotOpen : public IOError {
+public:
+    SocketNotOpen(const char* file, size_t line, const char* what) :
+        IOError(file, line, what) {}
+};
+
+/// \brief Error setting socket options
+///
+/// Thrown if attempt to change socket options fails.
+class SocketSetError : public IOError {
+public:
+    SocketSetError(const char* file, size_t line, const char* what) :
+        IOError(file, line, what) {}
+};
+
+/// \brief Buffer overflow
+///
+/// Thrown if an attempt is made to receive into an area beyond the end of
+/// the receive data buffer.
+class BufferOverflow : public IOError {
+public:
+    BufferOverflow(const char* file, size_t line, const char* what) :
+        IOError(file, line, what) {}
+};
+
+/// Forward declaration of an IOEndpoint
+class IOEndpoint;
+
+
+/// \brief I/O Socket with asynchronous operations
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// This is the basic IOSocket with additional operations - open, send, receive
+/// and close.  Depending on how the asiolink code develops, it may be a
+/// temporary class: its main use is to add the template parameter needed for
+/// the derived classes UDPSocket and TCPSocket but without changing the
+/// signature of the more basic IOSocket class.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it.  Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class IOAsioSocket : public IOSocket {
+
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOAsioSocket(const IOAsioSocket<C>& source);
+    IOAsioSocket& operator=(const IOAsioSocket<C>& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    IOAsioSocket() {}
+public:
+    /// The destructor.
+    virtual ~IOAsioSocket() {}
+    //@}
+
+    /// \brief Return the "native" representation of the socket.
+    ///
+    /// In practice, this is the file descriptor of the socket for UNIX-like
+    /// systems so the current implementation simply uses \c int as the type of
+    /// the return value. We may have to need revisit this decision later.
+    ///
+    /// In general, the application should avoid using this method; it
+    /// essentially discloses an implementation specific "handle" that can
+    /// change the internal state of the socket (consider what would happen if
+    /// the application closes it, for example).  But we sometimes need to
+    /// perform very low-level operations that requires the native
+    /// representation.  Passing the file descriptor to a different process is
+    /// one example.  This method is provided as a necessary evil for such
+    /// limited purposes.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The native representation of the socket.  This is the socket
+    ///         file descriptor for UNIX-like systems.
+    virtual int getNative() const = 0;
+
+    /// \brief Return the transport protocol of the socket.
+    ///
+    /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+    /// \c IPPROTO_TCP for TCP sockets.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return \c IPPROTO_UDP for UDP sockets, \c IPPROTO_TCP for TCP sockets
+    virtual int getProtocol() const = 0;
+
+    /// \brief Is Open() synchronous?
+    ///
+    /// On a TCP socket, an "open" operation is a call to the socket's "open()"
+    /// method followed by a connection to the remote system: it is an
+    /// asynchronous operation.  On a UDP socket, it is just a call to "open()"
+    /// and completes synchronously.
+    ///
+    /// For TCP, signalling of the completion of the operation is done by
+    /// by calling the callback function in the normal way.  This could be done
+    /// for UDP (by posting en event on the event queue); however, that will
+    /// incur additional overhead in the most common case.  So we give the
+    /// caller the choice for calling this open() method synchronously or
+    /// asynchronously.
+    ///
+    /// Owing to the way that the stackless coroutines are implemented, we need
+    /// to know _before_ executing the "open" function whether or not it is
+    /// asynchronous.  So this method is called to provide that information.
+    ///
+    /// (The reason there is a need to know is because the call to open() passes
+    /// in the state of the coroutine at the time the call is made.  On an
+    /// asynchronous I/O, we need to set the state to point to the statement
+    /// after the call to open() _before_ we pass the coroutine to the open()
+    /// call.  Unfortunately, the macros that set the state of the coroutine
+    /// also yield control - which we don't want to do if the open is
+    /// synchronous.  Hence we need to know before we make the call to open()
+    /// whether that call will complete asynchronously.)
+    virtual bool isOpenSynchronous() const = 0;
+
+    /// \brief Open AsioSocket
+    ///
+    /// Opens the socket for asynchronous I/O.  The open will complete
+    /// synchronously on UCP or asynchronously on TCP (in which case a callback
+    /// will be queued).
+    ///
+    /// \param endpoint Pointer to the endpoint object.  This is ignored for
+    ///        a UDP socket (the target is specified in the send call), but
+    ///        should be of type TCPEndpoint for a TCP connection.
+    /// \param callback I/O Completion callback, called when the operation has
+    ///        completed, but only if the operation was asynchronous. (It is
+    ///        ignored on a UDP socket.)
+    virtual void open(const IOEndpoint* endpoint, C& callback) = 0;
+
+    /// \brief Send Asynchronously
+    ///
+    /// This corresponds to async_send_to() for UDP sockets and async_send()
+    /// for TCP.  In both cases an endpoint argument is supplied indicating the
+    /// target of the send - this is ignored for TCP.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param endpoint Target of the send
+    /// \param callback Callback object.
+    virtual void asyncSend(const void* data, size_t length,
+                           const IOEndpoint* endpoint, C& callback) = 0;
+
+    /// \brief Receive Asynchronously
+    ///
+    /// This corresponds to async_receive_from() for UDP sockets and
+    /// async_receive() for TCP.  In both cases, an endpoint argument is
+    /// supplied to receive the source of the communication.  For TCP it will
+    /// be filled in with details of the connection.
+    ///
+    /// \param data Buffer to receive incoming message
+    /// \param length Length of the data buffer
+    /// \param offset Offset into buffer where data is to be put.  Although the
+    ///        offset could be implied by adjusting "data" and "length"
+    ///        appropriately, using this argument allows data to be specified as
+    ///        "const void*" - the overhead of converting it to a pointer to a
+    ///        set of bytes is hidden away here.
+    /// \param endpoint Source of the communication
+    /// \param callback Callback object
+    virtual void asyncReceive(void* data, size_t length, size_t offset,
+                              IOEndpoint* endpoint, C& callback) = 0;
+
+    /// \brief Processes received data
+    ///
+    /// In the IOFetch code, data is received into a staging buffer before being
+    /// copied into the target buffer.  (This is because (a) we don't know how
+    /// much data we will be receiving, so don't know how to size the output
+    /// buffer and (b) TCP data is preceded by a two-byte count field that needs
+    /// to be discarded before being returned to the user.)
+    ///
+    /// An additional consideration is that TCP data is not received in one
+    /// I/O - it may take a number of I/Os - each receiving any non-zero number
+    /// of bytes - to read the entire message.
+    ///
+    /// So the IOFetch code has to loop until it determines that all the data
+    /// has been read.  This is where this method comes in.  It has several
+    /// functions:
+    ///
+    /// - It checks if the received data is complete.
+    /// - If data is not complete, decides if the next set of data is to go into
+    ///   the start of the staging buffer or at some offset into it.  (This
+    ///   simplifies the case we could have in a TCP receive where the two-byte
+    ///   count field is received in one-byte chunks: we put off interpreting
+    ///   the count until we have all of it.  The alternative - copying the
+    ///   data to the output buffer and interpreting the count from there -
+    ///   would require moving the data in the output buffer by two bytes before
+    ///   returning it to the caller.)
+    /// - Copies data from the staging buffer into the output buffer.
+    ///
+    /// This functionality mainly applies to TCP receives.  For UDP, all the
+    /// data is received in one I/O, so this just copies the data into the
+    /// output buffer.
+    ///
+    /// \param staging Pointer to the start of the staging buffer.
+    /// \param length Amount of data in the staging buffer.
+    /// \param cumulative Amount of data received before the staging buffer is
+    ///        processed (this includes the TCP count field if appropriate).
+    ///        The value should be set to zero before the receive loop is
+    ///        entered, and it will be updated by this method as required.
+    /// \param offset Offset into the staging buffer where the next read should
+    ///        put the received data.  It should be set to zero before the first
+    ///        call and may be updated by this method.
+    /// \param expected Expected amount of data to be received.  This is
+    ///        really the TCP count field and is set to that value when enough
+    ///        of a TCP message is received.  It should be initialized to -1
+    ///        before the first read is executed.
+    /// \param outbuff Output buffer.  Data in the staging buffer may be copied
+    ///        to this output buffer in the call.
+    ///
+    /// \return true if the receive is complete, false if another receive is
+    ///         needed.  This is always true for UDP, but for TCP involves
+    ///         checking the amount of data received so far against the amount
+    ///         expected (as indicated by the two-byte count field).  If this
+    ///         method returns false, another read should be queued and data
+    ///         should be read into the staging buffer at offset given by the
+    ///         "offset" parameter.
+    virtual bool processReceivedData(const void* staging, size_t length,
+                                     size_t& cumulative, size_t& offset,
+                                     size_t& expected,
+                                     isc::util::OutputBufferPtr& outbuff) = 0;
+
+    /// \brief Cancel I/O On AsioSocket
+    virtual void cancel() = 0;
+
+    /// \brief Close socket
+    virtual void close() = 0;
+};
+
+
+/// \brief The \c DummyAsioSocket class is a concrete derived class of
+/// \c IOAsioSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOAsioSocket object without involving system resource
+/// allocation such as real network sockets.
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class DummyAsioSocket : public IOAsioSocket<C> {
+private:
+    DummyAsioSocket(const DummyAsioSocket<C>& source);
+    DummyAsioSocket& operator=(const DummyAsioSocket<C>& source);
+public:
+    /// \brief Constructor from the protocol number.
+    ///
+    /// The protocol must validly identify a standard network protocol.
+    /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+    ///
+    /// \param protocol The network protocol number for the socket.
+    DummyAsioSocket(const int protocol) : protocol_(protocol) {}
+
+    /// \brief A dummy derived method of \c IOAsioSocket::getNative().
+    ///
+    /// \return Always returns -1 as the object is not associated with a real
+    /// (native) socket.
+    virtual int getNative() const { return (-1); }
+
+    /// \brief A dummy derived method of \c IOAsioSocket::getProtocol().
+    ///
+    /// \return Protocol socket was created with
+    virtual int getProtocol() const { return (protocol_); }
+
+
+    /// \brief Is socket opening synchronous?
+    ///
+    /// \return true - it is for this class.
+    bool isOpenSynchronous() const {
+        return true;
+    }
+
+    /// \brief Open AsioSocket
+    ///
+    /// A call that is a no-op on UDP sockets, this opens a connection to the
+    /// system identified by the given endpoint.
+    /// The endpoint and callback are unused.
+    ///
+    /// \return false indicating that the operation completed synchronously.
+    virtual bool open(const IOEndpoint*, C&) {
+        return (false);
+    }
+
+    /// \brief Send Asynchronously
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    /// This is unused.
+    virtual void asyncSend(const void*, size_t, const IOEndpoint*, C&) {
+    }
+
+    /// \brief Receive Asynchronously
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    /// The parameters are unused.
+    virtual void asyncReceive(void*, size_t, size_t, IOEndpoint*, C&) {
+    }
+
+    /// \brief Checks if the data received is complete.
+    ///
+    /// The parameters are unused.
+    /// \return Always true
+    virtual bool receiveComplete(const void*, size_t, size_t&, size_t&,
+                                 size_t&, isc::util::OutputBufferPtr&)
+    {
+        return (true);
+    }
+
+
+    /// \brief Cancel I/O On AsioSocket
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    virtual void cancel() {
+    }
+
+    /// \brief Close socket
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    virtual void close() {
+    }
+
+private:
+    const int protocol_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_ASIO_SOCKET_H
diff --git a/src/lib/asiolink/asiolink/io_endpoint.cc b/src/lib/asiolink/asiolink/io_endpoint.cc
new file mode 100644 (file)
index 0000000..d236aa8
--- /dev/null
@@ -0,0 +1,68 @@
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/udp_endpoint.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <unistd.h>             // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+using namespace std;
+
+namespace isc {
+namespace asiolink {
+
+const IOEndpoint*
+IOEndpoint::create(const int protocol, const IOAddress& address,
+                   const unsigned short port)
+{
+    if (protocol == IPPROTO_UDP) {
+        return (new UDPEndpoint(address, port));
+    } else if (protocol == IPPROTO_TCP) {
+        return (new TCPEndpoint(address, port));
+    }
+    isc_throw(IOError,
+              "IOEndpoint creation attempt for unsupported protocol: " <<
+              protocol);
+}
+
+bool
+IOEndpoint::operator==(const IOEndpoint& other) const {
+    return (getProtocol() == other.getProtocol() &&
+            getPort() == other.getPort() &&
+            getFamily() == other.getFamily() &&
+            getAddress() == other.getAddress());
+}
+
+bool
+IOEndpoint::operator!=(const IOEndpoint& other) const {
+    return (!operator==(other));
+}
+
+ostream&
+operator<<(ostream& os, const IOEndpoint& endpoint) {
+    if (endpoint.getFamily() == AF_INET6) {
+        os << "[" << endpoint.getAddress() << "]";
+    } else {
+        // In practice this should be AF_INET, but it's not guaranteed by
+        // the interface.  We'll use the result of textual address
+        // representation opaquely.
+        os << endpoint.getAddress();
+    }
+    os << ":" << boost::lexical_cast<string>(endpoint.getPort());
+    return (os);
+}
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/io_endpoint.h b/src/lib/asiolink/asiolink/io_endpoint.h
new file mode 100644 (file)
index 0000000..8421a30
--- /dev/null
@@ -0,0 +1,183 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_ENDPOINT_H
+#define IO_ENDPOINT_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file.  In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+
+# include <ostream>
+
+#include <unistd.h>             // for some network system calls
+
+#include <sys/socket.h>         // for sockaddr
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c IOEndpoint class is an abstract base class to represent
+/// a communication endpoint.
+///
+/// This class is a wrapper for the ASIO endpoint classes such as
+/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation.  User applications only get access to concrete
+/// \c IOEndpoint objects via the abstract interfaces.
+class IOEndpoint {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOEndpoint(const IOEndpoint& source);
+    IOEndpoint& operator=(const IOEndpoint& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    IOEndpoint() {}
+public:
+    /// The destructor.
+    virtual ~IOEndpoint() {}
+    //@}
+
+    /// \brief Returns the address of the endpoint.
+    ///
+    /// This method returns an IOAddress object corresponding to \c this
+    /// endpoint.
+    ///
+    /// Note that the return value is a real object, not a reference or
+    /// a pointer.
+    ///
+    /// This is aligned with the interface of the ASIO counterpart:
+    /// the \c address() method of \c ip::xxx::endpoint classes returns
+    /// an \c ip::address object.
+    ///
+    /// This also means handling the address of an endpoint using this method
+    /// can be expensive.  If the address information is necessary in a
+    /// performance sensitive context and there's a more efficient interface
+    /// for that purpose, it's probably better to avoid using this method.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A copy of \c IOAddress object corresponding to the endpoint.
+    virtual IOAddress getAddress() const = 0;
+
+    /// \brief Returns the port of the endpoint.
+    virtual uint16_t getPort() const = 0;
+
+    /// \brief Returns the protocol number of the endpoint (TCP, UDP...)
+    virtual short getProtocol() const = 0;
+
+    /// \brief Returns the address family of the endpoint.
+    virtual short getFamily() const = 0;
+
+    /// \brief Returns the address of the endpoint in the form of sockaddr
+    /// structure.
+    ///
+    /// The actual instance referenced by the returned value of this method
+    /// is of per address family structure: For IPv4 (AF_INET), it's
+    /// \c sockaddr_in; for IPv6 (AF_INET6), it's \c sockaddr_in6.
+    /// The corresponding port and address members of the underlying structure
+    /// will be set in the network byte order.
+    ///
+    /// This method is "redundant" in that all information to construct the
+    /// \c sockaddr is available via the other "get" methods.
+    /// It is still defined for performance sensitive applications that need
+    /// to get the address information, such as for address based access
+    /// control at a high throughput.  Internally it is implemented with
+    /// minimum overhead such as data copy (this is another reason why this
+    /// method returns a reference).
+    ///
+    /// As a tradeoff, this method is more fragile; it assumes that the
+    /// underlying ASIO implementation stores the address information in
+    /// the form of \c sockaddr and it can be accessed in an efficient way.
+    /// This is the case as of this writing, but if the underlying
+    /// implementation changes this method may become much slower or its
+    /// interface may have to be changed, too.
+    ///
+    /// It is therefore discouraged for normal applications to use this
+    /// method.  Unless the application is very performance sensitive, it
+    /// should use the other "get" method to retrieve specific information
+    /// of the endpoint.
+    ///
+    /// The returned reference is only valid while the corresponding
+    /// \c IOEndpoint is valid.  Once it's destructed the reference will
+    /// become invalid.
+    ///
+    /// \exception None
+    /// \return Reference to a \c sockaddr structure corresponding to the
+    /// endpoint.
+    virtual const struct sockaddr& getSockAddr() const = 0;
+
+    bool operator==(const IOEndpoint& other) const;
+    bool operator!=(const IOEndpoint& other) const;
+
+    /// \brief A polymorphic factory of endpoint from address and port.
+    ///
+    /// This method creates a new instance of (a derived class of)
+    /// \c IOEndpoint object that identifies the pair of given address
+    /// and port.
+    /// The appropriate derived class is chosen based on the specified
+    /// transport protocol.  If the \c protocol doesn't specify a protocol
+    /// supported in this implementation, an exception of class \c IOError
+    /// will be thrown.
+    ///
+    /// Memory for the created object will be dynamically allocated.  It's
+    /// the caller's responsibility to \c delete it later.
+    /// If resource allocation for the new object fails, a corresponding
+    /// standard exception will be thrown.
+    ///
+    /// \param protocol The transport protocol used for the endpoint.
+    /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
+    /// \param address The (IP) address of the endpoint.
+    /// \param port The transport port number of the endpoint
+    /// \return A pointer to a newly created \c IOEndpoint object.
+    static const IOEndpoint* create(const int protocol,
+                                    const IOAddress& address,
+                                    const unsigned short port);
+};
+
+/// \brief Insert the \c IOEndpoint as a string into stream.
+///
+/// This method converts \c endpoint into a string and inserts it into the
+/// output stream \c os.
+///
+/// This method converts the address and port of the endpoint in the textual
+/// format that other Kea modules would use in logging, i.e.,
+/// - For IPv6 address: [&lt;address&gt;]:port (e.g., [2001:db8::5300]:53)
+/// - For IPv4 address: &lt;address&gt;:port (e.g., 192.0.2.53:5300)
+///
+/// If it's neither IPv6 nor IPv4, it converts the endpoint into text in the
+/// same format as that for IPv4, although in practice such a case is not
+/// really expected.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param endpoint A reference to an \c IOEndpoint object output by the
+/// operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const IOEndpoint& endpoint);
+} // namespace asiolink
+} // namespace isc
+#endif // IO_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/asiolink/io_error.h b/src/lib/asiolink/asiolink/io_error.h
new file mode 100644 (file)
index 0000000..692070c
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+#ifndef IO_ERROR_H
+#define IO_ERROR_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief An exception that is thrown if an error occurs within the IO
+/// module.  This is mainly intended to be a wrapper exception class for
+/// ASIO specific exceptions.
+class IOError : public isc::Exception {
+public:
+    IOError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_ERROR_H
diff --git a/src/lib/asiolink/asiolink/io_service.cc b/src/lib/asiolink/asiolink/io_service.cc
new file mode 100644 (file)
index 0000000..0574c5f
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+
+#include <unistd.h>             // for some IPC/network system calls
+#include <netinet/in.h>
+#include <boost/shared_ptr.hpp>
+#include <sys/socket.h>
+
+namespace isc {
+namespace asiolink {
+
+namespace {
+// A trivial wrapper for std::function.  SunStudio doesn't seem to be capable
+// of handling a std::function object if directly passed to
+// io_service::post().
+class CallbackWrapper {
+public:
+
+    /// \brief Constructor
+    CallbackWrapper(const std::function<void()>& callback) :
+        callback_(callback) {}
+
+    /// \brief Function operator
+    void operator()() {
+        callback_();
+    }
+
+private:
+
+    /// \brief The callback function
+    std::function<void()> callback_;
+};
+}
+
+class IOServiceImpl {
+private:
+    IOServiceImpl(const IOService& source);
+    IOServiceImpl& operator=(const IOService& source);
+public:
+    /// \brief The constructor
+    IOServiceImpl() :
+        io_service_(),
+        work_(new boost::asio::io_service::work(io_service_)) {
+    };
+
+    /// \brief The destructor.
+    ~IOServiceImpl() {};
+    //@}
+
+    /// \brief Start the underlying event loop.
+    ///
+    /// This method does not return control to the caller until
+    /// the \c stop() method is called via some handler.
+    void run() {
+        io_service_.run();
+    };
+
+    /// \brief Run the underlying event loop for a single event.
+    ///
+    /// This method return control to the caller as soon as the
+    /// first handler has completed.  (If no handlers are ready when
+    /// it is run, it will block until one is.)
+    void run_one() {
+        io_service_.run_one();
+    };
+
+    /// \brief Run the underlying event loop for a ready events.
+    ///
+    /// This method executes handlers for all ready events and returns.
+    /// It will return immediately if there are no ready events.
+    void poll() {
+        io_service_.poll();
+    };
+
+    /// \brief Stop the underlying event loop.
+    ///
+    /// This will return the control to the caller of the \c run() method.
+    void stop() { io_service_.stop();} ;
+
+    /// \brief Removes IO service work object to let it finish running
+    /// when all handlers have been invoked.
+    void stopWork() {
+        work_.reset();
+    }
+
+    /// \brief Return the native \c io_service object used in this wrapper.
+    ///
+    /// This is a short term work around to support other Kea modules
+    /// that share the same \c io_service with the authoritative server.
+    /// It will eventually be removed once the wrapper interface is
+    /// generalized.
+    boost::asio::io_service& get_io_service() { return io_service_; };
+
+    /// \brief Post a callback on the IO service
+    ///
+    /// \param callback The callback to be run on the IO service.
+    void post(const std::function<void ()>& callback) {
+        const CallbackWrapper wrapper(callback);
+        io_service_.post(wrapper);
+    }
+private:
+    boost::asio::io_service io_service_;
+    boost::shared_ptr<boost::asio::io_service::work> work_;
+};
+
+IOService::IOService() : io_impl_(new IOServiceImpl()) {
+}
+
+IOService::~IOService() {
+}
+
+void
+IOService::run() {
+    io_impl_->run();
+}
+
+void
+IOService::run_one() {
+    io_impl_->run_one();
+}
+
+void
+IOService::poll() {
+    io_impl_->poll();
+}
+
+void
+IOService::stop() {
+    io_impl_->stop();
+}
+
+void
+IOService::stopWork() {
+    io_impl_->stopWork();
+}
+
+boost::asio::io_service&
+IOService::get_io_service() {
+    return (io_impl_->get_io_service());
+}
+
+void
+IOService::post(const std::function<void ()>& callback) {
+    return (io_impl_->post(callback));
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/io_service.h b/src/lib/asiolink/asiolink/io_service.h
new file mode 100644 (file)
index 0000000..eeb668e
--- /dev/null
@@ -0,0 +1,106 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ASIOLINK_IO_SERVICE_H
+#define ASIOLINK_IO_SERVICE_H
+
+#include <boost/version.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+namespace boost {
+namespace asio {
+#if BOOST_VERSION < 106600
+    class io_service;
+#else
+    class io_context;
+    typedef io_context io_service;
+#endif
+}
+}
+
+namespace isc {
+namespace asiolink {
+
+class IOServiceImpl;
+
+/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
+/// class.
+///
+class IOService {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOService(const IOService& source);
+    IOService& operator=(const IOService& source);
+public:
+    /// \brief The constructor
+    IOService();
+    /// \brief The destructor.
+    ~IOService();
+    //@}
+
+    /// \brief Start the underlying event loop.
+    ///
+    /// This method does not return control to the caller until
+    /// the \c stop() method is called via some handler.
+    void run();
+
+    /// \brief Run the underlying event loop for a single event.
+    ///
+    /// This method return control to the caller as soon as the
+    /// first handler has completed.  (If no handlers are ready when
+    /// it is run, it will block until one is.)
+    void run_one();
+
+    /// \brief Run the underlying event loop for a ready events.
+    ///
+    /// This method executes handlers for all ready events and returns.
+    /// It will return immediately if there are no ready events.
+    void poll();
+
+    /// \brief Stop the underlying event loop.
+    ///
+    /// This will return the control to the caller of the \c run() method.
+    void stop();
+
+    /// \brief Removes IO service work object to let it finish running
+    /// when all handlers have been invoked.
+    void stopWork();
+
+    /// \brief Return the native \c io_service object used in this wrapper.
+    ///
+    /// This is a short term work around to support other Kea modules
+    /// that share the same \c io_service with the authoritative server.
+    /// It will eventually be removed once the wrapper interface is
+    /// generalized.
+    boost::asio::io_service& get_io_service();
+
+    /// \brief Post a callback to the end of the queue.
+    ///
+    /// Requests the callback be called sometime later. It is not guaranteed
+    /// by the underlying asio, but it can reasonably be expected the callback
+    /// is put to the end of the callback queue. It is not called from within
+    /// this function.
+    ///
+    /// It may be used to implement "background" work, for example (doing stuff
+    /// by small bits that are called from time to time).
+    void post(const std::function<void ()>& callback);
+
+private:
+    boost::shared_ptr<IOServiceImpl> io_impl_;
+};
+
+/// @brief Defines a smart pointer to an IOService instance.
+typedef boost::shared_ptr<IOService> IOServicePtr;
+
+} // namespace asiolink
+} // namespace isc
+#endif // ASIOLINK_IO_SERVICE_H
diff --git a/src/lib/asiolink/asiolink/io_service_signal.cc b/src/lib/asiolink/asiolink/io_service_signal.cc
new file mode 100644 (file)
index 0000000..95fc8a6
--- /dev/null
@@ -0,0 +1,124 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service_signal.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/asio/signal_set.hpp>
+#include <functional>
+
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implementation class of IOSignalSet.
+class IOSignalSetImpl : public boost::enable_shared_from_this<IOSignalSetImpl>,
+                        public boost::noncopyable {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param io_service the process IO service.
+    /// @param handler the signal handler.
+    IOSignalSetImpl(IOServicePtr io_service, IOSignalHandler handler);
+
+    /// @brief Destructor.
+    ~IOSignalSetImpl() = default;
+
+    /// @brief Install the callback on the IO service queue.
+    void install();
+
+    /// @brief Add a signal to the ASIO signal set.
+    ///
+    /// @param signum the signal number.
+    void add(int signum);
+
+    /// @brief Remove a signal from the ASIO signal set.
+    ///
+    /// @param signum the signal number.
+    void remove(int signum);
+
+private:
+    /// @brief the ASIO signal set.
+    boost::asio::signal_set signal_set_;
+
+    /// @brief the signal handler.
+    IOSignalHandler handler_;
+
+    /// @brief the callback (called on cancel or received signal).
+    ///
+    /// The callback is installed on the IO service queue and calls
+    /// the handler if the operation was not aborted.
+    void callback(const boost::system::error_code& ec, int signum);
+};
+
+IOSignalSetImpl::IOSignalSetImpl(IOServicePtr io_service,
+                                 IOSignalHandler handler)
+    : signal_set_(io_service->get_io_service()), handler_(handler) {
+}
+
+void
+IOSignalSetImpl::callback(const boost::system::error_code& ec, int signum) {
+    if (ec && ec.value() == boost::asio::error::operation_aborted) {
+        return;
+    }
+    install();
+    if (!ec && (signum > 0)) {
+        try {
+            handler_(signum);
+        } catch (const std::exception& ex) {
+        }
+    }
+}
+
+void
+IOSignalSetImpl::install() {
+    signal_set_.async_wait(std::bind(&IOSignalSetImpl::callback,
+                                     shared_from_this(), ph::_1, ph::_2));
+}
+
+void
+IOSignalSetImpl::add(int signum) {
+    try {
+        signal_set_.add(signum);
+    } catch (const boost::system::system_error& ex) {
+        isc_throw(isc::Unexpected,
+                  "Failed to add signal " << signum << ": " << ex.what());
+    }
+}
+
+void
+IOSignalSetImpl::remove(int signum) {
+    try {
+        signal_set_.remove(signum);
+    } catch (const boost::system::system_error& ex) {
+        isc_throw(isc::Unexpected,
+                  "Failed to remove signal " << signum << ": " << ex.what());
+    }
+}
+
+IOSignalSet::IOSignalSet(IOServicePtr io_service, IOSignalHandler handler) :
+    impl_(new IOSignalSetImpl(io_service, handler)) {
+    // It can throw but the error is fatal...
+    impl_->install();
+}
+
+void
+IOSignalSet::add(int signum) {
+    impl_->add(signum);
+}
+
+void
+IOSignalSet::remove(int signum) {
+    impl_->remove(signum);
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/io_service_signal.h b/src/lib/asiolink/asiolink/io_service_signal.h
new file mode 100644 (file)
index 0000000..b078ce4
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_SERVICE_SIGNAL_H
+#define IO_SERVICE_SIGNAL_H
+
+#include <asiolink/io_service.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Defines a handler function for an IOSignal.
+typedef std::function<void(int signum)> IOSignalHandler;
+
+class IOSignalSetImpl;
+
+/// @brief Implements an asynchronous "signal" for IOService driven processing
+///
+/// This class allows a OS signal such as SIGHUP to propagated to an IOService
+/// as a ready event with a callback using boost ASIO.
+class IOSignalSet {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param io_service IOService to which to send the signal.
+    /// @param handler Handler to call when a signal is received.
+    IOSignalSet(asiolink::IOServicePtr io_service, IOSignalHandler handler);
+
+    /// @brief Destructor.
+    ~IOSignalSet() = default;
+
+    /// @brief Add a signal to the list of signals to handle.
+    ///
+    /// @param signum Signal number.
+    /// @throw Unexpected on error.
+    void add(int signum);
+
+    /// @brief Remove a signal from the list of signals to handle.
+    ///
+    /// @param signum Signal number.
+    /// @throw Unexpected on error.
+    void remove(int signum);
+
+private:
+    /// @brief Pointer to the implementation.
+    boost::shared_ptr<IOSignalSetImpl> impl_;
+};
+
+/// @brief Defines a pointer to an IOSignalSet.
+typedef boost::shared_ptr<IOSignalSet> IOSignalSetPtr;
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_SERVICE_SIGNAL_H
diff --git a/src/lib/asiolink/asiolink/io_socket.cc b/src/lib/asiolink/asiolink/io_socket.cc
new file mode 100644 (file)
index 0000000..f7792a9
--- /dev/null
@@ -0,0 +1,57 @@
+// Copyright (C) 2010-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c DummySocket class is a concrete derived class of
+/// \c IOSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOSocket object without involving system resource
+/// allocation such as real network sockets.
+class DummySocket : public IOSocket {
+private:
+    DummySocket(const DummySocket& source);
+    DummySocket& operator=(const DummySocket& source);
+public:
+    /// \brief Constructor from the protocol number.
+    ///
+    /// The protocol must validly identify a standard network protocol.
+    /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+    ///
+    /// \param protocol The network protocol number for the socket.
+    DummySocket(const int protocol) : protocol_(protocol) {}
+
+    /// \brief A dummy derived method of \c IOSocket::getNative().
+    ///
+    /// This version of method always returns -1 as the object is not
+    /// associated with a real (native) socket.
+    virtual int getNative() const { return (-1); }
+
+    virtual int getProtocol() const { return (protocol_); }
+private:
+    const int protocol_;
+};
+
+IOSocket&
+IOSocket::getDummyUDPSocket() {
+    static DummySocket socket(IPPROTO_UDP);
+    return (socket);
+}
+
+IOSocket&
+IOSocket::getDummyTCPSocket() {
+    static DummySocket socket(IPPROTO_TCP);
+    return (socket);
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/io_socket.h b/src/lib/asiolink/asiolink/io_socket.h
new file mode 100644 (file)
index 0000000..9c9cee1
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_SOCKET_H
+#define IO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file.  In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h>             // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c IOSocket class is an abstract base class to represent
+/// various types of network sockets.
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation.  User applications only get access to concrete
+/// \c IOSocket objects via the abstract interfaces.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it.  Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+class IOSocket {
+public:
+
+    /// @name Types of objects encapsulating socket options.
+    //@{
+
+    /// @brief Represents SO_REUSEADDR socket option.
+    typedef boost::asio::socket_base::reuse_address ReuseAddress;
+
+    //@}
+
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOSocket(const IOSocket& source);
+    IOSocket& operator=(const IOSocket& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    IOSocket() {}
+public:
+    /// The destructor.
+    virtual ~IOSocket() {}
+    //@}
+
+    /// \brief Return the "native" representation of the socket.
+    ///
+    /// In practice, this is the file descriptor of the socket for
+    /// UNIX-like systems so the current implementation simply uses
+    /// \c int as the type of the return value.
+    /// We may have to need revisit this decision later.
+    ///
+    /// In general, the application should avoid using this method;
+    /// it essentially discloses an implementation specific "handle" that
+    /// can change the internal state of the socket (consider the
+    /// application closes it, for example).
+    /// But we sometimes need to perform very low-level operations that
+    /// requires the native representation.  Passing the file descriptor
+    /// to a different process is one example.
+    /// This method is provided as a necessary evil for such limited purposes.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The native representation of the socket.  This is the socket
+    /// file descriptor for UNIX-like systems.
+    virtual int getNative() const = 0;
+
+    /// \brief Return the transport protocol of the socket.
+    ///
+    /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+    /// \c IPPROTO_TCP for TCP sockets.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return IPPROTO_UDP for UDP sockets
+    /// \return IPPROTO_TCP for TCP sockets
+    virtual int getProtocol() const = 0;
+
+    /// \brief Return a non-usable "dummy" UDP socket for testing.
+    ///
+    /// This is a class method that returns a "mock" of UDP socket.
+    /// This is not associated with any actual socket, and its only
+    /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
+    /// The only feasible usage of this socket is for testing so that
+    /// the test code can prepare some "UDP data" even without opening any
+    /// actual socket.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A reference to an \c IOSocket object whose \c getProtocol()
+    /// returns \c IPPROTO_UDP.
+    static IOSocket& getDummyUDPSocket();
+
+    /// \brief Return a non-usable "dummy" TCP socket for testing.
+    ///
+    /// See \c getDummyUDPSocket().  This method is its TCP version.
+    ///
+    /// \return A reference to an \c IOSocket object whose \c getProtocol()
+    /// returns \c IPPROTO_TCP.
+    static IOSocket& getDummyTCPSocket();
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_SOCKET_H
diff --git a/src/lib/asiolink/asiolink/openssl_tls.cc b/src/lib/asiolink/asiolink/openssl_tls.cc
new file mode 100644 (file)
index 0000000..0ee5e8b
--- /dev/null
@@ -0,0 +1,114 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file openssl_tls.cc OpenSSL implementation of the TLS API.
+
+#include <config.h>
+
+#ifdef WITH_OPENSSL
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+
+#include <sys/stat.h>
+
+#include <openssl/opensslv.h>
+
+using namespace boost::asio;
+using namespace boost::asio::ssl;
+using namespace boost::system;
+using namespace isc::cryptolink;
+
+namespace isc {
+namespace asiolink {
+
+// Enforce TLS 1.2 when the generic TLS method is not available (i.e.
+// the boost version is older than 1.64.0).
+TlsContext::TlsContext(TlsRole role)
+    : TlsContextBase(role), cert_required_(true),
+#ifdef HAVE_GENERIC_TLS_METHOD
+      context_(context::method::tls)
+#else
+#ifdef HAVE_TLS_1_2_METHOD
+      context_(context::method::tlsv12)
+#else
+      context_(context::method::tlsv1)
+#endif
+#endif
+{
+    // Not leave the verify mode to OpenSSL default.
+    setCertRequired(true);
+}
+
+boost::asio::ssl::context&
+TlsContext::getContext() {
+    return (context_);
+}
+
+::SSL_CTX*
+TlsContext::getNativeContext() {
+    return (context_.native_handle());
+}
+
+void
+TlsContext::setCertRequired(bool cert_required) {
+    cert_required_ = cert_required;
+    error_code ec;
+    int mode = verify_peer | verify_fail_if_no_peer_cert;
+    if (!cert_required_) {
+        mode = verify_none;
+    }
+    context_.set_verify_mode(mode, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+bool
+TlsContext::getCertRequired() const {
+    return (cert_required_);
+}
+
+void
+TlsContext::loadCaFile(const std::string& ca_file) {
+    error_code ec;
+    context_.load_verify_file(ca_file, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+void
+TlsContext::loadCaPath(const std::string& ca_path) {
+    error_code ec;
+    context_.add_verify_path(ca_path, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+void
+TlsContext::loadCertFile(const std::string& cert_file) {
+    error_code ec;
+    context_.use_certificate_chain_file(cert_file, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+void
+TlsContext::loadKeyFile(const std::string& key_file) {
+    error_code ec;
+    context_.use_private_key_file(key_file, context::file_format::pem, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_OPENSSL
diff --git a/src/lib/asiolink/asiolink/openssl_tls.h b/src/lib/asiolink/asiolink/openssl_tls.h
new file mode 100644 (file)
index 0000000..9c224af
--- /dev/null
@@ -0,0 +1,238 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef OPENSSL_TLS_H
+#define OPENSSL_TLS_H
+
+/// @file openssl_tls.h OpenSSL implementation of the TLS API.
+
+#ifdef WITH_OPENSSL
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/common_tls.h>
+
+#include <boost/asio/ssl.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Translate TLS role into implementation.
+inline boost::asio::ssl::stream_base::handshake_type roleToImpl(TlsRole role) {
+    if (role == TlsRole::SERVER) {
+        return (boost::asio::ssl::stream_base::server);
+    } else {
+        return (boost::asio::ssl::stream_base::client);
+    }
+}
+
+/// @brief OpenSSL TLS context.
+class TlsContext : public TlsContextBase {
+public:
+
+    /// @brief Destructor.
+    virtual ~TlsContext() { }
+
+    /// @brief Create a fresh context.
+    ///
+    /// @param role The TLS role client or server.
+    explicit TlsContext(TlsRole role);
+
+    /// @brief Return a reference to the underlying context.
+    boost::asio::ssl::context& getContext();
+
+    /// @brief Return the pointer to the SSL_CTX object.
+    ///
+    /// Currently used only for tests. Please note that since OpenSSL 1.1
+    /// The SSL_CTX type is not fully publicly defined.
+    ::SSL_CTX* getNativeContext();
+
+    /// @brief Get the peer certificate requirement mode.
+    ///
+    /// @return True if peer certificates are required, false if they
+    /// are optional.
+    virtual bool getCertRequired() const;
+
+protected:
+    /// @brief Set the peer certificate requirement mode.
+    ///
+    /// @param cert_required True if peer certificates are required,
+    /// false if they are optional.
+    virtual void setCertRequired(bool cert_required);
+
+    /// @brief Load the trust anchor aka certification authority.
+    ///
+    /// @param ca_file The certificate file name.
+    virtual void loadCaFile(const std::string& ca_file);
+
+    /// @brief Load the trust anchor aka certification authority.
+    ///
+    /// @param ca_path The certificate directory name.
+    virtual void loadCaPath(const std::string& ca_path);
+
+    /// @brief Load the certificate file.
+    ///
+    /// @param cert_file The certificate file name.
+    virtual void loadCertFile(const std::string& cert_file);
+
+    /// @brief Load the private key from a file.
+    ///
+    /// @param key_file The private key file name.
+    virtual void loadKeyFile(const std::string& key_file);
+
+    /// @brief Cached cert_required value.
+    bool cert_required_;
+
+    /// @brief Boost ASIO SSL object.
+    boost::asio::ssl::context context_;
+
+    /// @brief Allow access to protected methods by the base class.
+    friend class TlsContextBase;
+};
+
+/// @brief The type of underlying TLS streams.
+typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> TlsStreamImpl;
+
+/// @brief TlsStreamBase constructor.
+///
+/// @tparam Callback The type of callbacks.
+/// @tparam TlsStreamImpl The type of underlying TLS streams.
+/// @param service I/O Service object used to manage the stream.
+/// @param context Pointer to the TLS context.
+/// @note The caller must not provide a null pointer to the TLS context.
+template <typename Callback, typename TlsStreamImpl>
+TlsStreamBase<Callback, TlsStreamImpl>::
+TlsStreamBase(IOService& service, TlsContextPtr context)
+    : TlsStreamImpl(service.get_io_service(), context->getContext()),
+      role_(context->getRole()) {
+}
+
+/// @brief OpenSSL TLS stream.
+///
+/// @tparam callback The callback.
+template <typename Callback>
+class TlsStream : public TlsStreamBase<Callback, TlsStreamImpl> {
+public:
+
+    /// @brief Type of the base.
+    typedef TlsStreamBase<Callback, TlsStreamImpl> Base;
+
+    /// @brief Constructor.
+    ///
+    /// @param service I/O Service object used to manage the stream.
+    /// @param context Pointer to the TLS context.
+    /// @note The caller must not provide a null pointer to the TLS context.
+    TlsStream(IOService& service, TlsContextPtr context)
+        : Base(service, context) {
+    }
+
+    /// @brief Destructor.
+    virtual ~TlsStream() { }
+
+    /// @brief TLS Handshake.
+    ///
+    /// @param callback Callback object.
+    virtual void handshake(Callback& callback) {
+        Base::async_handshake(roleToImpl(Base::getRole()), callback);
+    }
+
+    /// @brief TLS shutdown.
+    ///
+    /// @param callback Callback object.
+    virtual void shutdown(Callback& callback) {
+        Base::async_shutdown(callback);
+    }
+
+    /// @brief Clear the SSL object.
+    virtual void clear() {
+        static_cast<void>(::SSL_clear(this->native_handle()));
+    }
+
+    /// @brief Return the commonName part of the subjectName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    /// RFC 3280 provides as a commonName example "Susan Housley",
+    /// to idea to give access to this come from the Role Based
+    /// Access Control experiment.
+    ///
+    ///
+    /// @return The commonName part of the subjectName or the empty string.
+    virtual std::string getSubject() {
+        ::X509* cert = ::SSL_get_peer_certificate(this->native_handle());
+        if (!cert) {
+            return ("");
+        }
+        ::X509_NAME *name = ::X509_get_subject_name(cert);
+        int loc = ::X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+        ::X509_NAME_ENTRY* ne = ::X509_NAME_get_entry(name, loc);
+        if (!ne) {
+            ::X509_free(cert);
+            return ("");
+        }
+        unsigned char* buf = 0;
+        int len = ::ASN1_STRING_to_UTF8(&buf, ::X509_NAME_ENTRY_get_data(ne));
+        if (len < 0) {
+            ::X509_free(cert);
+            return ("");
+        }
+        std::string ret(reinterpret_cast<char*>(buf), static_cast<size_t>(len));
+        ::OPENSSL_free(buf);
+        ::X509_free(cert);
+        return (ret);
+    }
+
+    /// @brief Return the commonName part of the issuerName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    /// The issuerName is the subjectName of the signing certificate
+    /// (the issue in PKIX terms). The idea is to encode a group as
+    /// members of an intermediate certification authority.
+    ///
+    ///
+    /// @return The commonName part of the issuerName or the empty string.
+    virtual std::string getIssuer() {
+        ::X509* cert = ::SSL_get_peer_certificate(this->native_handle());
+        if (!cert) {
+            return ("");
+        }
+        ::X509_NAME *name = ::X509_get_issuer_name(cert);
+        int loc = ::X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+        ::X509_NAME_ENTRY* ne = ::X509_NAME_get_entry(name, loc);
+        if (!ne) {
+            ::X509_free(cert);
+            return ("");
+        }
+        unsigned char* buf = 0;
+        int len = ::ASN1_STRING_to_UTF8(&buf, ::X509_NAME_ENTRY_get_data(ne));
+        if (len < 0) {
+            ::X509_free(cert);
+            return ("");
+        }
+        std::string ret(reinterpret_cast<char*>(buf), static_cast<size_t>(len));
+        ::OPENSSL_free(buf);
+        ::X509_free(cert);
+        return (ret);
+    }
+};
+
+// Stream truncated error code.
+#ifdef HAVE_STREAM_TRUNCATED_ERROR
+const int STREAM_TRUNCATED = boost::asio::ssl::error::stream_truncated;
+#else
+const int STREAM_TRUNCATED = ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ);
+#endif
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_OPENSSL
+
+#endif // OPENSSL_TLS_H
diff --git a/src/lib/asiolink/asiolink/process_spawn.cc b/src/lib/asiolink/asiolink/process_spawn.cc
new file mode 100644 (file)
index 0000000..2f60718
--- /dev/null
@@ -0,0 +1,391 @@
+// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service_signal.h>
+#include <asiolink/process_spawn.h>
+#include <exceptions/exceptions.h>
+#include <cstring>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <signal.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Type for process state
+struct ProcessState {
+
+    /// @brief Constructor
+    ProcessState() : running_(true), status_(0) {
+    }
+
+    /// @brief true until the exit status is collected
+    bool running_;
+
+    /// @brief 0 or the exit status
+    int status_;
+};
+
+/// @brief Defines a pointer to a ProcessState.
+typedef boost::shared_ptr<ProcessState> ProcessStatePtr;
+
+/// @brief ProcessStates container which stores a ProcessState for each process
+/// identified by PID.
+typedef std::map<pid_t, ProcessStatePtr> ProcessStates;
+
+class ProcessSpawnImpl;
+
+/// @brief ProcessCollection container which stores all ProcessStates for each
+/// instance of @ref ProcessSpawnImpl.
+typedef std::map<const ProcessSpawnImpl*, ProcessStates> ProcessCollection;
+
+/// @brief Implementation of the @c ProcessSpawn class.
+///
+/// This pimpl idiom is used by the @c ProcessSpawn in this case to
+/// avoid exposing the internals of the implementation, such as
+/// custom handling of a SIGCHLD signal, and the conversion of the
+/// arguments of the executable from the STL container to the array.
+///
+/// This class is made noncopyable so that we don't have attempts
+/// to make multiple copies of an object.  This avoid problems
+/// with multiple copies of objects for a single global resource
+/// such as the SIGCHLD signal handler. In addition making it
+/// noncopyable keeps the static check code from flagging the
+/// lack of a copy constructor as an issue.
+class ProcessSpawnImpl : boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service The IOService which handles signal handlers.
+    /// @param executable A full path to the program to be executed.
+    /// @param args Arguments for the program to be executed.
+    /// @param vars Environment variables for the program to be executed.
+    ProcessSpawnImpl(IOServicePtr io_service,
+                     const std::string& executable,
+                     const ProcessArgs& args,
+                     const ProcessEnvVars& vars);
+
+    /// @brief Destructor.
+    ~ProcessSpawnImpl();
+
+    /// @brief Returns full command line, including arguments, for the process.
+    std::string getCommandLine() const;
+
+    /// @brief Spawn the new process.
+    ///
+    /// This method forks the current process and executes the specified
+    /// binary with arguments within the child process.
+    ///
+    /// The child process will return EXIT_FAILURE if the method was unable
+    /// to start the executable, e.g. as a result of insufficient permissions
+    /// or when the executable does not exist. If the process ends successfully
+    /// the EXIT_SUCCESS is returned.
+    ///
+    /// @param dismiss The flag which indicated if the process status can be
+    /// disregarded.
+    /// @return PID of the spawned process.
+    /// @throw ProcessSpawnError if forking a current process failed.
+    pid_t spawn(bool dismiss);
+
+    /// @brief Checks if the process is still running.
+    ///
+    /// @param pid ID of the child processes for which state should be checked.
+    /// @return true if the child process is running, false otherwise.
+    bool isRunning(const pid_t pid) const;
+
+    /// @brief Checks if any of the spawned processes is still running.
+    ///
+    /// @return true if at least one child process is still running.
+    bool isAnyRunning() const;
+
+    /// @brief Returns exit status of the process.
+    ///
+    /// If the process is still running, the previous status is returned
+    /// or 0, if the process is being ran for the first time.
+    ///
+    /// @param pid ID of the child process for which exit status should be
+    /// returned.
+    /// @return Exit code of the process.
+    int getExitStatus(const pid_t pid) const;
+
+    /// @brief Removes the status of the process with a specified PID.
+    ///
+    /// This method removes the status of the process with a specified PID.
+    /// If the process is still running, the status is not removed and the
+    /// exception is thrown.
+    ///
+    /// @param pid A process pid.
+    void clearState(const pid_t pid);
+
+private:
+
+    /// @brief Copies the argument specified as a C++ string to the new
+    /// C string.
+    ///
+    /// This method is used to convert arguments specified as an STL container
+    /// holding @c std::string objects to an array of C strings, used by the
+    /// @c execve function in the @c ProcessSpawnImpl::spawn. It allocates a
+    /// new C string and copies the contents of the @c src to it.
+    /// The data is stored in an internal container so that the caller of the
+    /// function can be exception safe.
+    ///
+    /// @param src A source string.
+    ///
+    /// @return Allocated C string holding the data from @c src.
+    char* allocateInternal(const std::string& src);
+
+    /// @brief Signal handler for SIGCHLD.
+    ///
+    /// This handler waits for the child process to finish and retrieves
+    /// its exit code into the @c status_ member.
+    ///
+    /// @return true if the processed signal was SIGCHLD or false if it
+    /// was a different signal.
+    static bool waitForProcess(int signum);
+
+    /// @brief A map holding the status codes of executed processes.
+    static ProcessCollection process_collection_;
+
+    /// @brief Path to an executable.
+    std::string executable_;
+
+    /// @brief An array holding arguments for the executable.
+    boost::shared_ptr<char*[]> args_;
+
+    /// @brief An array holding environment variables for the executable.
+    boost::shared_ptr<char*[]> vars_;
+
+    /// @brief Typedef for CString pointer.
+    typedef boost::shared_ptr<char[]> CStringPtr;
+
+    /// @brief An storage container for all allocated C strings.
+    std::vector<CStringPtr> storage_;
+
+    /// @brief Flag to indicate if process status must be stored.
+    bool store_;
+
+    /// @brief Mutex to protect internal state.
+    static std::mutex mutex_;
+
+    /// @brief ASIO signal set.
+    IOSignalSetPtr io_signal_set_;
+};
+
+ProcessCollection ProcessSpawnImpl::process_collection_;
+std::mutex ProcessSpawnImpl::mutex_;
+
+ProcessSpawnImpl::ProcessSpawnImpl(IOServicePtr io_service,
+                                   const std::string& executable,
+                                   const ProcessArgs& args,
+                                   const ProcessEnvVars& vars)
+    : executable_(executable), args_(new char*[args.size() + 2]),
+      vars_(new char*[vars.size() + 1]), store_(false),
+      io_signal_set_(new IOSignalSet(io_service,
+                                     std::bind(&ProcessSpawnImpl::waitForProcess,
+                                               ph::_1))) {
+    io_signal_set_->add(SIGCHLD);
+
+    // Conversion of the arguments to the C-style array we start by setting
+    // all pointers within an array to NULL to indicate that they haven't
+    // been allocated yet.
+    memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
+    memset(vars_.get(), 0, (vars.size() + 1) * sizeof(char*));
+    // By convention, the first argument points to an executable name.
+    args_[0] = allocateInternal(executable_);
+    // Copy arguments to the array.
+    for (int i = 1; i <= args.size(); ++i) {
+        args_[i] = allocateInternal(args[i - 1]);
+    }
+    // Copy environment variables to the array.
+    for (int i = 0; i < vars.size(); ++i) {
+        vars_[i] = allocateInternal(vars[i]);
+    }
+}
+
+ProcessSpawnImpl::~ProcessSpawnImpl() {
+    io_signal_set_->remove(SIGCHLD);
+    if (store_) {
+        lock_guard<std::mutex> lk(mutex_);
+        process_collection_.erase(this);
+    }
+}
+
+std::string
+ProcessSpawnImpl::getCommandLine() const {
+    std::ostringstream s;
+    s << executable_;
+    // Start with index 1, because the first argument duplicates the
+    // path to the executable. Note, that even if there are no parameters
+    // the minimum size of the table is 2.
+    int i = 1;
+    while (args_[i] != NULL) {
+        s << " " << args_[i];
+        ++i;
+    }
+    return (s.str());
+}
+
+pid_t
+ProcessSpawnImpl::spawn(bool dismiss) {
+    lock_guard<std::mutex> lk(mutex_);
+    // Create the child
+    pid_t pid = fork();
+    if (pid < 0) {
+        isc_throw(ProcessSpawnError, "unable to fork current process");
+
+    } else if (pid == 0) {
+        // Run the executable.
+        execve(executable_.c_str(), args_.get(), vars_.get());
+        // We may end up here if the execve failed, e.g. as a result
+        // of issue with permissions or invalid executable name.
+        _exit(EXIT_FAILURE);
+    }
+
+    // We're in the parent process.
+    if (!dismiss) {
+        store_ = true;
+        process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
+    }
+    return (pid);
+}
+
+bool
+ProcessSpawnImpl::isRunning(const pid_t pid) const {
+    lock_guard<std::mutex> lk(mutex_);
+    ProcessStates::const_iterator proc;
+    if (process_collection_.find(this) == process_collection_.end() ||
+        (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
+        isc_throw(BadValue, "the process with the pid '" << pid
+                  << "' hasn't been spawned and it status cannot be"
+                  " returned");
+    }
+    return (proc->second->running_);
+}
+
+bool
+ProcessSpawnImpl::isAnyRunning() const {
+    lock_guard<std::mutex> lk(mutex_);
+    if (process_collection_.find(this) != process_collection_.end()) {
+        for (auto const& proc : process_collection_[this]) {
+            if (proc.second->running_) {
+                return (true);
+            }
+        }
+    }
+    return (false);
+}
+
+int
+ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
+    lock_guard<std::mutex> lk(mutex_);
+    ProcessStates::const_iterator proc;
+    if (process_collection_.find(this) == process_collection_.end() ||
+        (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
+        isc_throw(InvalidOperation, "the process with the pid '" << pid
+                  << "' hasn't been spawned and it status cannot be"
+                  " returned");
+    }
+    return (WEXITSTATUS(proc->second->status_));
+}
+
+char*
+ProcessSpawnImpl::allocateInternal(const std::string& src) {
+    const size_t src_len = src.length();
+    storage_.push_back(CStringPtr(new char[src_len + 1]));
+    // Allocate the C-string with one byte more for the null termination.
+    char* dest = storage_[storage_.size() - 1].get();
+    // copy doesn't append the null at the end.
+    src.copy(dest, src_len);
+    // Append null on our own.
+    dest[src_len] = '\0';
+    return (dest);
+}
+
+bool
+ProcessSpawnImpl::waitForProcess(int) {
+    lock_guard<std::mutex> lk(mutex_);
+    for (;;) {
+        int status = 0;
+        pid_t pid = waitpid(-1, &status, WNOHANG);
+        if (pid <= 0) {
+            break;
+        }
+        for (auto const& instance : process_collection_) {
+            auto const& proc = instance.second.find(pid);
+            /// Check that the terminating process was started
+            /// by our instance of ProcessSpawn
+            if (proc != instance.second.end()) {
+                // In this order please
+                proc->second->status_ = status;
+                proc->second->running_ = false;
+            }
+        }
+    }
+    return (true);
+}
+
+void
+ProcessSpawnImpl::clearState(const pid_t pid) {
+    if (isRunning(pid)) {
+        isc_throw(InvalidOperation, "unable to remove the status for the"
+                  "process (pid: " << pid << ") which is still running");
+    }
+    lock_guard<std::mutex> lk(mutex_);
+    if (process_collection_.find(this) != process_collection_.end()) {
+        process_collection_[this].erase(pid);
+    }
+}
+
+ProcessSpawn::ProcessSpawn(IOServicePtr io_service,
+                           const std::string& executable,
+                           const ProcessArgs& args,
+                           const ProcessEnvVars& vars)
+    : impl_(new ProcessSpawnImpl(io_service, executable, args, vars)) {
+}
+
+std::string
+ProcessSpawn::getCommandLine() const {
+    return (impl_->getCommandLine());
+}
+
+pid_t
+ProcessSpawn::spawn(bool dismiss) {
+    return (impl_->spawn(dismiss));
+}
+
+bool
+ProcessSpawn::isRunning(const pid_t pid) const {
+    return (impl_->isRunning(pid));
+}
+
+bool
+ProcessSpawn::isAnyRunning() const {
+    return (impl_->isAnyRunning());
+}
+
+int
+ProcessSpawn::getExitStatus(const pid_t pid) const {
+    return (impl_->getExitStatus(pid));
+}
+
+void
+ProcessSpawn::clearState(const pid_t pid) {
+    return (impl_->clearState(pid));
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/process_spawn.h b/src/lib/asiolink/asiolink/process_spawn.h
new file mode 100644 (file)
index 0000000..ca72ce8
--- /dev/null
@@ -0,0 +1,149 @@
+// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PROCESS_SPAWN_H
+#define PROCESS_SPAWN_H
+
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <sys/types.h>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Exception thrown when error occurs during spawning a process.
+class ProcessSpawnError : public Exception {
+public:
+    ProcessSpawnError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the implementation of the @c ProcessSpawn
+/// class.
+class ProcessSpawnImpl;
+
+/// @brief Pointer to a ProcessSpawnImpl class.
+typedef boost::shared_ptr<ProcessSpawnImpl> ProcessSpawnImplPtr;
+
+/// @brief Type of the container holding arguments of the executable
+/// being run as a background process.
+typedef std::vector<std::string> ProcessArgs;
+
+/// @brief Type of the container holding environment variables of the executable
+/// being run as a background process.
+typedef std::vector<std::string> ProcessEnvVars;
+
+/// @brief Utility class for spawning new processes.
+///
+/// This class is used to spawn new process by Kea. It forks the current
+/// process and then uses the @c execve function to execute the specified
+/// binary with parameters. The @c ProcessSpawn installs the handler for
+/// the SIGCHLD signal, which is executed when the child process ends.
+/// The handler checks the exit code returned by the process and records
+/// it. The exit code can be retrieved by the caller using the
+/// @c ProcessSpawn::getExitStatus method.
+///
+/// This class is made noncopyable so that we don't have attempts
+/// to make multiple copies of an object.  This avoid problems
+/// with multiple copies of objects for a single global resource
+/// such as the SIGCHLD signal handler. In addition making it
+/// noncopyable keeps the static check code from flagging the
+/// lack of a copy constructor as an issue.
+///
+/// @note The ProcessSpawn uses full path for the program to execute.
+class ProcessSpawn : boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service The IOService which handles signal handlers.
+    /// @param executable A full path to the program to be executed.
+    /// @param args Arguments for the program to be executed.
+    /// @param vars Environment variables for the program to be executed.
+    ProcessSpawn(isc::asiolink::IOServicePtr io_service,
+                 const std::string& executable,
+                 const ProcessArgs& args = ProcessArgs(),
+                 const ProcessEnvVars& vars = ProcessEnvVars());
+
+    /// @brief Destructor.
+    ~ProcessSpawn() = default;
+
+    /// @brief Returns full command line, including arguments, for the process.
+    std::string getCommandLine() const;
+
+    /// @brief Spawn the new process.
+    ///
+    /// This method forks the current process and executes the specified
+    /// binary with arguments within the child process.
+    ///
+    /// The child process will return EXIT_FAILURE if the method was unable
+    /// to start the executable, e.g. as a result of insufficient permissions
+    /// or when the executable does not exist. If the process ends successfully
+    /// the EXIT_SUCCESS is returned.
+    ///
+    /// @param dismiss The flag which indicated if the process status can be
+    /// disregarded.
+    /// @throw ProcessSpawnError if forking a current process failed.
+    pid_t spawn(bool dismiss = false);
+
+    /// @brief Checks if the process is still running.
+    ///
+    /// Note that only a negative (false) result is reliable as the child
+    /// process can exit between the time its state is checked and this
+    /// function returns.
+    ///
+    /// @param pid ID of the child processes for which state should be checked.
+    ///
+    /// @return true if the child process is running, false otherwise.
+    bool isRunning(const pid_t pid) const;
+
+    /// @brief Checks if any of the spawned processes is still running.
+    ///
+    /// @return true if at least one child process is still running.
+    bool isAnyRunning() const;
+
+    /// @brief Returns exit status of the process.
+    ///
+    /// If the process is still running, the previous status is returned
+    /// or 0, if the process is being ran for the first time.
+    ///
+    /// @note @c ProcessSpawn::isRunning should be called and have returned
+    /// false before using @c ProcessSpawn::getExitStatus.
+    ///
+    /// @param pid ID of the child process for which exit status should be
+    /// returned.
+    ///
+    /// @return Exit code of the process.
+    int getExitStatus(const pid_t pid) const;
+
+    /// @brief Removes the status of the process with a specified PID.
+    ///
+    /// This method removes the status of the process with a specified PID.
+    /// If the process is still running, the status is not removed and the
+    /// exception is thrown.
+    ///
+    /// Note @c ProcessSpawn::isRunning must be called and have returned
+    /// false before using clearState(). And of course
+    /// @c ProcessSpawn::getExitStatus should be called first, if there is
+    /// some interest in the status.
+    ///
+    /// @param pid A process pid.
+    void clearState(const pid_t pid);
+
+private:
+
+    /// @brief A smart pointer to the implementation of this class.
+    ProcessSpawnImplPtr impl_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // PROCESS_SPAWN_H
diff --git a/src/lib/asiolink/asiolink/tcp_acceptor.h b/src/lib/asiolink/asiolink/tcp_acceptor.h
new file mode 100644 (file)
index 0000000..4fab1dd
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_ACCEPTOR_H
+#define TCP_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <netinet/in.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Provides a service for accepting new TCP connections.
+///
+/// Internally it uses @c boost::asio::ip::tcp::acceptor class to implement
+/// the acceptor service.
+///
+/// @tparam C Acceptor callback type.
+template<typename C>
+class TCPAcceptor : public IOAcceptor<boost::asio::ip::tcp, C> {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service.
+    explicit TCPAcceptor(IOService& io_service)
+        : IOAcceptor<boost::asio::ip::tcp, C>(io_service) {
+    }
+
+    /// @brief Returns protocol of the socket.
+    ///
+    /// @return IPPROTO_TCP.
+    virtual int getProtocol() const final {
+        return (IPPROTO_TCP);
+    }
+
+    /// @brief Asynchronously accept new connection.
+    ///
+    /// This method accepts new connection into the specified socket. When the
+    /// new connection arrives or an error occurs the specified callback function
+    /// is invoked.
+    ///
+    /// @param socket Socket into which connection should be accepted.
+    /// @param callback Callback function to be invoked when the new connection
+    /// arrives.
+    /// @tparam SocketCallback Type of the callback for the @ref TCPSocket.
+    template<typename SocketCallback>
+    void asyncAccept(const TCPSocket<SocketCallback>& socket, C& callback) {
+        IOAcceptor<boost::asio::ip::tcp, C>::asyncAcceptInternal(socket, callback);
+    }
+};
+
+
+} // namespace asiolink
+} // namespace isc
+
+#endif
diff --git a/src/lib/asiolink/asiolink/tcp_endpoint.h b/src/lib/asiolink/asiolink/tcp_endpoint.h
new file mode 100644 (file)
index 0000000..7cbb73b
--- /dev/null
@@ -0,0 +1,115 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_ENDPOINT_H
+#define TCP_ENDPOINT_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c TCPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a TCP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class TCPEndpoint : public IOEndpoint {
+public:
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    //@{
+
+    /// \brief Default Constructor
+    ///
+    /// Creates an internal endpoint.  This is expected to be set by some
+    /// external call.
+    TCPEndpoint() :
+        asio_endpoint_placeholder_(new boost::asio::ip::tcp::endpoint()),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief Constructor from a pair of address and port.
+    ///
+    /// \param address The IP address of the endpoint.
+    /// \param port The TCP port number of the endpoint.
+    TCPEndpoint(const IOAddress& address, const unsigned short port) :
+        asio_endpoint_placeholder_(
+            new boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address.toText()),
+                              port)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief Constructor from an ASIO TCP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c tcp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+    TCPEndpoint(boost::asio::ip::tcp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+    {}
+
+    /// \brief Constructor from an ASIO TCP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c tcp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+    TCPEndpoint(const boost::asio::ip::tcp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(new boost::asio::ip::tcp::endpoint(asio_endpoint)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief The destructor.
+    virtual ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+    //@}
+
+    virtual IOAddress getAddress() const {
+        return (asio_endpoint_.address());
+    }
+
+    virtual const struct sockaddr& getSockAddr() const {
+        return (*asio_endpoint_.data());
+    }
+
+    virtual uint16_t getPort() const {
+        return (asio_endpoint_.port());
+    }
+
+    virtual short getProtocol() const {
+        return (asio_endpoint_.protocol().protocol());
+    }
+
+    virtual short getFamily() const {
+        return (asio_endpoint_.protocol().family());
+    }
+
+    // This is not part of the exposed IOEndpoint API but allows
+    // direct access to the ASIO implementation of the endpoint
+    inline const boost::asio::ip::tcp::endpoint& getASIOEndpoint() const {
+        return (asio_endpoint_);
+    }
+    inline boost::asio::ip::tcp::endpoint& getASIOEndpoint() {
+        return (asio_endpoint_);
+    }
+
+private:
+    boost::asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
+    boost::asio::ip::tcp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+} // namespace isc
+#endif // TCP_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/asiolink/tcp_socket.h b/src/lib/asiolink/asiolink/tcp_socket.h
new file mode 100644 (file)
index 0000000..72d98b3
--- /dev/null
@@ -0,0 +1,504 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_SOCKET_H
+#define TCP_SOCKET_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>             // for some IPC/network system calls
+
+#include <algorithm>
+#include <cstddef>
+
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+
+#include <exceptions/isc_assert.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief Buffer Too Large
+///
+/// Thrown on an attempt to send a buffer > 64k
+class BufferTooLarge : public IOError {
+public:
+    BufferTooLarge(const char* file, size_t line, const char* what) :
+        IOError(file, line, what) {}
+};
+
+/// \brief The \c TCPSocket class is a concrete derived class of \c IOAsioSocket
+/// that represents a TCP socket.
+///
+/// \param C Callback type
+template <typename C>
+class TCPSocket : public IOAsioSocket<C> {
+private:
+    /// \brief Class is non-copyable
+    TCPSocket(const TCPSocket&);
+    TCPSocket& operator=(const TCPSocket&);
+
+public:
+
+    /// \brief Constructor from an ASIO TCP socket.
+    ///
+    /// \param socket The ASIO representation of the TCP socket.  It is assumed
+    ///        that the caller will open and close the socket, so these
+    ///        operations are a no-op for that socket.
+    TCPSocket(boost::asio::ip::tcp::socket& socket);
+
+    /// \brief Constructor
+    ///
+    /// Used when the TCPSocket is being asked to manage its own internal
+    /// socket.  In this case, the open() and close() methods are used.
+    ///
+    /// \param service I/O Service object used to manage the socket.
+    TCPSocket(IOService& service);
+
+    /// \brief Destructor
+    virtual ~TCPSocket();
+
+    /// \brief Return file descriptor of underlying socket
+    virtual int getNative() const {
+#if BOOST_VERSION < 106600
+        return (socket_.native());
+#else
+        return (socket_.native_handle());
+#endif
+    }
+
+    /// \brief Return protocol of socket
+    virtual int getProtocol() const {
+        return (IPPROTO_TCP);
+    }
+
+    /// \brief Is "open()" synchronous?
+    ///
+    /// Indicates that the opening of a TCP socket is asynchronous.
+    virtual bool isOpenSynchronous() const {
+        return (false);
+    }
+
+    /// \brief Checks if the connection is usable.
+    ///
+    /// The connection is usable if the socket is open and the peer has not
+    /// closed its connection.
+    ///
+    /// \return true if the connection is usable.
+    bool isUsable() const {
+        // If the socket is open it doesn't mean that it is still usable. The connection
+        // could have been closed on the other end. We have to check if we can still
+        // use this socket.
+        if (socket_.is_open()) {
+            // Remember the current non blocking setting.
+            const bool non_blocking_orig = socket_.non_blocking();
+            // Set the socket to non blocking mode. We're going to test if the socket
+            // returns would_block status on the attempt to read from it.
+            socket_.non_blocking(true);
+
+            boost::system::error_code ec;
+            char data[2];
+
+            // Use receive with message peek flag to avoid removing the data awaiting
+            // to be read.
+            socket_.receive(boost::asio::buffer(data, sizeof(data)),
+                            boost::asio::socket_base::message_peek,
+                            ec);
+
+            // Revert the original non_blocking flag on the socket.
+            socket_.non_blocking(non_blocking_orig);
+
+            // If the connection is alive we'd typically get would_block status code.
+            // If there are any data that haven't been read we may also get success
+            // status. We're guessing that try_again may also be returned by some
+            // implementations in some situations. Any other error code indicates a
+            // problem with the connection so we assume that the connection has been
+            // closed.
+            return (!ec || (ec.value() == boost::asio::error::try_again) ||
+                    (ec.value() == boost::asio::error::would_block));
+        }
+
+        return (false);
+    }
+
+    /// \brief Open Socket
+    ///
+    /// Opens the TCP socket.  This is an asynchronous operation, completion of
+    /// which will be signalled via a call to the callback function.
+    ///
+    /// \param endpoint Endpoint to which the socket will connect.
+    /// \param callback Callback object.
+    virtual void open(const IOEndpoint* endpoint, C& callback);
+
+    /// \brief Send Asynchronously
+    ///
+    /// Calls the underlying socket's async_send() method to send a packet of
+    /// data asynchronously to the remote endpoint.  The callback will be called
+    /// on completion.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param endpoint Target of the send. (Unused for a TCP socket because
+    ///        that was determined when the connection was opened.)
+    /// \param callback Callback object.
+    /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+    virtual void asyncSend(const void* data, size_t length,
+                           const IOEndpoint* endpoint, C& callback);
+
+    /// \brief Send Asynchronously without count.
+    ///
+    /// This variant of the method sends data over the TCP socket without
+    /// preceding the data with a data count. Eventually, we should migrate
+    /// the virtual method to not insert the count but there are existing
+    /// classes using the count. Once this migration is done, the existing
+    /// virtual method should be replaced by this method.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param callback Callback object.
+    /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+    void asyncSend(const void* data, size_t length, C& callback);
+
+    /// \brief Receive Asynchronously
+    ///
+    /// Calls the underlying socket's async_receive() method to read a packet
+    /// of data from a remote endpoint.  Arrival of the data is signalled via a
+    /// call to the callback function.
+    ///
+    /// \param data Buffer to receive incoming message
+    /// \param length Length of the data buffer
+    /// \param offset Offset into buffer where data is to be put
+    /// \param endpoint Source of the communication
+    /// \param callback Callback object
+    virtual void asyncReceive(void* data, size_t length, size_t offset,
+                              IOEndpoint* endpoint, C& callback);
+
+    /// \brief Process received data packet
+    ///
+    /// See the description of IOAsioSocket::receiveComplete for a complete
+    /// description of this method.
+    ///
+    /// \param staging Pointer to the start of the staging buffer.
+    /// \param length Amount of data in the staging buffer.
+    /// \param cumulative Amount of data received before the staging buffer is
+    ///        processed.
+    /// \param offset Unused.
+    /// \param expected unused.
+    /// \param outbuff Output buffer.  Data in the staging buffer is be copied
+    ///        to this output buffer in the call.
+    ///
+    /// \return Always true
+    virtual bool processReceivedData(const void* staging, size_t length,
+                                     size_t& cumulative, size_t& offset,
+                                     size_t& expected,
+                                     isc::util::OutputBufferPtr& outbuff);
+
+    /// \brief Cancel I/O On Socket
+    virtual void cancel();
+
+    /// \brief Close socket
+    virtual void close();
+
+    /// \brief Returns reference to the underlying ASIO socket.
+    ///
+    /// \return Reference to underlying ASIO socket.
+    virtual boost::asio::ip::tcp::socket& getASIOSocket() const {
+        return (socket_);
+    }
+
+private:
+    /// Two variables to hold the socket - a socket and a pointer to it.  This
+    /// handles the case where a socket is passed to the TCPSocket on
+    /// construction, or where it is asked to manage its own socket.
+
+    /// Pointer to own socket
+    std::unique_ptr<boost::asio::ip::tcp::socket> socket_ptr_;
+
+    /// Socket
+    boost::asio::ip::tcp::socket& socket_;
+
+    /// @todo Remove temporary buffer
+    /// The current implementation copies the buffer passed to asyncSend() into
+    /// a temporary buffer and precedes it with a two-byte count field.  As
+    /// ASIO should really be just about sending and receiving data, the TCP
+    /// code should not do this.  If the protocol using this requires a two-byte
+    /// count, it should add it before calling this code.  (This may be best
+    /// achieved by altering isc::dns::buffer to have pairs of methods:
+    /// getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
+    /// methods taking into account a two-byte count field.)
+    ///
+    /// The option of sending the data in two operations, the count followed by
+    /// the data was discounted as that would lead to two callbacks which would
+    /// cause problems with the stackless coroutine code.
+
+    /// Send buffer
+    isc::util::OutputBufferPtr send_buffer_;
+};
+
+// Constructor - caller manages socket
+
+template <typename C>
+TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
+    socket_ptr_(), socket_(socket), send_buffer_()
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+TCPSocket<C>::TCPSocket(IOService& service) :
+    socket_ptr_(new boost::asio::ip::tcp::socket(service.get_io_service())),
+    socket_(*socket_ptr_)
+{
+}
+
+// Destructor.
+
+template <typename C>
+TCPSocket<C>::~TCPSocket()
+{
+}
+
+// Open the socket.
+
+template <typename C> void
+TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
+    // If socket is open on this end but has been closed by the peer,
+    // we need to reconnect.
+    if (socket_.is_open() && !isUsable()) {
+        close();
+    }
+    // Ignore opens on already-open socket.  Don't throw a failure because
+    // of uncertainties as to what precedes when using asynchronous I/O.
+    // Also allows us a treat a passed-in socket as a self-managed socket.
+    if (!socket_.is_open()) {
+        if (endpoint->getFamily() == AF_INET) {
+            socket_.open(boost::asio::ip::tcp::v4());
+        }
+        else {
+            socket_.open(boost::asio::ip::tcp::v6());
+        }
+
+        // Set options on the socket:
+
+        // Reuse address - allow the socket to bind to a port even if the port
+        // is in the TIMED_WAIT state.
+        socket_.set_option(boost::asio::socket_base::reuse_address(true));
+    }
+
+    // Upconvert to a TCPEndpoint.  We need to do this because although
+    // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
+    // contain a method for getting at the underlying endpoint type - that is in
+    /// the derived class and the two classes differ on return type.
+    isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+    const TCPEndpoint* tcp_endpoint =
+        static_cast<const TCPEndpoint*>(endpoint);
+
+    // Connect to the remote endpoint.  On success, the handler will be
+    // called (with one argument - the length argument will default to
+    // zero).
+    socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
+}
+
+// Send a message.  Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length, C& callback)
+{
+    if (socket_.is_open()) {
+
+        try {
+            send_buffer_.reset(new isc::util::OutputBuffer(length));
+            send_buffer_->writeData(data, length);
+
+            // Send the data.
+            socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
+                                                   send_buffer_->getLength()),
+                               callback);
+        } catch (const boost::numeric::bad_numeric_cast&) {
+            isc_throw(BufferTooLarge,
+                      "attempt to send buffer larger than 64kB");
+        }
+
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to send on a TCP socket that is not open");
+    }
+}
+
+template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length,
+    const IOEndpoint*, C& callback)
+{
+    if (socket_.is_open()) {
+
+        /// Need to copy the data into a temporary buffer and precede it with
+        /// a two-byte count field.
+        /// @todo arrange for the buffer passed to be preceded by the count
+        try {
+            /// Ensure it fits into 16 bits
+            uint16_t count = boost::numeric_cast<uint16_t>(length);
+
+            /// Copy data into a buffer preceded by the count field.
+            send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
+            send_buffer_->writeUint16(count);
+            send_buffer_->writeData(data, length);
+
+            /// ... and send it
+            socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
+                               send_buffer_->getLength()), callback);
+        } catch (const boost::numeric::bad_numeric_cast&) {
+            isc_throw(BufferTooLarge,
+                      "attempt to send buffer larger than 64kB");
+        }
+
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to send on a TCP socket that is not open");
+    }
+}
+
+// Receive a message. Note that the "offset" argument is used as an index
+// into the buffer in order to decide where to put the data.  It is up to the
+// caller to initialize the data to zero
+template <typename C> void
+TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+    IOEndpoint* endpoint, C& callback)
+{
+    if (socket_.is_open()) {
+        // Upconvert to a TCPEndpoint.  We need to do this because although
+        // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+        // does not contain a method for getting at the underlying endpoint
+        // type - that is in the derived class and the two classes differ on
+        // return type.
+        isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+        TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
+
+        // Write the endpoint details from the communications link.  Ideally
+        // we should make IOEndpoint assignable, but this runs in to all sorts
+        // of problems concerning the management of the underlying Boost
+        // endpoint (e.g. if it is not self-managed, is the copied one
+        // self-managed?) The most pragmatic solution is to let Boost take care
+        // of everything and copy details of the underlying endpoint.
+        tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
+
+        // Ensure we can write into the buffer and if so, set the pointer to
+        // where the data will be written.
+        if (offset >= length) {
+            isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+                                      "TCP receive buffer");
+        }
+        void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+        // ... and kick off the read.
+        socket_.async_receive(boost::asio::buffer(buffer_start, length - offset), callback);
+
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to receive from a TCP socket that is not open");
+    }
+}
+
+// Is the receive complete?
+
+template <typename C> bool
+TCPSocket<C>::processReceivedData(const void* staging, size_t length,
+                                  size_t& cumulative, size_t& offset,
+                                  size_t& expected,
+                                  isc::util::OutputBufferPtr& outbuff)
+{
+    // Point to the data in the staging buffer and note how much there is.
+    const uint8_t* data = static_cast<const uint8_t*>(staging);
+    size_t data_length = length;
+
+    // Is the number is "expected" valid?  It won't be unless we have received
+    // at least two bytes of data in total for this set of receives.
+    if (cumulative < 2) {
+
+        // "expected" is not valid.  Did this read give us enough data to
+        // work it out?
+        cumulative += length;
+        if (cumulative < 2) {
+
+            // Nope, still not valid.  This must have been the first packet and
+            // was only one byte long.  Tell the fetch code to read the next
+            // packet into the staging buffer beyond the data that is already
+            // there so that the next time we are called we have a complete
+            // TCP count.
+            offset = cumulative;
+            return (false);
+        }
+
+        // Have enough data to interpret the packet count, so do so now.
+        expected = isc::util::readUint16(data, cumulative);
+
+        // We have two bytes less of data to process.  Point to the start of the
+        // data and adjust the packet size.  Note that at this point,
+        // "cumulative" is the true amount of data in the staging buffer, not
+        // "length".
+        data += 2;
+        data_length = cumulative - 2;
+    } else {
+
+        // Update total amount of data received.
+        cumulative += length;
+    }
+
+    // Regardless of anything else, the next read goes into the start of the
+    // staging buffer.
+    offset = 0;
+
+    // Work out how much data we still have to put in the output buffer. (This
+    // could be zero if we have just interpreted the TCP count and that was
+    // set to zero.)
+    if (expected >= outbuff->getLength()) {
+
+        // Still need data in the output packet.  Copy what we can from the
+        // staging buffer to the output buffer.
+        size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
+        outbuff->writeData(data, copy_amount);
+    }
+
+    // We can now say if we have all the data.
+    return (expected == outbuff->getLength());
+}
+
+// Cancel I/O on the socket.  No-op if the socket is not open.
+
+template <typename C> void
+TCPSocket<C>::cancel() {
+    if (socket_.is_open()) {
+        socket_.cancel();
+    }
+}
+
+// Close the socket down.  Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TCPSocket<C>::close() {
+    if (socket_.is_open() && socket_ptr_) {
+        socket_.close();
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // TCP_SOCKET_H
diff --git a/src/lib/asiolink/asiolink/tests/.gitignore b/src/lib/asiolink/asiolink/tests/.gitignore
new file mode 100644 (file)
index 0000000..6a0c5b5
--- /dev/null
@@ -0,0 +1,2 @@
+/run_unittests
+/process_spawn_app.sh
diff --git a/src/lib/asiolink/asiolink/tests/Makefile.am b/src/lib/asiolink/asiolink/tests/Makefile.am
new file mode 100644 (file)
index 0000000..db32572
--- /dev/null
@@ -0,0 +1,69 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CPPFLAGS += -DTEST_SCRIPT_SH=\"$(abs_top_builddir)/src/lib/asiolink/tests/process_spawn_app.sh\"
+TEST_CA_DIR = $(abs_srcdir)/../testutils/ca
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda test-socket
+
+DISTCLEANFILES = process_spawn_app.sh
+
+noinst_SCRIPTS = process_spawn_app.sh
+
+TESTS_ENVIRONMENT = \
+       $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES  = run_unittests.cc
+run_unittests_SOURCES += addr_utilities_unittest.cc
+run_unittests_SOURCES += io_address_unittest.cc
+run_unittests_SOURCES += hash_address_unittest.cc
+run_unittests_SOURCES += io_endpoint_unittest.cc
+run_unittests_SOURCES += io_socket_unittest.cc
+run_unittests_SOURCES += interval_timer_unittest.cc
+run_unittests_SOURCES += tcp_endpoint_unittest.cc
+run_unittests_SOURCES += tcp_socket_unittest.cc
+run_unittests_SOURCES += udp_endpoint_unittest.cc
+run_unittests_SOURCES += udp_socket_unittest.cc
+run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += io_service_signal_unittests.cc
+run_unittests_SOURCES += dummy_io_callback_unittest.cc
+run_unittests_SOURCES += tcp_acceptor_unittest.cc
+run_unittests_SOURCES += unix_domain_socket_unittest.cc
+run_unittests_SOURCES += process_spawn_unittest.cc
+if HAVE_OPENSSL
+run_unittests_SOURCES += tls_unittest.cc
+run_unittests_SOURCES += tls_acceptor_unittest.cc
+run_unittests_SOURCES += tls_socket_unittest.cc
+endif
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+run_unittests_LDADD  = $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+run_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+run_unittests_LDADD += $(GTEST_LDADD)
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter -Wno-unused-private-field
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/asiolink/tests/addr_utilities_unittest.cc b/src/lib/asiolink/asiolink/tests/addr_utilities_unittest.cc
new file mode 100644 (file)
index 0000000..a856437
--- /dev/null
@@ -0,0 +1,386 @@
+// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/addr_utilities.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace {
+
+// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
+    IOAddress addr1("192.0.2.1");
+
+    // Prefixes rounded to addresses are easy...
+    EXPECT_EQ("192.255.255.255", lastAddrInPrefix(addr1, 8).toText());
+    EXPECT_EQ("192.0.255.255",   lastAddrInPrefix(addr1, 16).toText());
+    EXPECT_EQ("192.0.2.255",     lastAddrInPrefix(addr1, 24).toText());
+
+    // these are trickier
+    EXPECT_EQ("192.0.2.127", lastAddrInPrefix(addr1, 25).toText());
+    EXPECT_EQ("192.0.2.63",  lastAddrInPrefix(addr1, 26).toText());
+    EXPECT_EQ("192.0.2.31",  lastAddrInPrefix(addr1, 27).toText());
+    EXPECT_EQ("192.0.2.15",  lastAddrInPrefix(addr1, 28).toText());
+    EXPECT_EQ("192.0.2.7",   lastAddrInPrefix(addr1, 29).toText());
+    EXPECT_EQ("192.0.2.3",   lastAddrInPrefix(addr1, 30).toText());
+
+    // that doesn't make much sense as /31 subnet consists of network address
+    // and a broadcast address, with 0 usable addresses.
+    EXPECT_EQ("192.0.2.1",   lastAddrInPrefix(addr1, 31).toText());
+    EXPECT_EQ("192.0.2.1",   lastAddrInPrefix(addr1, 32).toText());
+
+    // Let's check extreme cases
+    IOAddress anyAddr("0.0.0.0");
+    EXPECT_EQ("127.255.255.255", lastAddrInPrefix(anyAddr, 1).toText());
+    EXPECT_EQ("255.255.255.255", lastAddrInPrefix(anyAddr, 0).toText());
+    EXPECT_EQ("0.0.0.0", lastAddrInPrefix(anyAddr, 32).toText());
+}
+
+// This test checks if firstAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, firstAddrInPrefix4) {
+    IOAddress addr1("192.223.2.255");
+
+    // Prefixes rounded to addresses are easy...
+    EXPECT_EQ("192.0.0.0",   firstAddrInPrefix(addr1, 8).toText());
+    EXPECT_EQ("192.223.0.0", firstAddrInPrefix(addr1, 16).toText());
+    EXPECT_EQ("192.223.2.0", firstAddrInPrefix(addr1, 24).toText());
+
+    // these are trickier
+    EXPECT_EQ("192.223.2.128", firstAddrInPrefix(addr1, 25).toText());
+    EXPECT_EQ("192.223.2.192", firstAddrInPrefix(addr1, 26).toText());
+    EXPECT_EQ("192.223.2.224", firstAddrInPrefix(addr1, 27).toText());
+    EXPECT_EQ("192.223.2.240", firstAddrInPrefix(addr1, 28).toText());
+    EXPECT_EQ("192.223.2.248", firstAddrInPrefix(addr1, 29).toText());
+    EXPECT_EQ("192.223.2.252", firstAddrInPrefix(addr1, 30).toText());
+
+    // that doesn't make much sense as /31 subnet consists of network address
+    // and a broadcast address, with 0 usable addresses.
+    EXPECT_EQ("192.223.2.254", firstAddrInPrefix(addr1, 31).toText());
+    EXPECT_EQ("192.223.2.255", firstAddrInPrefix(addr1, 32).toText());
+
+    // Let's check extreme cases.
+    IOAddress bcast("255.255.255.255");
+    EXPECT_EQ("128.0.0.0", firstAddrInPrefix(bcast, 1).toText());
+    EXPECT_EQ("0.0.0.0", firstAddrInPrefix(bcast, 0).toText());
+    EXPECT_EQ("255.255.255.255", firstAddrInPrefix(bcast, 32).toText());
+
+}
+
+/// This test checks if lastAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, lastAddrInPrefix6) {
+    IOAddress addr1("2001:db8:1:1234:5678:abcd:1234:beef");
+
+    // Prefixes rounded to nibbles are easy...
+    EXPECT_EQ("2001:db8:1:1234:5678:abcd:1234:ffff",
+              lastAddrInPrefix(addr1, 112).toText());
+    EXPECT_EQ("2001:db8:1:1234:5678:abcd:123f:ffff",
+              lastAddrInPrefix(addr1, 108).toText());
+    EXPECT_EQ("2001:db8:1:1234:5678:abcd:12ff:ffff",
+              lastAddrInPrefix(addr1, 104).toText());
+    EXPECT_EQ("2001:db8:1:1234:ffff:ffff:ffff:ffff",
+              lastAddrInPrefix(addr1, 64).toText());
+
+    IOAddress addr2("2001::");
+
+    // These are trickier, though, as they are done in 1 bit increments
+
+    // the last address in 2001::/127 pool should be 2001::1
+    EXPECT_EQ("2001::1", lastAddrInPrefix(addr2, 127).toText());
+
+    EXPECT_EQ("2001::3", lastAddrInPrefix(addr2, 126).toText());
+    EXPECT_EQ("2001::7", lastAddrInPrefix(addr2, 125).toText());
+    EXPECT_EQ("2001::f", lastAddrInPrefix(addr2, 124).toText());
+    EXPECT_EQ("2001::1f", lastAddrInPrefix(addr2, 123).toText());
+    EXPECT_EQ("2001::3f", lastAddrInPrefix(addr2, 122).toText());
+    EXPECT_EQ("2001::7f", lastAddrInPrefix(addr2, 121).toText());
+    EXPECT_EQ("2001::ff", lastAddrInPrefix(addr2, 120).toText());
+
+    // Let's check extreme cases
+    IOAddress anyAddr("::");
+    EXPECT_EQ("7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+              lastAddrInPrefix(anyAddr, 1).toText());
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+              lastAddrInPrefix(anyAddr, 0).toText());
+    EXPECT_EQ("::", lastAddrInPrefix(anyAddr, 128).toText());
+}
+
+/// This test checks if firstAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
+    IOAddress addr1("2001:db8:1:1234:5678:1234:abcd:beef");
+
+    // Prefixes rounded to nibbles are easy...
+    EXPECT_EQ("2001:db8:1:1234:5678:1234::",
+              firstAddrInPrefix(addr1, 96).toText());
+    EXPECT_EQ("2001:db8:1:1234:5678:1230::",
+              firstAddrInPrefix(addr1, 92).toText());
+    EXPECT_EQ("2001:db8:1:1234:5678:1200::",
+              firstAddrInPrefix(addr1, 88).toText());
+    EXPECT_EQ("2001:db8:1:1234::",
+              firstAddrInPrefix(addr1, 64).toText());
+
+    IOAddress addr2("2001::ffff");
+
+    // These are trickier, though, as they are done in 1 bit increments
+
+    // the first address in 2001::/127 pool should be 2001::1
+    EXPECT_EQ("2001::fffe", firstAddrInPrefix(addr2, 127).toText());
+
+    EXPECT_EQ("2001::fffc", firstAddrInPrefix(addr2, 126).toText());
+    EXPECT_EQ("2001::fff8", firstAddrInPrefix(addr2, 125).toText());
+    EXPECT_EQ("2001::fff0", firstAddrInPrefix(addr2, 124).toText());
+    EXPECT_EQ("2001::ffe0", firstAddrInPrefix(addr2, 123).toText());
+    EXPECT_EQ("2001::ffc0", firstAddrInPrefix(addr2, 122).toText());
+    EXPECT_EQ("2001::ff80", firstAddrInPrefix(addr2, 121).toText());
+    EXPECT_EQ("2001::ff00", firstAddrInPrefix(addr2, 120).toText());
+}
+
+// Checks if IPv4 netmask is generated properly
+TEST(AddrUtilitiesTest, getNetmask4) {
+    EXPECT_EQ("0.0.0.0", getNetmask4(0).toText());
+    EXPECT_EQ("128.0.0.0", getNetmask4(1).toText());
+    EXPECT_EQ("192.0.0.0", getNetmask4(2).toText());
+    EXPECT_EQ("224.0.0.0", getNetmask4(3).toText());
+    EXPECT_EQ("240.0.0.0", getNetmask4(4).toText());
+    EXPECT_EQ("248.0.0.0", getNetmask4(5).toText());
+    EXPECT_EQ("252.0.0.0", getNetmask4(6).toText());
+    EXPECT_EQ("254.0.0.0", getNetmask4(7).toText());
+    EXPECT_EQ("255.0.0.0", getNetmask4(8).toText());
+
+    EXPECT_EQ("255.128.0.0", getNetmask4(9).toText());
+    EXPECT_EQ("255.192.0.0", getNetmask4(10).toText());
+    EXPECT_EQ("255.224.0.0", getNetmask4(11).toText());
+    EXPECT_EQ("255.240.0.0", getNetmask4(12).toText());
+    EXPECT_EQ("255.248.0.0", getNetmask4(13).toText());
+    EXPECT_EQ("255.252.0.0", getNetmask4(14).toText());
+    EXPECT_EQ("255.254.0.0", getNetmask4(15).toText());
+    EXPECT_EQ("255.255.0.0", getNetmask4(16).toText());
+
+    EXPECT_EQ("255.255.128.0", getNetmask4(17).toText());
+    EXPECT_EQ("255.255.192.0", getNetmask4(18).toText());
+    EXPECT_EQ("255.255.224.0", getNetmask4(19).toText());
+    EXPECT_EQ("255.255.240.0", getNetmask4(20).toText());
+    EXPECT_EQ("255.255.248.0", getNetmask4(21).toText());
+    EXPECT_EQ("255.255.252.0", getNetmask4(22).toText());
+    EXPECT_EQ("255.255.254.0", getNetmask4(23).toText());
+    EXPECT_EQ("255.255.255.0", getNetmask4(24).toText());
+
+    EXPECT_EQ("255.255.255.128", getNetmask4(25).toText());
+    EXPECT_EQ("255.255.255.192", getNetmask4(26).toText());
+    EXPECT_EQ("255.255.255.224", getNetmask4(27).toText());
+    EXPECT_EQ("255.255.255.240", getNetmask4(28).toText());
+    EXPECT_EQ("255.255.255.248", getNetmask4(29).toText());
+    EXPECT_EQ("255.255.255.252", getNetmask4(30).toText());
+    EXPECT_EQ("255.255.255.254", getNetmask4(31).toText());
+    EXPECT_EQ("255.255.255.255", getNetmask4(32).toText());
+
+    EXPECT_THROW(getNetmask4(33), isc::BadValue);
+}
+
+// Checks if the calculation for IPv4 addresses in range are correct.
+TEST(AddrUtilitiesTest, addrsInRange4) {
+
+    // Let's start with something simple
+    EXPECT_EQ(1, addrsInRange(IOAddress("192.0.2.0"), IOAddress("192.0.2.0")));
+    EXPECT_EQ(10, addrsInRange(IOAddress("192.0.2.10"), IOAddress("192.0.2.19")));
+    EXPECT_EQ(256, addrsInRange(IOAddress("192.0.2.0"), IOAddress("192.0.2.255")));
+    EXPECT_EQ(65536, addrsInRange(IOAddress("192.0.0.0"), IOAddress("192.0.255.255")));
+    EXPECT_EQ(16777216, addrsInRange(IOAddress("10.0.0.0"), IOAddress("10.255.255.255")));
+
+    // Let's check if the network boundaries are crossed correctly.
+    EXPECT_EQ(3, addrsInRange(IOAddress("10.0.0.255"), IOAddress("10.0.1.1")));
+
+    // Let's go a bit overboard with this! How many addresses are there in
+    // IPv4 address space? That's a slightly tricky question, as the answer
+    // cannot be stored in uint32_t.
+    EXPECT_EQ(uint64_t(std::numeric_limits<uint32_t>::max()) + 1,
+              addrsInRange(IOAddress("0.0.0.0"), IOAddress("255.255.255.255")));
+
+    // The upper bound cannot be smaller than the lower bound.
+    EXPECT_THROW(addrsInRange(IOAddress("192.0.2.5"), IOAddress("192.0.2.4")),
+                 isc::BadValue);
+}
+
+// Checks if the calculation for IPv6 addresses in range are correct.
+TEST(AddrUtilitiesTest, addrsInRange6) {
+
+    // Let's start with something simple
+    EXPECT_EQ(1, addrsInRange(IOAddress("::"), IOAddress("::")));
+    EXPECT_EQ(16, addrsInRange(IOAddress("fe80::1"), IOAddress("fe80::10")));
+    EXPECT_EQ(65536, addrsInRange(IOAddress("fe80::"), IOAddress("fe80::ffff")));
+    EXPECT_EQ(uint64_t(std::numeric_limits<uint32_t>::max()) + 1,
+              addrsInRange(IOAddress("fe80::"), IOAddress("fe80::ffff:ffff")));
+
+    // There's 2^80 addresses between those. Due to uint64_t limits, this method is
+    // capped at 2^64 -1.
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+              addrsInRange(IOAddress("2001:db8:1::"), IOAddress("2001:db8:2::")));
+
+    // Let's check if the network boundaries are crossed correctly.
+    EXPECT_EQ(3, addrsInRange(IOAddress("2001:db8::ffff"), IOAddress("2001:db8::1:1")));
+
+    // Let's go a bit overboard with this! How many addresses are there in
+    // IPv6 address space? That's a really tricky question, as the answer
+    // wouldn't fit even in uint128_t (if we had it). This method is capped
+    // at max value of uint64_t.
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(), addrsInRange(IOAddress("::"),
+              IOAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+    EXPECT_THROW(addrsInRange(IOAddress("fe80::5"), IOAddress("fe80::4")),
+                 isc::BadValue);
+}
+
+// Checks if IPv4 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange4) {
+    // Use a shorter name
+    const auto& plfr = prefixLengthFromRange;
+
+    // Let's start with something simple
+    EXPECT_EQ(32, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.0")));
+    EXPECT_EQ(31, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.1")));
+    EXPECT_EQ(30, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.3")));
+    EXPECT_EQ(29, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.7")));
+    EXPECT_EQ(28, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.15")));
+    EXPECT_EQ(27, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.31")));
+    EXPECT_EQ(26, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.63")));
+    EXPECT_EQ(25, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.127")));
+    EXPECT_EQ(24, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.255")));
+    EXPECT_EQ(23, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.3.255")));
+    EXPECT_EQ(16, plfr(IOAddress("10.0.0.0"), IOAddress("10.0.255.255")));
+    EXPECT_EQ(8, plfr(IOAddress("10.0.0.0"), IOAddress("10.255.255.255")));
+    EXPECT_EQ(0, plfr(IOAddress("0.0.0.0"), IOAddress("255.255.255.255")));
+
+    // Fail if a network boundary is crossed
+    EXPECT_EQ(-1, plfr(IOAddress("10.0.0.255"), IOAddress("10.0.1.1")));
+
+    // Fail if first is not at the begin
+    EXPECT_EQ(-1, plfr(IOAddress("10.0.0.2"), IOAddress("10.0.0.5")));
+
+    // The upper bound cannot be smaller than the lower bound
+    EXPECT_THROW(plfr(IOAddress("192.0.2.5"), IOAddress("192.0.2.4")),
+                 isc::BadValue);
+}
+
+// Checks if IPv6 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange6) {
+    // Use a shorter name
+    const auto& plfr = prefixLengthFromRange;
+
+    // Let's start with something simple
+    EXPECT_EQ(128, plfr(IOAddress("::"), IOAddress("::")));
+    EXPECT_EQ(112, plfr(IOAddress("fe80::"),  IOAddress("fe80::ffff")));
+    EXPECT_EQ(96, plfr(IOAddress("fe80::"),  IOAddress("fe80::ffff:ffff")));
+    EXPECT_EQ(80, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ffff:ffff:ffff")));
+    EXPECT_EQ(64, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(63, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(62, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(61, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(60, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(59, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(58, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(57, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(56, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(55, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(54, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(53, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(52, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(51, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(50, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(49, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(48, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ffff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(0, plfr(IOAddress("::"),
+                      IOAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+    // Fail if a network boundary is crossed
+    EXPECT_EQ(-1, plfr(IOAddress("2001:db8::ffff"),
+                       IOAddress("2001:db8::1:1")));
+
+    // Fail if first is not at the begin
+    EXPECT_EQ(-1, plfr(IOAddress("2001:db8::2"), IOAddress("2001:db8::5")));
+    EXPECT_EQ(-1, plfr(IOAddress("2001:db8::2:0"),
+                       IOAddress("2001:db8::5:ffff")));
+    EXPECT_EQ(-1, plfr(IOAddress("2001:db8::2:ff00:0"),
+                       IOAddress("2001:db8::3:00ff:ffff")));
+
+    // The upper bound cannot be smaller than the lower bound
+    EXPECT_THROW(plfr(IOAddress("fe80::5"), IOAddress("fe80::4")),
+                 isc::BadValue);
+
+    // Address family must match
+    EXPECT_THROW(plfr(IOAddress("192.0.2.0"), IOAddress("fe80::1")),
+                 isc::BadValue);
+}
+
+// Checks if prefixInRange returns valid number of prefixes in specified range.
+TEST(AddrUtilitiesTest, prefixesInRange) {
+
+    // How many /64 prefixes are in /64 pool?
+    EXPECT_EQ(1, prefixesInRange(64, 64));
+
+    // How many /63 prefixes are in /64 pool?
+    EXPECT_EQ(2, prefixesInRange(63, 64));
+
+    // How many /64 prefixes are in /48 pool?
+    EXPECT_EQ(65536, prefixesInRange(48, 64));
+
+    // How many /127 prefixes are in /64 pool?
+    EXPECT_EQ(uint64_t(9223372036854775808ull), prefixesInRange(64, 127));
+
+    // How many /128 prefixes are in /64 pool?
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+              prefixesInRange(64, 128));
+
+    // Let's go overboard again. How many IPv6 addresses are there?
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+              prefixesInRange(0, 128));
+
+}
+
+// Checks the function which finds an IPv4 address from input address and offset.
+TEST(AddrUtilitiesTest, offsetIPv4Address) {
+    EXPECT_EQ("10.1.2.46", offsetAddress(IOAddress("10.1.1.45"), 257).toText());
+    EXPECT_EQ("10.1.7.9", offsetAddress(IOAddress("10.1.1.45"), 1500).toText());
+    // Using very large offset. The maximum IPv4 address should be returned.
+    EXPECT_EQ("255.255.255.255", offsetAddress(IOAddress("255.255.254.254"), 0xFFFFFFFFFFFFFFFA).toText());
+}
+
+// Checks the function which finds an IPv6 address from input address and offset.
+TEST(AddrUtilitiesTest, offsetIPv6Address) {
+    EXPECT_EQ("2001:db8:1::4", offsetAddress(IOAddress("2001:db8:1::4"), 0).toText());
+    EXPECT_EQ("2001:db8:1::10:3", offsetAddress(IOAddress("2001:db8:1::4"), 0xFFFFF).toText());
+    EXPECT_EQ("2001:db8:2::", offsetAddress(IOAddress("2001:db8:1:FFFF::1"), 0xFFFFFFFFFFFFFFFF).toText());
+    EXPECT_EQ("3000::1c", offsetAddress(IOAddress("3000::15"), 7).toText());
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/asiolink/asiolink/tests/dummy_io_callback_unittest.cc b/src/lib/asiolink/asiolink/tests/dummy_io_callback_unittest.cc
new file mode 100644 (file)
index 0000000..b836893
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/dummy_io_cb.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace boost::asio;
+
+namespace { // begin unnamed namespace
+
+TEST(DummyIOCallbackTest, throws) {
+    DummyIOCallback cb;
+    boost::system::error_code error_code;
+
+    // All methods should throw isc::Unexpected.
+    EXPECT_THROW(cb(error_code), isc::Unexpected);
+    EXPECT_THROW(cb(error_code, 42), isc::Unexpected);
+}
+
+} // end of unnamed namespace
diff --git a/src/lib/asiolink/asiolink/tests/hash_address_unittest.cc b/src/lib/asiolink/asiolink/tests/hash_address_unittest.cc
new file mode 100644 (file)
index 0000000..50fa43f
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <boost/functional/hash.hpp>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+// In fact the goal i.e. is to check if the file compiles.
+
+using namespace isc::asiolink;
+
+typedef boost::hash<IOAddress> hash_address;
+
+TEST(HashAddressTest, unorderedSet) {
+    std::unordered_set<IOAddress, hash_address> set;
+    EXPECT_TRUE(set.empty());
+    EXPECT_EQ(0, set.size());
+
+    IOAddress addr("192.168.2.1");
+    EXPECT_EQ(set.end(), set.find(addr));
+    EXPECT_EQ(0, set.count(addr));
+
+    EXPECT_NO_THROW(set.insert(addr));
+    EXPECT_FALSE(set.empty());
+    EXPECT_EQ(1, set.size());
+    EXPECT_NE(set.end(), set.find(addr));
+    EXPECT_EQ(1, set.count(addr));
+
+    EXPECT_NO_THROW(set.clear());
+    EXPECT_TRUE(set.empty());
+}
+
+TEST(HashAddressTest, unorderedMap) {
+    std::unordered_map<IOAddress, std::string, hash_address> map;
+    EXPECT_TRUE(map.empty());
+    EXPECT_EQ(0, map.size());
+
+    IOAddress addr("192.168.2.1");
+    EXPECT_EQ(map.end(), map.find(addr));
+    EXPECT_EQ(0, map.count(addr));
+
+    std::string str("my-address");
+    EXPECT_NO_THROW(map[addr] = str);
+    EXPECT_FALSE(map.empty());
+    EXPECT_EQ(1, map.size());
+    EXPECT_NE(map.end(), map.find(addr));
+    EXPECT_EQ(1, map.count(addr));
+
+    EXPECT_NO_THROW(map.clear());
+    EXPECT_TRUE(map.empty());
+}
diff --git a/src/lib/asiolink/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/asiolink/tests/interval_timer_unittest.cc
new file mode 100644 (file)
index 0000000..24e6cdf
--- /dev/null
@@ -0,0 +1,377 @@
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/asiolink.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+    boost::posix_time::milliseconds(50);
+}
+
+using namespace isc::asiolink;
+
+// This fixture is for testing IntervalTimer. Some callback functors are
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+    IntervalTimerTest() :
+        io_service_(), timer_called_(false), timer_cancel_success_(false)
+    {}
+    ~IntervalTimerTest() {}
+    class TimerCallBack : public std::unary_function<void, void> {
+    public:
+        TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+        void operator()() const {
+            test_obj_->timer_called_ = true;
+            test_obj_->io_service_.stop();
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCounter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCounter(IntervalTimerTest* test_obj) :
+            test_obj_(test_obj)
+        {
+            counter_ = 0;
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+        int counter_;
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+                                   IntervalTimer* timer,
+                                   TimerCallBackCounter& counter)
+            : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0),
+              prev_counter_(-1)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Store the value of counter_.counter_.
+                prev_counter_ = counter_.counter_;
+                delete timer_;
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // Stop io_service to stop all timers.
+                test_obj_->io_service_.stop();
+                // Compare the value of counter_.counter_ with stored one.
+                // If TimerCallBackCounter was not called (expected behavior),
+                // they are same.
+                if (counter_.counter_ == prev_counter_) {
+                    test_obj_->timer_cancel_success_ = true;
+                }
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer* timer_;
+        TimerCallBackCounter& counter_;
+        int count_;
+        int prev_counter_;
+    };
+    class TimerCallBackCanceller {
+    public:
+        TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+            counter_(counter), itimer_(itimer)
+        {}
+        void operator()() {
+            ++counter_;
+            itimer_.cancel();
+        }
+    private:
+        unsigned int& counter_;
+        IntervalTimer& itimer_;
+    };
+    class TimerCallBackOverwriter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+                                IntervalTimer& timer)
+            : test_obj_(test_obj), timer_(timer), count_(0)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Call setup() to update callback function to TimerCallBack.
+                test_obj_->timer_called_ = false;
+                timer_.setup(TimerCallBack(test_obj_), 100);
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // If it reaches here, re-setup() is failed (unexpected).
+                // We should stop here.
+                test_obj_->io_service_.stop();
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer& timer_;
+        int count_;
+    };
+    class TimerCallBackAccumulator: public std::unary_function<void, void> {
+    public:
+        TimerCallBackAccumulator(IntervalTimerTest* test_obj, int &counter) :
+            test_obj_(test_obj), counter_(counter) {
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        // Reference to integer accumulator
+        int& counter_;
+    };
+protected:
+    IOService io_service_;
+    bool timer_called_;
+    bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    IntervalTimer itimer(io_service_);
+    // expect throw if call back function is empty
+    EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
+                 isc::InvalidParameter);
+    // expect throw if interval is negative.
+    EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    // Then run IOService and test if the callback function is called.
+    IntervalTimer itimer(io_service_);
+    timer_called_ = false;
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    // setup timer
+    itimer.setup(TimerCallBack(this), 100);
+    EXPECT_EQ(100, itimer.getInterval());
+    io_service_.run();
+    // Control reaches here after io_service_ was stopped by TimerCallBack.
+
+    // delta: difference between elapsed time and 100 milliseconds.
+    boost::posix_time::time_duration test_runtime =
+        boost::posix_time::microsec_clock::universal_time() - start;
+    EXPECT_FALSE(test_runtime.is_negative()) <<
+                 "test duration " << test_runtime <<
+                 " negative - clock skew?";
+    // Expect TimerCallBack is called; timer_called_ is true
+    EXPECT_TRUE(timer_called_);
+    // Expect test_runtime is 100 milliseconds or longer.
+    // Allow 1% of clock skew
+    EXPECT_TRUE(test_runtime >= boost::posix_time::milliseconds(99)) <<
+                "test runtime " << test_runtime.total_milliseconds() <<
+                "msec " << ">= 100";
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+    // This code isn't exception safe, but we'd rather keep the code
+    // simpler and more readable as this is only for tests and if it throws
+    // the program would immediately terminate anyway.
+
+    // The call back function will not be called after the timer is
+    // destroyed.
+    //
+    // There are two timers:
+    //  itimer_counter (A)
+    //   (Calls TimerCallBackCounter)
+    //     - increments internal counter in callback function
+    //  itimer_canceller (B)
+    //   (Calls TimerCallBackCancelDeleter)
+    //     - first time of callback, it stores the counter value of
+    //       callback_canceller and destroys itimer_counter
+    //     - second time of callback, it compares the counter value of
+    //       callback_canceller with stored value
+    //       if they are same the timer was not called; expected result
+    //       if they are different the timer was called after destroyed
+    //
+    //     0  100  200  300  400  500  600 (ms)
+    // (A) i--------+----x
+    //                   ^
+    //                   |destroy itimer_counter
+    // (B) i-------------+--------------s
+    //                                  ^stop io_service
+    //                                   and check if itimer_counter have been
+    //                                   stopped
+
+    // itimer_counter will be deleted in TimerCallBackCancelDeleter
+    IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+    IntervalTimer itimer_canceller(io_service_);
+    timer_cancel_success_ = false;
+    TimerCallBackCounter callback_canceller(this);
+    itimer_counter->setup(callback_canceller, 200);
+    itimer_canceller.setup(
+        TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
+        300);
+    io_service_.run();
+    EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, cancel) {
+    // Similar to destructIntervalTimer test, but the first timer explicitly
+    // cancels itself on first callback.
+    IntervalTimer itimer_counter(io_service_);
+    IntervalTimer itimer_watcher(io_service_);
+    unsigned int counter = 0;
+    itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
+    itimer_watcher.setup(TimerCallBack(this), 200);
+    io_service_.run();
+    EXPECT_EQ(1, counter);
+    EXPECT_EQ(0, itimer_counter.getInterval());
+
+    // canceling an already canceled timer shouldn't cause any surprise.
+    EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+    // Call setup() multiple times to update call back function and interval.
+    //
+    // There are two timers:
+    //  itimer (A)
+    //   (Calls TimerCallBackCounter / TimerCallBack)
+    //     - increments internal counter in callback function
+    //       (TimerCallBackCounter)
+    //       interval: 300 milliseconds
+    //     - io_service_.stop() (TimerCallBack)
+    //       interval: 100 milliseconds
+    //  itimer_overwriter (B)
+    //   (Calls TimerCallBackOverwriter)
+    //     - first time of callback, it calls setup() to change call back
+    //       function to TimerCallBack and interval of itimer to 100
+    //       milliseconds
+    //       after 300 + 100 milliseconds from the beginning of this test,
+    //       TimerCallBack() will be called and io_service_ stops.
+    //     - second time of callback, it means the test fails.
+    //
+    //     0  100  200  300  400  500  600  700  800 (ms)
+    // (A) i-------------+----C----s
+    //                        ^    ^stop io_service
+    //                        |change call back function and interval
+    // (B) i------------------+-------------------S
+    //                                            ^(stop io_service on fail)
+    //
+
+    IntervalTimer itimer(io_service_);
+    IntervalTimer itimer_overwriter(io_service_);
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    itimer.setup(TimerCallBackCounter(this), 300);
+    itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
+    io_service_.run();
+    // Control reaches here after io_service_ was stopped by
+    // TimerCallBackCounter or TimerCallBackOverwriter.
+
+    // Expect callback function is updated: TimerCallBack is called
+    EXPECT_TRUE(timer_called_);
+    // Expect interval is updated: return value of getInterval() is updated
+    EXPECT_EQ(itimer.getInterval(), 100);
+}
+
+// This test verifies that timers operate correctly based on their mode.
+TEST_F(IntervalTimerTest, intervalModeTest) {
+    // Create a timer to control the duration of the test.
+    IntervalTimer test_timer(io_service_);
+    test_timer.setup(TimerCallBack(this), 2000);
+
+    // Create an timer which automatically reschedules itself.  Use the
+    // accumulator callback to increment local counter for it.
+    int repeater_count = 0;
+    IntervalTimer repeater(io_service_);
+    repeater.setup(TimerCallBackAccumulator(this, repeater_count), 10);
+
+    // Create a one-shot timer. Use the accumulator callback to increment
+    // local counter variable for it.
+    int one_shot_count = 0;
+    IntervalTimer one_shot(io_service_);
+    one_shot.setup(TimerCallBackAccumulator(this, one_shot_count), 10,
+                   IntervalTimer::ONE_SHOT);
+
+    // As long as service runs at least one event handler, loop until
+    // we've hit our goals.  It won't return zero unless is out of
+    // work or the the service has been stopped by the test timer.
+    int cnt = 0;
+    while (((cnt = io_service_.get_io_service().run_one()) > 0)
+           && (repeater_count < 5)) {
+        // deliberately empty
+    };
+
+    // If cnt is zero, then something went wrong.
+    EXPECT_TRUE(cnt > 0);
+
+    // The loop stopped make sure it was for the right reason.
+    EXPECT_EQ(repeater_count, 5);
+    EXPECT_EQ(one_shot_count, 1);
+}
+
+// This test verifies that the same timer can be reused in either mode.
+TEST_F(IntervalTimerTest, timerReuseTest) {
+    // Create a timer to control the duration of the test.
+    IntervalTimer test_timer(io_service_);
+    test_timer.setup(TimerCallBack(this), 2000);
+
+    // Create a one-shot timer. Use the accumulator callback to increment
+    // local counter variable for it.
+    int one_shot_count = 0;
+    IntervalTimer one_shot(io_service_);
+    TimerCallBackAccumulator callback(this, one_shot_count);
+    one_shot.setup(callback, 10, IntervalTimer::ONE_SHOT);
+
+    // Run until a single event handler executes.  This should be our
+    // one-shot expiring.
+    io_service_.run_one();
+
+    // Verify the timer expired once.
+    ASSERT_EQ(one_shot_count, 1);
+
+    // Setup the one-shot to go again.
+    one_shot.setup(callback, 10, IntervalTimer::ONE_SHOT);
+
+    // Run until a single event handler executes.  This should be our
+    // one-shot expiring.
+    io_service_.run_one();
+
+    // Verify the timer expired once.
+    ASSERT_EQ(one_shot_count, 2);
+
+    // Setup the timer to be repeating.
+    one_shot.setup(callback, 10, IntervalTimer::REPEATING);
+
+    // As long as service runs at least one event handler, loop until
+    // we've hit our goals.  It won't return zero unless is out of
+    // work or the the service has been stopped by the test timer.
+    int cnt = 0;
+    while ((cnt = io_service_.get_io_service().run_one())
+            && (one_shot_count < 4)) {
+        // deliberately empty
+    };
+
+    // If cnt is zero, then something went wrong.
+    EXPECT_TRUE(cnt > 0);
+
+    // Verify the timer repeated.
+    EXPECT_GE(one_shot_count, 4);
+}
diff --git a/src/lib/asiolink/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/asiolink/tests/io_address_unittest.cc
new file mode 100644 (file)
index 0000000..79888d2
--- /dev/null
@@ -0,0 +1,341 @@
+// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_error.h>
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+
+#include <algorithm>
+#include <cstring>
+#include <vector>
+#include <sstream>
+
+using namespace isc::asiolink;
+
+TEST(IOAddressTest, fromText) {
+    IOAddress io_address_v4("192.0.2.1");
+    EXPECT_EQ("192.0.2.1", io_address_v4.toText());
+
+    IOAddress io_address_v6("2001:db8::1234");
+    EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
+
+    // bogus IPv4 address-like input
+    EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
+
+    // bogus IPv4 address-like input: out-of-range octet
+    EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
+
+    // bogus IPv6 address-like input
+    EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
+
+    // bogus IPv6 address-like input
+    EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
+}
+
+TEST(IOAddressTest, Equality) {
+    EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
+    EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
+
+    EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
+    EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
+
+    EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
+    EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
+
+    EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
+    EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
+
+    EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
+    EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
+}
+
+TEST(IOAddressTest, Family) {
+    EXPECT_EQ(AF_INET, IOAddress("192.0.2.1").getFamily());
+    EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily());
+}
+
+TEST(IOAddressTest, fromBytes) {
+    // 2001:db8:1::dead:beef
+    uint8_t v6[] = {
+        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
+        0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef };
+
+    uint8_t v4[] = { 192, 0 , 2, 3 };
+
+    IOAddress addr("::");
+    EXPECT_NO_THROW({
+        addr = IOAddress::fromBytes(AF_INET6, v6);
+    });
+    EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
+
+    EXPECT_NO_THROW({
+        addr = IOAddress::fromBytes(AF_INET, v4);
+    });
+    EXPECT_EQ(addr, IOAddress("192.0.2.3"));
+}
+
+TEST(IOAddressTest, toBytesV4) {
+    // Address and network byte-order representation of the address.
+    const char* V4STRING = "192.0.2.1";
+    uint8_t V4[] = {0xc0, 0x00, 0x02, 0x01};
+
+    std::vector<uint8_t> actual = IOAddress(V4STRING).toBytes();
+    ASSERT_EQ(sizeof(V4), actual.size());
+    EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V4));
+}
+
+TEST(IOAddressTest, toBytesV6) {
+    // Address and network byte-order representation of the address.
+    const char* V6STRING = "2001:db8:1::dead:beef";
+    uint8_t V6[] = {
+        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef
+    };
+
+    std::vector<uint8_t> actual = IOAddress(V6STRING).toBytes();
+    ASSERT_EQ(sizeof(V6), actual.size());
+    EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V6));
+}
+
+TEST(IOAddressTest, isV4) {
+    const IOAddress address4("192.0.2.1");
+    const IOAddress address6("2001:db8:1::dead:beef");
+
+    EXPECT_TRUE(address4.isV4());
+    EXPECT_FALSE(address6.isV4());
+}
+
+TEST(IOAddressTest, isV4Zero) {
+    // 0.0.0.0
+    const IOAddress address_zero("0.0.0.0");
+    EXPECT_TRUE(address_zero.isV4Zero());
+    // :: (v6 zero address)
+    const IOAddress address_zero_v6("::");
+    EXPECT_FALSE(address_zero_v6.isV4Zero());
+    // 192.0.2.3
+    const IOAddress address_non_zero("192.0.2.3");
+    EXPECT_FALSE(address_non_zero.isV4Zero());
+    // 0.0.0.100
+    const IOAddress address_non_zero1("0.0.0.100");
+    EXPECT_FALSE(address_non_zero1.isV4Zero());
+    // 64.0.0.0
+    const IOAddress address_non_zero2("64.0.0.0");
+    EXPECT_FALSE(address_non_zero2.isV4Zero());
+}
+
+TEST(IOAddressTest, isV4Bcast) {
+    // 255.255.255.255
+    const IOAddress address_bcast("255.255.255.255");
+    EXPECT_TRUE(address_bcast.isV4Bcast());
+    // 10.2.3.4
+    const IOAddress address_non_bcast("10.2.3.4");
+    EXPECT_FALSE(address_non_bcast.isV4Bcast());
+    // 255.255.255.23
+    const IOAddress address_non_bcast1("255.255.255.23");
+    EXPECT_FALSE(address_non_bcast1.isV4Bcast());
+    // 123.255.255.255
+    const IOAddress address_non_bcast2("123.255.255.255");
+    EXPECT_FALSE(address_non_bcast2.isV4Bcast());
+
+}
+
+TEST(IOAddressTest, isV6) {
+    const IOAddress address4("192.0.2.1");
+    const IOAddress address6("2001:db8:1::dead:beef");
+
+    EXPECT_FALSE(address4.isV6());
+    EXPECT_TRUE(address6.isV6());
+}
+
+TEST(IOAddressTest, isV6Zero) {
+    // ::
+    const IOAddress address_zero("::");
+    EXPECT_TRUE(address_zero.isV6Zero());
+    // 0.0.0.0
+    const IOAddress address_non_zero("0.0.0.0");
+    EXPECT_FALSE(address_non_zero.isV6Zero());
+    // ::ff
+    const IOAddress address_non_zero1("::ff");
+    EXPECT_FALSE(address_non_zero1.isV6Zero());
+    // ff::
+    const IOAddress address_non_zero2("ff::");
+    EXPECT_FALSE(address_non_zero2.isV6Zero());
+}
+
+TEST(IOAddressTest, uint32) {
+    IOAddress addr1("192.0.2.5");
+
+    // operator uint_32() is used here
+    uint32_t tmp = addr1.toUint32();
+
+    uint32_t expected = (192U << 24) +  (0U << 16) + (2U << 8) + 5U;
+
+    EXPECT_EQ(expected, tmp);
+
+    // now let's try opposite conversion
+    IOAddress addr3 = IOAddress(expected);
+
+    EXPECT_EQ(addr3.toText(), "192.0.2.5");
+}
+
+TEST(IOAddressTest, lessThanEqual) {
+    IOAddress addr1("192.0.2.5");
+    IOAddress addr2("192.0.2.6");
+    IOAddress addr3("0.0.0.0");
+
+    IOAddress addr4("::");
+    IOAddress addr5("2001:db8::1");
+    IOAddress addr6("2001:db8::1:0");
+    IOAddress addr7("2001:db8::1:0"); // the same as 6
+
+    // v4 comparisons
+    EXPECT_TRUE(addr1 < addr2);
+    EXPECT_FALSE(addr2 < addr1);
+    EXPECT_FALSE(addr2 <= addr1);
+    EXPECT_TRUE(addr3 < addr1);
+    EXPECT_TRUE(addr3 < addr2);
+    EXPECT_TRUE(addr3 <= addr2);
+
+    // v6 comparisons
+    EXPECT_TRUE(addr4 < addr5);
+    EXPECT_TRUE(addr5 < addr6);
+    EXPECT_FALSE(addr6 < addr5);
+    EXPECT_FALSE(addr6 <= addr5);
+
+    // v4 to v6 - v4 should always be smaller
+    EXPECT_TRUE(addr1 < addr4);
+    EXPECT_TRUE(addr3 < addr4);
+    EXPECT_TRUE(addr2 < addr5);
+
+    EXPECT_TRUE(addr6 <= addr7);
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST(IOAddressTest, LeftShiftOperator) {
+    const IOAddress addr("192.0.2.5");
+
+    std::ostringstream oss;
+    oss << addr;
+    EXPECT_EQ(addr.toText(), oss.str());
+}
+
+// Tests address classification methods (which were previously used by accessing
+// underlying asio objects directly)
+TEST(IOAddressTest, accessClassificationMethods) {
+    IOAddress addr1("192.0.2.5"); // IPv4
+    IOAddress addr2("::");  // IPv6
+    IOAddress addr3("2001:db8::1"); // global IPv6
+    IOAddress addr4("fe80::1234");  // link-local
+    IOAddress addr5("ff02::1:2");   // multicast
+
+    EXPECT_TRUE (addr1.isV4());
+    EXPECT_FALSE(addr1.isV6());
+    EXPECT_FALSE(addr1.isV6LinkLocal());
+    EXPECT_FALSE(addr1.isV6Multicast());
+
+    EXPECT_FALSE(addr2.isV4());
+    EXPECT_TRUE (addr2.isV6());
+    EXPECT_FALSE(addr2.isV6LinkLocal());
+    EXPECT_FALSE(addr2.isV6Multicast());
+
+    EXPECT_FALSE(addr3.isV4());
+    EXPECT_TRUE (addr3.isV6());
+    EXPECT_FALSE(addr3.isV6LinkLocal());
+    EXPECT_FALSE(addr3.isV6Multicast());
+
+    EXPECT_FALSE(addr4.isV4());
+    EXPECT_TRUE (addr4.isV6());
+    EXPECT_TRUE (addr4.isV6LinkLocal());
+    EXPECT_FALSE(addr4.isV6Multicast());
+
+    EXPECT_FALSE(addr5.isV4());
+    EXPECT_TRUE (addr5.isV6());
+    EXPECT_FALSE(addr5.isV6LinkLocal());
+    EXPECT_TRUE (addr5.isV6Multicast());
+}
+
+TEST(IOAddressTest, staticAddresses) {
+    EXPECT_EQ(IOAddress("0.0.0.0"), IOAddress::IPV4_ZERO_ADDRESS());
+    EXPECT_EQ(IOAddress("255.255.255.255"), IOAddress::IPV4_BCAST_ADDRESS());
+    EXPECT_EQ(IOAddress("::"), IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+// Tests whether address subtraction works correctly.
+TEST(IOAddressTest, subtract) {
+    IOAddress addr1("192.0.2.12");
+    IOAddress addr2("192.0.2.5");
+    IOAddress addr3("192.0.2.0");
+    IOAddress addr4("0.0.2.1");
+    IOAddress any4("0.0.0.0");
+    IOAddress bcast("255.255.255.255");
+
+    EXPECT_EQ("0.0.0.7", IOAddress::subtract(addr1, addr2).toText());
+    EXPECT_EQ("0.0.0.12", IOAddress::subtract(addr1, addr3).toText());
+
+    // Subtracting 0.0.0.0 is like subtracting 0.
+    EXPECT_EQ("192.0.2.12", IOAddress::subtract(addr1, any4).toText());
+    EXPECT_EQ("192.0.2.13", IOAddress::subtract(addr1, bcast).toText());
+    EXPECT_EQ("191.255.255.255", IOAddress::subtract(addr3, addr4).toText());
+
+    // Let's check if we can subtract greater address from smaller.
+    // This will check if we can "loop"
+    EXPECT_EQ("255.255.255.251", IOAddress::subtract(addr3, addr2).toText());
+
+    IOAddress addr6("fe80::abcd");
+    IOAddress addr7("fe80::");
+    IOAddress addr8("fe80::1234");
+    IOAddress addr9("2001:db8::face");
+    IOAddress addr10("2001:db8::ffff:ffff:ffff:ffff");
+    IOAddress addr11("::1");
+    IOAddress any6("::");
+
+    EXPECT_EQ(IOAddress("::abcd"), IOAddress::subtract(addr6, addr7));
+    EXPECT_EQ(IOAddress("::9999"), IOAddress::subtract(addr6, addr8));
+    EXPECT_EQ("::ffff:ffff:ffff:531", IOAddress::subtract(addr10, addr9).toText());
+
+    // Subtract with borrow, extreme edition. Need to borrow one bit
+    // 112 times.
+    EXPECT_EQ("fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+              IOAddress::subtract(addr7, addr11).toText());
+
+    // Now check if we can loop beyond :: (:: - ::1 is a lot of F's)
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+              IOAddress::subtract(any6, addr11).toText());
+
+    // Subtracting :: is like subtracting 0.
+    EXPECT_EQ("2001:db8::face", IOAddress::subtract(addr9, any6).toText());
+
+    // Let's check if we can subtract greater address from smaller.
+    // This will check if we can "loop"
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:edcc",
+              IOAddress::subtract(addr7, addr8).toText());
+
+    // Inter-family relations are not allowed.
+    EXPECT_THROW(IOAddress::subtract(addr1, addr6), isc::BadValue);
+    EXPECT_THROW(IOAddress::subtract(addr6, addr1), isc::BadValue);
+}
+
+// Test checks whether an address can be increased.
+TEST(IOAddressTest, increaseAddr) {
+    IOAddress addr1("192.0.2.12");
+    IOAddress any4("0.0.0.0");
+    IOAddress bcast("255.255.255.255");
+    IOAddress addr6("2001:db8::ffff:ffff:ffff:ffff");
+    IOAddress addr11("::1");
+    IOAddress any6("::");
+    IOAddress the_last_one("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+
+    EXPECT_EQ("192.0.2.13", IOAddress::increase(addr1).toText());
+    EXPECT_EQ("0.0.0.1", IOAddress::increase(any4).toText());
+    EXPECT_EQ("0.0.0.0", IOAddress::increase(bcast).toText());
+    EXPECT_EQ("2001:db8:0:1::", IOAddress::increase(addr6).toText());
+    EXPECT_EQ(IOAddress("::2"), IOAddress::increase(addr11));
+    EXPECT_EQ(IOAddress("::1"), IOAddress::increase(any6));
+    EXPECT_EQ(IOAddress("::"), IOAddress::increase(the_last_one));
+}
diff --git a/src/lib/asiolink/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/asiolink/tests/io_endpoint_unittest.cc
new file mode 100644 (file)
index 0000000..314a6a0
--- /dev/null
@@ -0,0 +1,286 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <sstream>
+#include <string>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <string.h>
+
+using namespace isc::asiolink;
+
+namespace {
+typedef boost::shared_ptr<const IOEndpoint> ConstIOEndpointPtr;
+
+TEST(IOEndpointTest, createUDPv4) {
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("192.0.2.1"), 53210));
+    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+    EXPECT_EQ(53210, ep->getPort());
+    EXPECT_EQ(AF_INET, ep->getFamily());
+    EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+    EXPECT_EQ(static_cast<short>(IPPROTO_UDP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv4) {
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_TCP,
+                                             IOAddress("192.0.2.1"), 5301));
+    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+    EXPECT_EQ(5301, ep->getPort());
+    EXPECT_EQ(AF_INET, ep->getFamily());
+    EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+    EXPECT_EQ(static_cast<short>(IPPROTO_TCP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createUDPv6) {
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("2001:db8::1234"),
+                                             5302));
+    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+    EXPECT_EQ(5302, ep->getPort());
+    EXPECT_EQ(AF_INET6, ep->getFamily());
+    EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+    EXPECT_EQ(static_cast<short>(IPPROTO_UDP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv6) {
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_TCP,
+                                             IOAddress("2001:db8::1234"),
+                                             5303));
+    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+    EXPECT_EQ(5303, ep->getPort());
+    EXPECT_EQ(AF_INET6, ep->getFamily());
+    EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+    EXPECT_EQ(static_cast<short>(IPPROTO_TCP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, equality) {
+    std::vector<ConstIOEndpointPtr> epv;
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1234"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1234"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1234"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1234"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1235"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1235"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1235"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1235"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.1"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.1"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.1"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.1"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.2"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.2"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.2"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.2"), 5304)));
+
+    for (size_t i = 0; i < epv.size(); ++i) {
+        for (size_t j = 0; j < epv.size(); ++j) {
+            if (i != j) {
+                // We use EXPECT_TRUE/FALSE instead of _EQ here, since
+                // _EQ requires there is an operator<< as well
+                EXPECT_FALSE(*epv[i] == *epv[j]);
+                EXPECT_TRUE(*epv[i] != *epv[j]);
+            }
+        }
+    }
+
+    // Create a second array with exactly the same values. We use create()
+    // again to make sure we get different endpoints
+    std::vector<ConstIOEndpointPtr> epv2;
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1234"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1234"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1234"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1234"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1235"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1235"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1235"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1235"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("192.0.2.1"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("192.0.2.1"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"),
+                                          5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"),
+                                          5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"),
+                                          5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"),
+                                          5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"),
+                                          5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"),
+                                          5304)));
+
+    for (size_t i = 0; i < epv.size(); ++i) {
+        EXPECT_TRUE(*epv[i] == *epv2[i]);
+        EXPECT_FALSE(*epv[i] != *epv2[i]);
+    }
+}
+
+TEST(IOEndpointTest, createIPProto) {
+    EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
+                                    53210)->getAddress().toText(),
+                 IOError);
+}
+
+void
+sockAddrMatch(const struct sockaddr& actual_sa,
+              const char* const expected_addr_text,
+              const char* const expected_port_text)
+{
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_DGRAM; // this shouldn't matter
+    hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+    struct addrinfo* res;
+    ASSERT_EQ(0, getaddrinfo(expected_addr_text, expected_port_text, &hints,
+                             &res));
+    EXPECT_EQ(res->ai_family, actual_sa.sa_family);
+#ifdef HAVE_SA_LEN
+    // ASIO doesn't seem to set sa_len, so we set it to the expected value
+    res->ai_addr->sa_len = actual_sa.sa_len;
+#endif
+    EXPECT_EQ(0, memcmp(res->ai_addr, &actual_sa, res->ai_addrlen));
+    freeaddrinfo(res);
+}
+
+TEST(IOEndpointTest, getSockAddr) {
+    // UDP/IPv4
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("192.0.2.1"), 53210));
+    sockAddrMatch(ep->getSockAddr(), "192.0.2.1", "53210");
+
+    // UDP/IPv6
+    ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+    sockAddrMatch(ep->getSockAddr(), "2001:db8::53", "53");
+
+    // TCP/IPv4
+    ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 53211));
+    sockAddrMatch(ep->getSockAddr(), "192.0.2.2", "53211");
+
+    // TCP/IPv6
+    ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::5300"), 35));
+    sockAddrMatch(ep->getSockAddr(), "2001:db8::5300", "35");
+}
+
+// A faked IOEndpoint for an uncommon address family.  It wouldn't be possible
+// to create via the normal factory, so we define a special derived class
+// for it.
+class TestIOEndpoint : public IOEndpoint {
+    virtual IOAddress getAddress() const {
+        return IOAddress("2001:db8::bad:add");
+    }
+    virtual uint16_t getPort() const { return (42); }
+    virtual short getProtocol() const { return (IPPROTO_UDP); }
+    virtual short getFamily() const { return (AF_UNSPEC); }
+    virtual const struct sockaddr& getSockAddr() const {
+        static struct sockaddr sa_placeholder;
+        return (sa_placeholder);
+    }
+};
+
+void
+checkEndpointText(const std::string& expected, const IOEndpoint& ep) {
+    std::ostringstream oss;
+    oss << ep;
+    EXPECT_EQ(expected, oss.str());
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST(IOEndpointTest, LeftShiftOperator) {
+    // UDP/IPv4
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("192.0.2.1"), 53210));
+    checkEndpointText("192.0.2.1:53210", *ep);
+
+    // UDP/IPv6
+    ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+    checkEndpointText("[2001:db8::53]:53", *ep);
+
+    // Same for TCP: shouldn't be different
+    ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 53210));
+    checkEndpointText("192.0.2.1:53210", *ep);
+    ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::53"), 53));
+    checkEndpointText("[2001:db8::53]:53", *ep);
+
+    // Uncommon address family.  The actual behavior doesn't matter much
+    // in practice, but we check such input doesn't make it crash.
+    // We explicitly instantiate the test EP because otherwise some compilers
+    // would be confused and complain.
+    TestIOEndpoint test_ep;
+    checkEndpointText("2001:db8::bad:add:42", test_ep);
+}
+}
diff --git a/src/lib/asiolink/asiolink/tests/io_service_signal_unittests.cc b/src/lib/asiolink/asiolink/tests/io_service_signal_unittests.cc
new file mode 100644 (file)
index 0000000..5f35359
--- /dev/null
@@ -0,0 +1,280 @@
+// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service_signal.h>
+#include <asiolink/testutils/timed_signal.h>
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <queue>
+
+using namespace isc::asiolink::test;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Test fixture for testing the use of IO service signals.
+///
+/// This fixture is exercises IO service signaling.
+class IOSignalTest : public ::testing::Test {
+public:
+    /// @brief IOService instance to process IO.
+    asiolink::IOServicePtr io_service_;
+
+    /// @brief Failsafe timer to ensure test(s) do not hang.
+    isc::asiolink::IntervalTimer test_timer_;
+
+    /// @brief Maximum time should be allowed to run.
+    int test_time_ms_;
+
+    /// @brief IOSignalSet object.
+    IOSignalSetPtr io_signal_set_;
+
+    /// @brief Vector to record the signal values received.
+    std::vector<int> processed_signals_;
+
+    /// @brief The number of signals that must be received to stop the test.
+    int stop_at_count_;
+
+    /// @brief Boolean which causes IOSignalHandler to throw if true.
+    bool handler_throw_error_;
+
+    /// @brief Constructor.
+    IOSignalTest() :
+        io_service_(new asiolink::IOService()), test_timer_(*io_service_),
+        test_time_ms_(0), io_signal_set_(),
+        processed_signals_(), stop_at_count_(0), handler_throw_error_(false) {
+
+        io_signal_set_.reset(new IOSignalSet(
+                                     io_service_,
+                                     std::bind(&IOSignalTest::processSignal,
+                                               this, ph::_1)));
+    }
+
+    /// @brief Destructor.
+    ~IOSignalTest() {}
+
+    /// @brief Method used as the IOSignalSet handler.
+    ///
+    /// Records the value of the given signal and checks if the desired
+    /// number of signals have been received.  If so, the IOService is
+    /// stopped which will cause IOService::run() to exit, returning control
+    /// to the test.
+    ///
+    /// @param signum signal number.
+    void processSignal(int signum) {
+        // Remember the signal we got.
+        processed_signals_.push_back(signum);
+
+        // If the flag is on, force a throw to test error handling.
+        if (handler_throw_error_) {
+            handler_throw_error_ = false;
+            isc_throw(BadValue, "processSignal throwing simulated error");
+        }
+
+        // If we've hit the number we want stop the IOService. This will cause
+        // run to exit.
+        if (processed_signals_.size() >= stop_at_count_) {
+            io_service_->stop();
+        }
+    }
+
+    /// @brief Sets the failsafe timer for the test to the given time.
+    ///
+    /// @param  test_time_ms maximum time in milliseconds the test should
+    /// be allowed to run.
+    void setTestTime(int test_time_ms) {
+        // Fail safe shutdown
+        test_time_ms_ = test_time_ms;
+        test_timer_.setup(std::bind(&IOSignalTest::testTimerHandler, this),
+                          test_time_ms_, asiolink::IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Failsafe timer expiration handler.
+    void testTimerHandler() {
+        io_service_->stop();
+        FAIL() << "Test Time: " << test_time_ms_ << " expired";
+    }
+};
+
+// Test the basic mechanics of IOSignal by handling one signal occurrence.
+TEST_F(IOSignalTest, singleSignalTest) {
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // Register to receive SIGINT.
+    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+
+    // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
+    TimedSignal sig_int(*io_service_, SIGINT, 100);
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    // Verify that we processed the signal.
+    ASSERT_EQ(1, processed_signals_.size());
+
+    // Now check that signal value is correct.
+    EXPECT_EQ(SIGINT, processed_signals_[0]);
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // Unregister the receive of SIGINT.
+    ASSERT_NO_THROW(io_signal_set_->remove(SIGINT));
+
+    // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
+    TimedSignal sig_int_too_late(*io_service_, SIGINT, 100);
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    // Verify that we did not process the signal.
+    ASSERT_EQ(1, processed_signals_.size());
+}
+
+// Test verifies that signals can be delivered rapid-fire without falling over.
+TEST_F(IOSignalTest, hammer) {
+    // Set test fail safe.  We want to allow at least 100 ms per signal,
+    // plus a bit more so 6 seconds ought to be enough.
+    setTestTime(6000);
+
+    // Register to receive SIGINT.
+    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+
+    // Stop the test after 50 signals. This allows 100ms+ per signal
+    // so even sluggish VMs should handle it.
+    stop_at_count_ = 50;
+
+    // User a repeating TimedSignal so we should generate a signal every 1 ms
+    // until we hit our stop count.
+    TimedSignal sig_int(*io_service_, SIGINT, 1,
+                        asiolink::IntervalTimer::REPEATING);
+
+    // Start processing IO.  This should continue until we stop either by
+    // hitting the stop count or if things go wrong, max test time.
+    io_service_->run();
+
+    // Verify we received the expected number of signals.
+    EXPECT_EQ(stop_at_count_, processed_signals_.size());
+
+    // Now check that each signal value is correct. This is sort of a silly
+    // check but it does ensure things didn't go off the rails somewhere.
+    for (int i = 0; i < processed_signals_.size(); ++i) {
+        EXPECT_EQ(SIGINT, processed_signals_[i]);
+    }
+}
+
+// Verifies that handler exceptions are caught.
+TEST_F(IOSignalTest, handlerThrow) {
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // Register to receive SIGINT.
+    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+
+    // Set the stop after we've done at least 1 all the way through.
+    stop_at_count_ = 1;
+
+    // Use TimedSignal to generate SIGINT after we start IOService::run.
+    TimedSignal sig_int(*io_service_, SIGINT, 100,
+                        asiolink::IntervalTimer::REPEATING);
+
+    // Set the test flag to cause the handler to throw an exception.
+    handler_throw_error_ = true;
+
+    // Start processing IO.  This should fail with the handler exception.
+    ASSERT_NO_THROW(io_service_->run());
+
+    // Verify that the we hit the throw block.  The flag will be false
+    // we will have skipped the stop count check so number signals processed
+    // is stop_at_count_ + 1.
+    EXPECT_FALSE(handler_throw_error_);
+    EXPECT_EQ(stop_at_count_ + 1, processed_signals_.size());
+}
+
+// Verifies that we can handle a mixed set of signals.
+TEST_F(IOSignalTest, mixedSignals) {
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // Register to receive SIGINT, SIGUSR1, and SIGUSR2.
+    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+    ASSERT_NO_THROW(io_signal_set_->add(SIGUSR1));
+    ASSERT_NO_THROW(io_signal_set_->add(SIGUSR2));
+
+    // Stop the test after 21 signals.  Needs to be a multiple of 3.
+    stop_at_count_ = 21;
+
+    // User a repeating TimedSignal so we should generate a signal every 1 ms
+    // until we hit our stop count.
+    TimedSignal sig_1(*io_service_, SIGINT, 1,
+                      asiolink::IntervalTimer::REPEATING);
+    TimedSignal sig_2(*io_service_, SIGUSR1, 1,
+                      asiolink::IntervalTimer::REPEATING);
+    TimedSignal sig_3(*io_service_, SIGUSR2, 1,
+                      asiolink::IntervalTimer::REPEATING);
+
+    // Start processing IO.  This should continue until we stop either by
+    // hitting the stop count or if things go wrong, max test time.
+    io_service_->run();
+
+    // Verify we received the expected number of signals.
+    ASSERT_EQ(stop_at_count_, processed_signals_.size());
+
+    // There is no guarantee that the signals will always be delivered in the
+    // order they are raised, but all of them should get delivered.  Loop
+    // through and tally them up.
+    int sigint_cnt = 0;
+    int sigusr1_cnt = 0;
+    int sigusr2_cnt = 0;
+    for (int i = 0; i < stop_at_count_; ++i) {
+        switch (processed_signals_[i]) {
+        case SIGINT:
+            ++sigint_cnt;
+            break;
+        case SIGUSR1:
+            ++sigusr1_cnt;
+            break;
+        case SIGUSR2:
+            ++sigusr2_cnt;
+            break;
+        default:
+            FAIL() << "Invalid signal value: "
+                   << processed_signals_[i]
+                   << " at i:" << i;
+            break;
+        }
+    }
+
+    // See if our counts are correct.
+    EXPECT_EQ(sigint_cnt, (stop_at_count_/3));
+    EXPECT_EQ(sigusr1_cnt, (stop_at_count_/3));
+    EXPECT_EQ(sigusr2_cnt, (stop_at_count_/3));
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/asiolink/tests/io_service_unittest.cc
new file mode 100644 (file)
index 0000000..05fc5ac
--- /dev/null
@@ -0,0 +1,48 @@
+// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+
+#include <gtest/gtest.h>
+#include <functional>
+#include <vector>
+
+using namespace isc::asiolink;
+
+namespace {
+
+void
+postedEvent(std::vector<int>* destination, int value) {
+    destination->push_back(value);
+}
+
+// Check the posted events are called, in the same order they are posted.
+TEST(IOService, post) {
+    std::vector<int> called;
+    IOService service;
+    // Post two events
+    service.post(std::bind(&postedEvent, &called, 1));
+    service.post(std::bind(&postedEvent, &called, 2));
+    service.post(std::bind(&postedEvent, &called, 3));
+    // They have not yet been called
+    EXPECT_TRUE(called.empty());
+    // Process two events
+    service.run_one();
+    service.run_one();
+    // Both events were called in the right order
+    ASSERT_EQ(2, called.size());
+    EXPECT_EQ(1, called[0]);
+    EXPECT_EQ(2, called[1]);
+
+    // Test that poll() executes the last handler.
+    service.poll();
+    ASSERT_EQ(3, called.size());
+    EXPECT_EQ(3, called[2]);
+}
+
+}
diff --git a/src/lib/asiolink/asiolink/tests/io_socket_unittest.cc b/src/lib/asiolink/asiolink/tests/io_socket_unittest.cc
new file mode 100644 (file)
index 0000000..659a6a6
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_socket.h>
+
+#include <gtest/gtest.h>
+#include <netinet/in.h>
+
+using namespace isc::asiolink;
+
+TEST(IOSocketTest, dummySockets) {
+    EXPECT_EQ(static_cast<short>(IPPROTO_UDP),
+              IOSocket::getDummyUDPSocket().getProtocol());
+    EXPECT_EQ(static_cast<short>(IPPROTO_TCP),
+              IOSocket::getDummyTCPSocket().getProtocol());
+    EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
+    EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
+}
+
+
diff --git a/src/lib/asiolink/asiolink/tests/process_spawn_app.sh.in b/src/lib/asiolink/asiolink/tests/process_spawn_app.sh.in
new file mode 100644 (file)
index 0000000..e3d525c
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+# Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+# This script is used for testing the ProcessSpawn utility class. This
+# class is used to fork and execute a new process. It also allows for
+# checking the exit code returned when the process terminates.
+# The unit tests execute this script via ProcessSpawn class with
+# different command line parameters to test the class functionality.
+#
+# In particular, they check if the class correctly records the exit code
+# returned. The exit code returned is controlled by the caller. It is
+# possible to explicitly specify the exit code to be returned using
+# the command line options. It is also possible to specify that the
+# exit code is "unique" for the process, so as the test can check
+# that two distinct processes spawned by the same ProcessSpawn
+# object may return different status code. The command line of this
+# script also allows for forcing the process to sleep so as the
+# test has much enough time to verify that the convenience methods
+# checking the state of the process, i.e. process running or not.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+exit_code=
+
+while test "${#}" -gt 0
+do
+    option=${1}
+    case ${option} in
+        -p)
+            exit_code=${$}
+            ;;
+        -e)
+            shift
+            exit_code=${1}
+            ;;
+        -s)
+            shift
+            sleep "${1}"
+            ;;
+        -v)
+            shift
+            VAR_NAME=${1}
+            shift
+            VAR_VALUE=${1}
+            EXPECTED=$(env | grep -E "^${VAR_NAME}=")
+            if ! test "${VAR_NAME}=${VAR_VALUE}" = "${EXPECTED}"; then
+                exit 123
+            fi
+            ;;
+        *)
+            exit 123
+            ;;
+    esac
+    shift
+done
+
+# The exit code of 32 is returned when no args specified or
+# when only the -s arg has been specified.
+if [ -z "${exit_code}" ]; then
+    exit 32
+fi
+
+exit "${exit_code}"
diff --git a/src/lib/asiolink/asiolink/tests/process_spawn_unittest.cc b/src/lib/asiolink/asiolink/tests/process_spawn_unittest.cc
new file mode 100644 (file)
index 0000000..11bb26d
--- /dev/null
@@ -0,0 +1,346 @@
+// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service_signal.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/process_spawn.h>
+#include <gtest/gtest.h>
+#include <signal.h>
+#include <stdint.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace std;
+namespace ph = std::placeholders;
+
+/// @brief Test fixture for testing the use of ProcessSpawn with IO service
+/// signals.
+///
+/// This fixture is exercises ProcessSpawn using IO service signaling.
+class ProcessSpawnTest : public ::testing::Test {
+public:
+    /// @brief IOService instance to process IO.
+    asiolink::IOServicePtr io_service_;
+
+    /// @brief Failsafe timer to ensure test(s) do not hang.
+    isc::asiolink::IntervalTimer test_timer_;
+
+    /// @brief Maximum time should be allowed to run.
+    int test_time_ms_;
+
+    /// @brief IOSignalSet object.
+    IOSignalSetPtr io_signal_set_;
+
+    /// @brief Vector to record the signal values received.
+    std::vector<int> processed_signals_;
+
+    /// @brief Constructor.
+    ProcessSpawnTest() :
+        io_service_(new asiolink::IOService()), test_timer_(*io_service_),
+        test_time_ms_(0), io_signal_set_(), processed_signals_() {
+
+        io_signal_set_.reset(new IOSignalSet(io_service_,
+                                             std::bind(&ProcessSpawnTest::processSignal,
+                                                       this, ph::_1)));
+        io_signal_set_->add(SIGCHLD);
+    }
+
+    /// @brief Destructor.
+    ~ProcessSpawnTest() {}
+
+    /// @brief Method used as the IOSignalSet handler.
+    ///
+    /// Records the value of the given signal and checks if the desired
+    /// number of signals have been received.  If so, the IOService is
+    /// stopped which will cause IOService::run() to exit, returning control
+    /// to the test.
+    ///
+    /// @param signum signal number.
+    void processSignal(int signum) {
+        // Remember the signal we got.
+        processed_signals_.push_back(signum);
+    }
+
+    /// @brief Sets the failsafe timer for the test to the given time.
+    ///
+    /// @param  test_time_ms maximum time in milliseconds the test should
+    /// be allowed to run.
+    void setTestTime(int test_time_ms) {
+        // Fail safe shutdown
+        test_time_ms_ = test_time_ms;
+        test_timer_.setup(std::bind(&ProcessSpawnTest::testTimerHandler, this),
+                          test_time_ms_, asiolink::IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Failsafe timer expiration handler.
+    void testTimerHandler() {
+        io_service_->stop();
+    }
+};
+
+// This test verifies that the external application can be ran with
+// arguments and that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnWithArgs) {
+    vector<string> args;
+    args.push_back("-e");
+    args.push_back("64");
+
+    ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args);
+    pid_t pid = 0;
+    ASSERT_NO_THROW(pid = process.spawn());
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    ASSERT_EQ(1, processed_signals_.size());
+    ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+
+    EXPECT_EQ(64, process.getExitStatus(pid));
+}
+
+// This test verifies that the external application can be ran with
+// arguments and environment variables that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnWithArgsAndEnvVars) {
+    vector<string> args;
+    vector<string> vars;
+    args.push_back("-v");
+    args.push_back("TEST_VARIABLE_NAME");
+    args.push_back("TEST_VARIABLE_VALUE");
+    vars.push_back("TEST_VARIABLE_NAME=TEST_VARIABLE_VALUE");
+
+    ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args, vars);
+    pid_t pid = 0;
+    ASSERT_NO_THROW(pid = process.spawn());
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    ASSERT_EQ(1, processed_signals_.size());
+    ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+
+    EXPECT_EQ(32, process.getExitStatus(pid));
+}
+
+// This test verifies that the single ProcessSpawn object can be used
+// to start two processes and that their status codes can be gathered.
+// It also checks that it is possible to clear the status of the
+// process.
+TEST_F(ProcessSpawnTest, spawnTwoProcesses) {
+    vector<string> args;
+    args.push_back("-p");
+
+    ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args);
+    pid_t pid1 = 0;
+    ASSERT_NO_THROW(pid1 = process.spawn());
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    pid_t pid2 = 0;
+    ASSERT_NO_THROW(pid2 = process.spawn());
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    ASSERT_EQ(2, processed_signals_.size());
+    ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+    ASSERT_EQ(SIGCHLD, processed_signals_[1]);
+
+    EXPECT_NE(process.getExitStatus(pid1), process.getExitStatus(pid2));
+
+    // Clear the status of the first process. An attempt to get the status
+    // for the cleared process should result in exception. But, there should
+    // be no exception for the second process.
+    process.clearState(pid1);
+    EXPECT_THROW(process.getExitStatus(pid1), InvalidOperation);
+    EXPECT_NO_THROW(process.getExitStatus(pid2));
+
+    process.clearState(pid2);
+    EXPECT_THROW(process.getExitStatus(pid2), InvalidOperation);
+}
+
+// This test verifies that the external application can be ran without
+// arguments and that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnNoArgs) {
+    ProcessSpawn process(io_service_, TEST_SCRIPT_SH);
+    pid_t pid = 0;
+    ASSERT_NO_THROW(pid = process.spawn());
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    ASSERT_EQ(1, processed_signals_.size());
+    ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+
+    EXPECT_EQ(32, process.getExitStatus(pid));
+
+    ASSERT_NO_THROW(pid = process.spawn(true));
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    ASSERT_EQ(2, processed_signals_.size());
+    ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+    ASSERT_EQ(SIGCHLD, processed_signals_[1]);
+
+    EXPECT_THROW(process.getExitStatus(pid), InvalidOperation);
+}
+
+// This test verifies that the EXIT_FAILURE code is returned when
+// application can't be executed.
+TEST_F(ProcessSpawnTest, invalidExecutable) {
+    ProcessSpawn process(io_service_, "foo");
+    pid_t pid = 0;
+    ASSERT_NO_THROW(pid = process.spawn());
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    ASSERT_EQ(1, processed_signals_.size());
+    ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+
+    EXPECT_EQ(EXIT_FAILURE, process.getExitStatus(pid));
+}
+
+// This test verifies that the full command line for the process is
+// returned.
+TEST_F(ProcessSpawnTest, getCommandLine) {
+    // Note that cases below are enclosed in separate scopes to make
+    // sure that the ProcessSpawn object is destroyed before a new
+    // object is created. Current implementation doesn't allow for
+    // having two ProcessSpawn objects simultaneously as they will
+    // both try to allocate a signal handler for SIGCHLD.
+    {
+        // Case 1: arguments present.
+        ProcessArgs args;
+        args.push_back("-x");
+        args.push_back("-y");
+        args.push_back("foo");
+        args.push_back("bar");
+        ProcessSpawn process(io_service_, "myapp", args);
+        EXPECT_EQ("myapp -x -y foo bar", process.getCommandLine());
+    }
+
+    {
+        // Case 2: no arguments.
+        ProcessSpawn process(io_service_, "myapp");
+        EXPECT_EQ("myapp", process.getCommandLine());
+    }
+}
+
+// This test verifies that it is possible to check if the process is
+// running.
+TEST_F(ProcessSpawnTest, isRunning) {
+    // Run the process which sleeps for 10 seconds, so as we have
+    // enough time to check if it is running.
+    vector<string> args;
+    args.push_back("-s");
+    args.push_back("10");
+
+    ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args);
+    pid_t pid = 0;
+    ASSERT_NO_THROW(pid = process.spawn());
+
+    EXPECT_TRUE(process.isRunning(pid));
+
+    // Kill the process.
+    ASSERT_EQ(0, kill(pid, SIGKILL));
+
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // The first handler executed is the IOSignal's internal timer expire
+    // callback.
+    io_service_->run_one();
+
+    // Polling once to be sure.
+    io_service_->poll();
+
+    ASSERT_EQ(1, processed_signals_.size());
+    ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/asiolink/asiolink/tests/run_unittests.cc b/src/lib/asiolink/asiolink/tests/run_unittests.cc
new file mode 100644 (file)
index 0000000..566c7c9
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (C) 2009-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <log/logger_manager.h>
+
+int
+main(int argc, char* argv[])
+{
+    ::testing::InitGoogleTest(&argc, argv);         // Initialize Google test
+    isc::log::LoggerManager::init("unittest");      // Set a root logger name
+    return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/asiolink/asiolink/tests/tcp_acceptor_unittest.cc b/src/lib/asiolink/asiolink/tests/tcp_acceptor_unittest.cc
new file mode 100644 (file)
index 0000000..d674146
--- /dev/null
@@ -0,0 +1,443 @@
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tcp_endpoint.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+#include <list>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple class representing TCP socket callback.
+class SocketCallback {
+public:
+
+    /// @brief Implements callback for the asynchronous operation on the socket.
+    ///
+    /// This callback merely checks if error has occurred and reports this
+    /// error. It does nothing in case of success.
+    ///
+    /// @param ec Error code.
+    /// @param length Length of received data.
+    void operator()(boost::system::error_code ec, size_t length = 0) {
+        if (ec) {
+            ADD_FAILURE() << "error occurred for a socket: " << ec.message();
+        }
+    }
+
+};
+
+/// @brief Entity which can connect to the TCP server endpoint and close the
+/// connection.
+class TCPClient : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates new socket instance. It doesn't connect. Call
+    /// connect() to connect to the server.
+    ///
+    /// @param io_service IO service to be stopped on error.
+    explicit TCPClient(IOService& io_service)
+        : io_service_(io_service.get_io_service()), socket_(io_service_) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes the underlying socket if it is open.
+    ~TCPClient() {
+        close();
+    }
+
+    /// @brief Connect to the test server address and port.
+    ///
+    /// This method asynchronously connects to the server endpoint and uses the
+    /// connectHandler as a callback function.
+    void connect() {
+        boost::asio::ip::tcp::endpoint
+            endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+                     SERVER_PORT);
+        socket_.async_connect(endpoint,
+                              std::bind(&TCPClient::connectHandler, this,
+                                        ph::_1));
+    }
+
+    /// @brief Callback function for connect().
+    ///
+    /// This function stops the IO service upon error.
+    ///
+    /// @param ec Error code.
+    void connectHandler(const boost::system::error_code& ec) {
+        if (ec) {
+            // One would expect that async_connect wouldn't return EINPROGRESS
+            // error code, but simply wait for the connection to get
+            // established before the handler is invoked. It turns out, however,
+            // that on some OSes the connect handler may receive this error code
+            // which doesn't necessarily indicate a problem. Making an attempt
+            // to write and read from this socket will typically succeed. So,
+            // we ignore this error.
+            if (ec.value() != boost::asio::error::in_progress) {
+                ADD_FAILURE() << "error occurred while connecting: "
+                              << ec.message();
+                io_service_.stop();
+            }
+        }
+    }
+
+    /// @brief Close connection.
+    void close() {
+        socket_.close();
+    }
+
+private:
+
+    /// @brief Holds reference to the IO service.
+    boost::asio::io_service& io_service_;
+
+    /// @brief A socket used for the connection.
+    boost::asio::ip::tcp::socket socket_;
+
+};
+
+/// @brief Pointer to the TCPClient.
+typedef boost::shared_ptr<TCPClient> TCPClientPtr;
+
+/// @brief A signature of the function implementing callback for the
+/// TCPAcceptor.
+typedef std::function<void(const boost::system::error_code&)> TCPAcceptorCallback;
+
+/// @brief TCPAcceptor using TCPAcceptorCallback.
+typedef TCPAcceptor<TCPAcceptorCallback> TestTCPAcceptor;
+
+/// @brief Implements asynchronous TCP acceptor service.
+///
+/// It creates a new socket into which connection is accepted. The socket
+/// is retained until class instance exists.
+class Acceptor {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service.
+    /// @param acceptor Reference to the TCP acceptor on which asyncAccept
+    /// will be called.
+    /// @param callback Callback function for the asyncAccept.
+    explicit Acceptor(IOService& io_service, TestTCPAcceptor& acceptor,
+                      const TCPAcceptorCallback& callback)
+        : socket_(io_service), acceptor_(acceptor), callback_(callback) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes socket.
+    ~Acceptor() {
+        socket_.close();
+    }
+
+    /// @brief Asynchronous accept new connection.
+    void accept() {
+        acceptor_.asyncAccept(socket_, callback_);
+    }
+
+    /// @brief Close connection.
+    void close() {
+        socket_.close();
+    }
+
+private:
+
+    /// @brief Socket into which connection is accepted.
+    TCPSocket<SocketCallback> socket_;
+
+    /// @brief Reference to the TCPAcceptor on which asyncAccept is called.
+    TestTCPAcceptor& acceptor_;
+
+    /// @brief Instance of the callback used for asyncAccept.
+    TCPAcceptorCallback callback_;
+
+};
+
+/// @brief Pointer to the Acceptor object.
+typedef boost::shared_ptr<Acceptor> AcceptorPtr;
+
+/// @brief Test fixture class for TCPAcceptor.
+///
+/// This class provides means for creating new TCP connections, i.e. simulates
+/// clients connecting to the servers via TCPAcceptor. It is possible to create
+/// multiple simultaneous connections, which are retained by the test fixture
+/// class and closed cleanly when the test fixture is destroyed.
+class TCPAcceptorTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Besides initializing class members it also sets the test timer to guard
+    /// against endlessly running IO service when TCP connections are
+    /// unsuccessful.
+    TCPAcceptorTest()
+        : io_service_(), acceptor_(io_service_),
+          asio_endpoint_(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+                         SERVER_PORT),
+          endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
+          clients_(), connections_num_(0), aborted_connections_num_(0),
+          max_connections_(1) {
+        test_timer_.setup(std::bind(&TCPAcceptorTest::timeoutHandler, this),
+                                    TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Destructor.
+    virtual ~TCPAcceptorTest() {
+    }
+
+    /// @brief Specifies how many new connections are expected before the IO
+    /// service is stopped.
+    ///
+    /// @param max_connections Connections limit.
+    void setMaxConnections(const unsigned int max_connections) {
+        max_connections_ = max_connections;
+    }
+
+    /// @brief Create ASIO endpoint from the provided endpoint by retaining the
+    /// IP address and modifying the port.
+    ///
+    /// This convenience method is useful to create new endpoint from the
+    /// existing endpoint to test reusing IP address for multiple acceptors.
+    /// The returned endpoint has the same IP address but different port.
+    ///
+    /// @param endpoint Source endpoint.
+    ///
+    /// @return New endpoint with the port number increased by 1.
+    boost::asio::ip::tcp::endpoint
+    createSiblingEndpoint(const boost::asio::ip::tcp::endpoint& endpoint) const {
+        boost::asio::ip::tcp::endpoint endpoint_copy(endpoint);
+        endpoint_copy.port(endpoint.port() + 1);
+        return (endpoint_copy);
+    }
+
+    /// @brief Opens TCP acceptor and sets 'reuse address' option.
+    void acceptorOpen() {
+        acceptor_.open(endpoint_);
+        acceptor_.setOption(TestTCPAcceptor::ReuseAddress(true));
+    }
+
+    /// @brief Starts accepting TCP connections.
+    ///
+    /// This method creates new Acceptor instance and calls accept() to start
+    /// accepting new connections. The instance of the Acceptor object is
+    /// retained in the connections_ list.
+    void accept() {
+        TCPAcceptorCallback cb = std::bind(&TCPAcceptorTest::acceptHandler,
+                                           this, ph::_1);
+        AcceptorPtr conn(new Acceptor(io_service_, acceptor_, cb));
+        connections_.push_back(conn);
+        connections_.back()->accept();
+    }
+
+    /// @brief Connect to the endpoint.
+    ///
+    /// This method creates TCPClient instance and retains it in the clients_
+    /// list.
+    void connect() {
+        TCPClientPtr client(new TCPClient(io_service_));
+        clients_.push_back(client);
+        clients_.back()->connect();
+    }
+
+    /// @brief Callback function for asynchronous accept calls.
+    ///
+    /// It stops the IO service upon error or when the number of accepted
+    /// connections reaches the max_connections_ value. Otherwise it calls
+    /// accept() to start accepting next connections.
+    ///
+    /// @param ec Error code.
+    void acceptHandler(const boost::system::error_code& ec) {
+        if (ec) {
+            if (ec.value() != boost::asio::error::operation_aborted) {
+                ADD_FAILURE() << "error occurred while accepting connection: "
+                              << ec.message();
+            } else {
+                ++aborted_connections_num_;
+            }
+            io_service_.stop();
+        }
+
+        // We have reached the maximum number of connections - end the test.
+        if (++connections_num_ >= max_connections_) {
+            io_service_.stop();
+            return;
+        }
+
+        accept();
+    }
+
+    /// @brief Callback function invoke upon test timeout.
+    ///
+    /// It stops the IO service and reports test timeout.
+    void timeoutHandler() {
+        ADD_FAILURE() << "Timeout occurred while running the test!";
+        io_service_.stop();
+    }
+
+    /// @brief IO service.
+    IOService io_service_;
+
+    /// @brief TCPAcceptor under test.
+    TestTCPAcceptor acceptor_;
+
+    /// @brief Server endpoint.
+    boost::asio::ip::tcp::endpoint asio_endpoint_;
+
+    /// @brief asiolink server endpoint (uses asio_endpoint_).
+    TCPEndpoint endpoint_;
+
+    /// @brief Asynchronous timer service to detect timeouts.
+    IntervalTimer test_timer_;
+
+    /// @brief List of connections on the server side.
+    std::list<AcceptorPtr> connections_;
+
+    /// @brief List of client connections.
+    std::list<TCPClientPtr> clients_;
+
+    /// @brief Current number of established connections.
+    unsigned int connections_num_;
+
+    /// @brief Current number of aborted connections.
+    unsigned int aborted_connections_num_;
+
+    /// @brief Connections limit.
+    unsigned int max_connections_;
+};
+
+// Test TCPAcceptor::asyncAccept.
+TEST_F(TCPAcceptorTest, asyncAccept) {
+    // Establish up to 10 connections.
+    setMaxConnections(10);
+
+    // Initialize acceptor.
+    acceptorOpen();
+    acceptor_.bind(endpoint_);
+    acceptor_.listen();
+
+    // Start accepting new connections.
+    accept();
+
+    // Create 10 new TCP connections (client side).
+    for (unsigned int i = 0; i < 10; ++i) {
+        connect();
+    }
+
+    // Run the IO service until we have accepted 10 connections, an error
+    // or test timeout occurred.
+    io_service_.run();
+
+    // Make sure that all accepted connections have been recorded.
+    EXPECT_EQ(10, connections_num_);
+    EXPECT_EQ(10, connections_.size());
+}
+
+// Check that it is possible to set SO_REUSEADDR flag for the TCPAcceptor.
+TEST_F(TCPAcceptorTest, reuseAddress) {
+    // We need at least two acceptors using common address. Let's create the
+    // second endpoint which has the same address but different port.
+    boost::asio::ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
+    TCPEndpoint endpoint2(asio_endpoint2);
+
+    // Create and open two acceptors.
+    TestTCPAcceptor acceptor1(io_service_);
+    TestTCPAcceptor acceptor2(io_service_);
+    ASSERT_NO_THROW(acceptor1.open(endpoint_));
+    ASSERT_NO_THROW(acceptor2.open(endpoint2));
+
+    // Set SO_REUSEADDR socket option so as acceptors can bind to the
+    /// same address.
+    ASSERT_NO_THROW(
+        acceptor1.setOption(TestTCPAcceptor::ReuseAddress(true))
+    );
+    ASSERT_NO_THROW(
+        acceptor2.setOption(TestTCPAcceptor::ReuseAddress(true))
+    );
+    ASSERT_NO_THROW(acceptor1.bind(endpoint_));
+    ASSERT_NO_THROW(acceptor2.bind(endpoint2));
+
+    // Create third acceptor, but don't set the SO_REUSEADDR. It should
+    // refuse to bind.
+    TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
+    TestTCPAcceptor acceptor3(io_service_);
+    ASSERT_NO_THROW(acceptor3.open(endpoint3));
+    EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
+}
+
+// Test that TCPAcceptor::getProtocol returns IPPROTO_TCP.
+TEST_F(TCPAcceptorTest, getProtocol) {
+    EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
+}
+
+// Test that TCPAcceptor::getNative returns valid socket descriptor.
+TEST_F(TCPAcceptorTest, getNative) {
+    // Initially the descriptor should be invalid (negative).
+    ASSERT_LT(acceptor_.getNative(), 0);
+    // Now open the socket and make sure the returned descriptor is now valid.
+    ASSERT_NO_THROW(acceptorOpen());
+    EXPECT_GE(acceptor_.getNative(), 0);
+}
+
+// macOS 10.12.3 has a bug which causes the connections to not enter
+// the TIME-WAIT state and they never get closed.
+#if !defined (OS_OSX)
+
+// Test that TCPAcceptor::close works properly.
+TEST_F(TCPAcceptorTest, close) {
+    // Initialize acceptor.
+    acceptorOpen();
+    acceptor_.bind(endpoint_);
+    acceptor_.listen();
+
+    // Start accepting new connections.
+    accept();
+
+    // Create 10 new TCP connections (client side).
+    for (unsigned int i = 0; i < 10; ++i) {
+        connect();
+    }
+
+    // Close the acceptor before connections are accepted.
+    acceptor_.close();
+
+    // Run the IO service.
+    io_service_.run();
+
+    // The connections should have been aborted.
+    EXPECT_EQ(1, connections_num_);
+    EXPECT_EQ(1, aborted_connections_num_);
+    EXPECT_EQ(1, connections_.size());
+}
+
+#endif
+
+}
diff --git a/src/lib/asiolink/asiolink/tests/tcp_endpoint_unittest.cc b/src/lib/asiolink/asiolink/tests/tcp_endpoint_unittest.cc
new file mode 100644 (file)
index 0000000..0d838d4
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc::asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// boost::asio::ip::tcp::endpoint object.
+
+TEST(TCPEndpointTest, v4Address) {
+    const string test_address("192.0.2.1");
+    const unsigned short test_port = 5301;
+
+    IOAddress address(test_address);
+    TCPEndpoint endpoint(address, test_port);
+
+    EXPECT_TRUE(address == endpoint.getAddress());
+    EXPECT_EQ(test_port, endpoint.getPort());
+    EXPECT_EQ(static_cast<short>(IPPROTO_TCP), endpoint.getProtocol());
+    EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(TCPEndpointTest, v6Address) {
+    const string test_address("2001:db8::1235");
+    const unsigned short test_port = 5302;
+
+    IOAddress address(test_address);
+    TCPEndpoint endpoint(address, test_port);
+
+    EXPECT_TRUE(address == endpoint.getAddress());
+    EXPECT_EQ(test_port, endpoint.getPort());
+    EXPECT_EQ(static_cast<short>(IPPROTO_TCP), endpoint.getProtocol());
+    EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/asiolink/tests/tcp_socket_unittest.cc
new file mode 100644 (file)
index 0000000..cd65cdb
--- /dev/null
@@ -0,0 +1,515 @@
+// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @brief Test of TCPSocket
+///
+/// Tests the functionality of a TCPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cstddef>
+#include <cstdlib>
+#include <errno.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string>
+#include <vector>
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5303;
+
+/// @todo Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O completes.
+/// The arguments to the completion callback are stored for later retrieval.
+class TCPCallback {
+public:
+    /// @brief Operations the server is doing
+    enum Operation {
+        ACCEPT = 0,     ///< accept() was issued
+        OPEN = 1,       /// Client connected to server
+        READ = 2,       ///< Asynchronous read completed
+        WRITE = 3,      ///< Asynchronous write completed
+        NONE = 4        ///< "Not set" state
+    };
+
+    /// @brief Minimum size of buffers
+    enum {
+        MIN_SIZE = (64 * 1024 + 2)          ///< 64kB + two bytes for a count
+    };
+
+    struct PrivateData {
+        PrivateData() :
+            error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0),
+            name_(""), queued_(NONE), called_(NONE), data_(MIN_SIZE, 0)
+        {}
+
+        boost::system::error_code  error_code_;    ///< Completion error code
+        size_t                     length_;        ///< Bytes transferred in this I/O
+        size_t                     cumulative_;    ///< Cumulative bytes transferred
+        size_t                     expected_;      ///< Expected amount of data
+        size_t                     offset_;        ///< Where to put data in buffer
+        std::string                name_;          ///< Which of the objects this is
+        Operation                  queued_;        ///< Queued operation
+        Operation                  called_;        ///< Which callback called
+        std::vector<uint8_t>       data_;          ///< Receive buffer
+    };
+
+    /// @brief Constructor
+    ///
+    /// Constructs the object.  It also creates the data member pointed to by
+    /// a shared pointer.  When used as a callback object, this is copied as it
+    /// is passed into the asynchronous function.  This means that there are two
+    /// objects and inspecting the one we passed in does not tell us anything.
+    ///
+    /// Therefore we use a boost::shared_ptr.  When the object is copied, the
+    /// shared pointer is copied, which leaves both objects pointing to the same
+    /// data.
+    ///
+    /// @param which Which of the two callback objects this is
+    TCPCallback(std::string which) : ptr_(new PrivateData())
+    {
+        ptr_->name_ = which;
+    }
+
+    /// @brief Destructor
+    ///
+    /// No code needed, destroying the shared pointer destroys the private data.
+    virtual ~TCPCallback()
+    {}
+
+    /// @brief Client Callback Function
+    ///
+    /// Called when an asynchronous operation is completed by the client, this
+    /// stores the origin of the operation in the client_called_ data member.
+    ///
+    /// @param ec I/O completion error code passed to callback function.
+    /// @param length Number of bytes transferred
+    void operator()(boost::system::error_code ec = boost::system::error_code(),
+                            size_t length = 0)
+    {
+        setCode(ec.value());
+        ptr_->called_ = ptr_->queued_;
+        ptr_->length_ = length;
+    }
+
+    /// @brief Get I/O completion error code
+    int getCode() {
+        return (ptr_->error_code_.value());
+    }
+
+    /// @brief Set I/O completion code
+    ///
+    /// @param code New value of completion code
+    void setCode(int code) {
+        ptr_->error_code_ = boost::system::error_code(code, boost::system::error_code().category());
+    }
+
+    /// @brief Get number of bytes transferred in I/O
+    size_t& length() {
+        return (ptr_->length_);
+    }
+
+    /// @brief Get cumulative number of bytes transferred in I/O
+    size_t& cumulative() {
+        return (ptr_->cumulative_);
+    }
+
+    /// @brief Get expected amount of data
+    size_t& expected() {
+        return (ptr_->expected_);
+    }
+
+    /// @brief Get offset into data
+    size_t& offset() {
+        return (ptr_->offset_);
+    }
+
+    /// @brief Get data member
+    uint8_t* data() {
+        return (&ptr_->data_[0]);
+    }
+
+    /// @brief Get flag to say what was queued
+    Operation& queued() {
+        return (ptr_->queued_);
+    }
+
+    /// @brief Get flag to say when callback was called
+    Operation& called() {
+        return (ptr_->called_);
+    }
+
+    /// @brief Return instance of callback name
+    std::string& name() {
+        return (ptr_->name_);
+    }
+
+private:
+    boost::shared_ptr<PrivateData>  ptr_;   ///< Pointer to private data
+};
+
+
+// Read Server Data
+//
+// Called in the part of the test that has the client send a message to the
+// server, this loops until all the data has been read (synchronously) by the
+// server.
+//
+// "All the data read" means that the server has received a message that is
+// preceded by a two-byte count field and that the total amount of data received
+// from the remote end is equal to the value in the count field plus two bytes
+// for the count field itself.
+//
+// @param socket Socket on which the server is reading data
+// @param server_cb Structure in which server data is held.
+void
+serverRead(tcp::socket& socket, TCPCallback& server_cb) {
+
+    // As we may need to read multiple times, keep a count of the cumulative
+    // amount of data read and do successive reads into the appropriate part
+    // of the buffer.
+    //
+    // Note that there are no checks for buffer overflow - this is a test
+    // program and we have sized the buffer to be large enough for the test.
+    server_cb.cumulative() = 0;
+
+    bool complete = false;
+    while (!complete) {
+
+        // Read block of data and update cumulative amount of data received.
+        server_cb.length() = socket.receive(
+            boost::asio::buffer(server_cb.data() + server_cb.cumulative(),
+                TCPCallback::MIN_SIZE - server_cb.cumulative()));
+        server_cb.cumulative() += server_cb.length();
+
+        // If we have read at least two bytes, we can work out how much we
+        // should be reading.
+        if (server_cb.cumulative() >= 2) {
+            server_cb.expected() = readUint16(server_cb.data(), server_cb.length());
+            if ((server_cb.expected() + 2) == server_cb.cumulative()) {
+
+                // Amount of data read from socket equals the size of the
+                // message (as indicated in the first two bytes of the message)
+                // plus the size of the count field.  Therefore we have received
+                // all the data.
+                complete = true;
+            }
+        }
+    }
+}
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TCPSocket, processReceivedData) {
+    const uint16_t PACKET_SIZE = 16382;     // Amount of "real" data in the buffer
+
+    IOService               service;        // Used to instantiate socket
+    TCPSocket<TCPCallback>  test(service);  // Socket under test
+    uint8_t                 inbuff[PACKET_SIZE + 2];   // Buffer to check
+    OutputBufferPtr         outbuff(new OutputBuffer(16));
+                                            // Where data is put
+    size_t                  expected;       // Expected amount of data
+    size_t                  offset;         // Where to put next data
+    size_t                  cumulative;     // Cumulative data received
+
+    // Set some dummy values in the buffer to check
+    for (size_t i = 0; i < sizeof(inbuff); ++i) {
+        inbuff[i] = i % 256;
+    }
+
+    // Check that the method will handle various receive sizes.
+    writeUint16(PACKET_SIZE, inbuff, sizeof(inbuff));
+
+    cumulative = 0;
+    offset = 0;
+    expected = 0;
+    outbuff->clear();
+    bool complete = test.processReceivedData(inbuff, 1, cumulative, offset,
+                                             expected, outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(1, cumulative);
+    EXPECT_EQ(1, offset);
+    EXPECT_EQ(0, expected);
+    EXPECT_EQ(0, outbuff->getLength());
+
+    // Now pretend that we've received one more byte.
+    complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected,
+                                        outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(2, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(0, outbuff->getLength());
+
+    // Add another two bytes.  However, this time note that we have to offset
+    // in the input buffer because it is expected that the next chunk of data
+    // from the connection will be read into the start of the buffer.
+    complete = test.processReceivedData(inbuff + cumulative, 2, cumulative,
+                                        offset, expected, outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(4, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(2, outbuff->getLength());
+
+    const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+    EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+
+    // And add the remaining data.  Remember that "inbuff" is "PACKET_SIZE + 2"
+    // long.
+    complete = test.processReceivedData(inbuff + cumulative,
+                                        PACKET_SIZE + 2 - cumulative,
+                                        cumulative, offset, expected, outbuff);
+    EXPECT_TRUE(complete);
+    EXPECT_EQ(PACKET_SIZE + 2, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(PACKET_SIZE, outbuff->getLength());
+    dataptr = static_cast<const uint8_t*>(outbuff->getData());
+    EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+}
+
+/// @todo Need to add a test to check the cancel() method
+
+// Tests the operation of a TCPSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(TCPSocket, sequenceTest) {
+
+    // Common objects.
+    IOService   service;                    // Service object for async control
+
+    // The client - the TCPSocket being tested
+    TCPSocket<TCPCallback>  client(service);// Socket under test
+    TCPCallback client_cb("Client");        // Async I/O callback function
+    TCPEndpoint client_remote_endpoint;     // Where client receives message from
+    OutputBufferPtr client_buffer(new OutputBuffer(128));
+                                            // Received data is put here
+
+    // The server - with which the client communicates.
+    IOAddress   server_address(SERVER_ADDRESS);
+                                            // Address of target server
+    TCPCallback server_cb("Server");        // Server callback
+    TCPEndpoint server_endpoint(server_address, SERVER_PORT);
+                                            // Endpoint describing server
+    TCPEndpoint server_remote_endpoint;     // Address where server received message from
+    tcp::socket server_socket(service.get_io_service());
+                                            // Socket used for server
+
+    // Step 1.  Create the connection between the client and the server.  Set
+    // up the server to accept incoming connections and have the client open
+    // a channel to it.
+
+    // Set up server - open socket and queue an accept.
+    server_cb.queued() = TCPCallback::ACCEPT;
+    server_cb.called() = TCPCallback::NONE;
+    server_cb.setCode(42);  // Some error
+    tcp::acceptor acceptor(service.get_io_service(),
+                            tcp::endpoint(tcp::v4(), SERVER_PORT));
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    acceptor.async_accept(server_socket, server_cb);
+
+    // Set up client - connect to the server.
+    client_cb.queued() = TCPCallback::OPEN;
+    client_cb.called() = TCPCallback::NONE;
+    client_cb.setCode(43);  // Some error
+    EXPECT_FALSE(client.isOpenSynchronous());
+    client.open(&server_endpoint, client_cb);
+
+    // Run the open and the accept callback and check that they ran.
+    service.run_one();
+    service.run_one();
+
+    EXPECT_EQ(TCPCallback::ACCEPT, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+
+    EXPECT_EQ(TCPCallback::OPEN, client_cb.called());
+
+    // On some operating system the async_connect may return EINPROGRESS.
+    // This doesn't necessarily indicate an error. In most cases trying
+    // to asynchronously write and read from the socket would work just
+    // fine.
+    if ((client_cb.getCode()) != 0 && (client_cb.getCode() != EINPROGRESS)) {
+        ADD_FAILURE() << "expected error code of 0 or " << EINPROGRESS
+            << " as a result of async_connect, got " << client_cb.getCode();
+    }
+
+    // Step 2.  Get the client to write to the server asynchronously.  The
+    // server will loop reading the data synchronously.
+
+    // Write asynchronously to the server.
+    client_cb.called() = TCPCallback::NONE;
+    client_cb.queued() = TCPCallback::WRITE;
+    client_cb.setCode(143);  // Arbitrary number
+    client_cb.length() = 0;
+    client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+
+    // Wait for the client callback to complete. (Must do this first on
+    // Solaris: if we do the synchronous read first, the test hangs.)
+    service.run_one();
+
+    // Synchronously read the data from the server.;
+    serverRead(server_socket, server_cb);
+
+    // Check the client state
+    EXPECT_EQ(TCPCallback::WRITE, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length());
+
+    // ... and check what the server received.
+    EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative());
+    EXPECT_TRUE(equal(OUTBOUND_DATA,
+                (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)),
+                (server_cb.data() + 2)));
+
+    // Step 3.  Get the server to write all the data asynchronously and have the
+    // client loop (asynchronously) reading the data.  Note that we copy the
+    // data into the server's internal buffer in order to precede it with a two-
+    // byte count field.
+
+    // Have the server write asynchronously to the client.
+    server_cb.called() = TCPCallback::NONE;
+    server_cb.queued() = TCPCallback::WRITE;
+    server_cb.length() = 0;
+    server_cb.cumulative() = 0;
+
+    writeUint16(sizeof(INBOUND_DATA), server_cb.data(), TCPCallback::MIN_SIZE);
+    copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
+        (server_cb.data() + 2));
+    server_socket.async_send(boost::asio::buffer(server_cb.data(),
+                                          (sizeof(INBOUND_DATA) + 2)),
+                             server_cb);
+
+    // Have the client read asynchronously.
+    client_cb.called() = TCPCallback::NONE;
+    client_cb.queued() = TCPCallback::READ;
+    client_cb.length() = 0;
+    client_cb.cumulative() = 0;
+    client_cb.expected() = 0;
+    client_cb.offset() = 0;
+
+    client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE,
+                        client_cb.offset(), &client_remote_endpoint,
+                        client_cb);
+
+    // Run the callbacks. Several options are possible depending on how ASIO
+    // is implemented and whether the message gets fragmented:
+    //
+    // 1) The send handler may complete immediately, regardless of whether the
+    // data has been read by the client.  (This is the most likely.)
+    // 2) The send handler may only run after all the data has been read by
+    // the client. (This could happen if the client's TCP buffers were too
+    // small so the data was not transferred to the "remote" system until the
+    // remote buffer has been emptied one or more times.)
+    // 3) The client handler may be run a number of times to handle the message
+    // fragments and the server handler may run between calls of the client
+    // handler.
+    //
+    // So loop, running one handler at a time until we are certain that all the
+    // handlers have run.
+
+    bool server_complete = false;
+    bool client_complete = false;
+    while (!server_complete || !client_complete) {
+        service.run_one();
+
+        // Has the server run?
+        if (!server_complete) {
+            if (server_cb.called() == server_cb.queued()) {
+
+                // Yes.  Check that the send completed successfully and that
+                // all the data that was expected to have been sent was in fact
+                // sent.
+                EXPECT_EQ(0, server_cb.getCode());
+                EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length());
+                server_complete = true;
+            }
+        }
+
+        // Has the client run?
+        if (!client_complete) {
+
+            if (client_cb.called() != client_cb.queued()) {
+                // No. Run the service another time.
+                continue;
+            }
+
+            // Client callback must have run.  Check that it ran OK.
+            EXPECT_EQ(TCPCallback::READ, client_cb.called());
+            EXPECT_EQ(0, client_cb.getCode());
+
+            // Check if we need to queue another read, copying the data into
+            // the output buffer as we do so.
+            client_complete = client.processReceivedData(client_cb.data(),
+                                                         client_cb.length(),
+                                                         client_cb.cumulative(),
+                                                         client_cb.offset(),
+                                                         client_cb.expected(),
+                                                         client_buffer);
+
+            // If the data is not complete, queue another read.
+            if (! client_complete) {
+                client_cb.called() = TCPCallback::NONE;
+                client_cb.queued() = TCPCallback::READ;
+                client_cb.length() = 0;
+                client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE ,
+                                    client_cb.offset(), &client_remote_endpoint,
+                                    client_cb);
+            }
+        }
+    }
+
+    // Both the send and the receive have completed.  Check that the received
+    // is what was sent.
+
+    // Check the client state
+    EXPECT_EQ(TCPCallback::READ, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative());
+    EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength());
+
+    // ... and check what the server sent.
+    EXPECT_EQ(TCPCallback::WRITE, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length());
+
+    // ... and that what was sent is what was received.
+    const uint8_t* received = static_cast<const uint8_t*>(client_buffer->getData());
+    EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)),
+                      received));
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.close());
+    EXPECT_NO_THROW(server_socket.close());
+}
diff --git a/src/lib/asiolink/asiolink/tests/tls_acceptor_unittest.cc b/src/lib/asiolink/asiolink/tests/tls_acceptor_unittest.cc
new file mode 100644 (file)
index 0000000..06f0631
--- /dev/null
@@ -0,0 +1,449 @@
+// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tls_acceptor.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+#include <list>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace boost::asio;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple class representing TCP socket callback.
+class SocketCallback {
+public:
+
+    /// @brief Implements callback for the asynchronous operation on the socket.
+    ///
+    /// This callback merely checks if error has occurred and reports this
+    /// error. It does nothing in case of success.
+    ///
+    /// @param ec Error code.
+    /// @param length Length of received data.
+    void operator()(boost::system::error_code ec, size_t length = 0) {
+        if (ec) {
+            ADD_FAILURE() << "error occurred for a socket: " << ec.message();
+        }
+    }
+
+};
+
+/// @brief Entity which can connect to the TLS server endpoint and close the
+/// connection.
+class TLSClient : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates new socket instance. It doesn't connect. Call
+    /// connect() to connect to the server.
+    ///
+    /// @param io_service IO service to be stopped on error.
+    explicit TLSClient(IOService& io_service)
+        : io_service_(io_service.get_io_service()), socket_(io_service_) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes the underlying socket if it is open.
+    ~TLSClient() {
+        close();
+    }
+
+    /// @brief Connect to the test server address and port.
+    ///
+    /// This method asynchronously connects to the server endpoint and uses the
+    /// connectHandler as a callback function.
+    void connect() {
+        ip::tcp::endpoint
+            endpoint(ip::address::from_string(SERVER_ADDRESS),
+                     SERVER_PORT);
+        socket_.async_connect(endpoint,
+                              std::bind(&TLSClient::connectHandler, this,
+                                        ph::_1));
+    }
+
+    /// @brief Callback function for connect().
+    ///
+    /// This function stops the IO service upon error.
+    ///
+    /// @param ec Error code.
+    void connectHandler(const boost::system::error_code& ec) {
+        if (ec) {
+            // One would expect that async_connect wouldn't return EINPROGRESS
+            // error code, but simply wait for the connection to get
+            // established before the handler is invoked. It turns out, however,
+            // that on some OSes the connect handler may receive this error code
+            // which doesn't necessarily indicate a problem. Making an attempt
+            // to write and read from this socket will typically succeed. So,
+            // we ignore this error.
+            if (ec.value() != error::in_progress) {
+                ADD_FAILURE() << "error occurred while connecting: "
+                              << ec.message();
+                io_service_.stop();
+            }
+        }
+    }
+
+    /// @brief Close connection.
+    void close() {
+        socket_.close();
+    }
+
+private:
+
+    /// @brief Holds reference to the IO service.
+    io_service& io_service_;
+
+    /// @brief A socket used for the connection.
+    ip::tcp::socket socket_;
+
+};
+
+/// @brief Pointer to the TLSClient.
+typedef boost::shared_ptr<TLSClient> TLSClientPtr;
+
+/// @brief A signature of the function implementing callback for the
+/// TLSAcceptor.
+typedef std::function<void(const boost::system::error_code&)> TLSAcceptorCallback;
+
+/// @brief TLSAcceptor using TLSAcceptorCallback.
+typedef TLSAcceptor<TLSAcceptorCallback> TestTLSAcceptor;
+
+/// @brief Implements asynchronous TLS acceptor service.
+///
+/// It creates a new socket into which connection is accepted. The socket
+/// is retained until class instance exists.
+class Acceptor {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service.
+    /// @param context Pointer to TLS context.
+    /// @param acceptor Reference to the TLS acceptor on which asyncAccept
+    /// will be called.
+    /// @param callback Callback function for the asyncAccept.
+    explicit Acceptor(IOService& io_service,
+                      TlsContextPtr context,
+                      TestTLSAcceptor& acceptor,
+                      const TLSAcceptorCallback& callback)
+        : socket_(io_service, context), acceptor_(acceptor),
+          callback_(callback) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes socket.
+    ~Acceptor() {
+        socket_.close();
+    }
+
+    /// @brief Asynchronous accept new connection.
+    void accept() {
+        acceptor_.asyncAccept(socket_, callback_);
+    }
+
+    /// @brief Close connection.
+    void close() {
+        socket_.close();
+    }
+
+private:
+
+    /// @brief Socket into which connection is accepted.
+    TLSSocket<SocketCallback> socket_;
+
+    /// @brief Reference to the TLSAcceptor on which asyncAccept is called.
+    TestTLSAcceptor& acceptor_;
+
+    /// @brief Instance of the callback used for asyncAccept.
+    TLSAcceptorCallback callback_;
+
+};
+
+/// @brief Pointer to the Acceptor object.
+typedef boost::shared_ptr<Acceptor> AcceptorPtr;
+
+/// @brief Test fixture class for TLSAcceptor.
+///
+/// This class provides means for creating new TLS connections, i.e. simulates
+/// clients connecting to the servers via TLSAcceptor. It is possible to create
+/// multiple simultaneous connections, which are retained by the test fixture
+/// class and closed cleanly when the test fixture is destroyed.
+class TLSAcceptorTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Besides initializing class members it also sets the test timer to guard
+    /// against endlessly running IO service when TLS connections are
+    /// unsuccessful.
+    TLSAcceptorTest()
+        : io_service_(), acceptor_(io_service_),
+          asio_endpoint_(ip::address::from_string(SERVER_ADDRESS),
+                         SERVER_PORT),
+          endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
+          clients_(), connections_num_(0), aborted_connections_num_(0),
+          max_connections_(1) {
+        test_timer_.setup(std::bind(&TLSAcceptorTest::timeoutHandler, this),
+                                    TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Destructor.
+    virtual ~TLSAcceptorTest() {
+    }
+
+    /// @brief Specifies how many new connections are expected before the IO
+    /// service is stopped.
+    ///
+    /// @param max_connections Connections limit.
+    void setMaxConnections(const unsigned int max_connections) {
+        max_connections_ = max_connections;
+    }
+
+    /// @brief Create ASIO endpoint from the provided endpoint by retaining the
+    /// IP address and modifying the port.
+    ///
+    /// This convenience method is useful to create new endpoint from the
+    /// existing endpoint to test reusing IP address for multiple acceptors.
+    /// The returned endpoint has the same IP address but different port.
+    ///
+    /// @param endpoint Source endpoint.
+    ///
+    /// @return New endpoint with the port number increased by 1.
+    ip::tcp::endpoint
+    createSiblingEndpoint(const ip::tcp::endpoint& endpoint) const {
+        ip::tcp::endpoint endpoint_copy(endpoint);
+        endpoint_copy.port(endpoint.port() + 1);
+        return (endpoint_copy);
+    }
+
+    /// @brief Opens TLS acceptor and sets 'reuse address' option.
+    void acceptorOpen() {
+        acceptor_.open(endpoint_);
+        acceptor_.setOption(TestTLSAcceptor::ReuseAddress(true));
+    }
+
+    /// @brief Starts accepting TLS connections.
+    ///
+    /// This method creates new Acceptor instance and calls accept() to start
+    /// accepting new connections. The instance of the Acceptor object is
+    /// retained in the connections_ list.
+    void accept() {
+        TLSAcceptorCallback cb = std::bind(&TLSAcceptorTest::acceptHandler,
+                                           this, ph::_1);
+        TlsContextPtr ctx(new TlsContext(SERVER));
+        AcceptorPtr conn(new Acceptor(io_service_, ctx, acceptor_, cb));
+        connections_.push_back(conn);
+        connections_.back()->accept();
+    }
+
+    /// @brief Connect to the endpoint.
+    ///
+    /// This method creates TLSClient instance and retains it in the clients_
+    /// list.
+    void connect() {
+        TLSClientPtr client(new TLSClient(io_service_));
+        clients_.push_back(client);
+        clients_.back()->connect();
+    }
+
+    /// @brief Callback function for asynchronous accept calls.
+    ///
+    /// It stops the IO service upon error or when the number of accepted
+    /// connections reaches the max_connections_ value. Otherwise it calls
+    /// accept() to start accepting next connections.
+    ///
+    /// @param ec Error code.
+    void acceptHandler(const boost::system::error_code& ec) {
+        if (ec) {
+            if (ec.value() != error::operation_aborted) {
+                ADD_FAILURE() << "error occurred while accepting connection: "
+                              << ec.message();
+            } else {
+                ++aborted_connections_num_;
+            }
+            io_service_.stop();
+        }
+
+        // We have reached the maximum number of connections - end the test.
+        if (++connections_num_ >= max_connections_) {
+            io_service_.stop();
+            return;
+        }
+
+        accept();
+    }
+
+    /// @brief Callback function invoke upon test timeout.
+    ///
+    /// It stops the IO service and reports test timeout.
+    void timeoutHandler() {
+        ADD_FAILURE() << "Timeout occurred while running the test!";
+        io_service_.stop();
+    }
+
+    /// @brief IO service.
+    IOService io_service_;
+
+    /// @brief TLSAcceptor under test.
+    TestTLSAcceptor acceptor_;
+
+    /// @brief Server endpoint.
+    ip::tcp::endpoint asio_endpoint_;
+
+    /// @brief asiolink server endpoint (uses asio_endpoint_).
+    TCPEndpoint endpoint_;
+
+    /// @brief Asynchronous timer service to detect timeouts.
+    IntervalTimer test_timer_;
+
+    /// @brief List of connections on the server side.
+    std::list<AcceptorPtr> connections_;
+
+    /// @brief List of client connections.
+    std::list<TLSClientPtr> clients_;
+
+    /// @brief Current number of established connections.
+    unsigned int connections_num_;
+
+    /// @brief Current number of aborted connections.
+    unsigned int aborted_connections_num_;
+
+    /// @brief Connections limit.
+    unsigned int max_connections_;
+};
+
+// Test TLSAcceptor::asyncAccept.
+TEST_F(TLSAcceptorTest, asyncAccept) {
+    // Establish up to 10 connections.
+    setMaxConnections(10);
+
+    // Initialize acceptor.
+    acceptorOpen();
+    acceptor_.bind(endpoint_);
+    acceptor_.listen();
+
+    // Start accepting new connections.
+    accept();
+
+    // Create 10 new TLS connections (client side).
+    for (unsigned int i = 0; i < 10; ++i) {
+        connect();
+    }
+
+    // Run the IO service until we have accepted 10 connections, an error
+    // or test timeout occurred.
+    io_service_.run();
+
+    // Make sure that all accepted connections have been recorded.
+    EXPECT_EQ(10, connections_num_);
+    EXPECT_EQ(10, connections_.size());
+}
+
+// Check that it is possible to set SO_REUSEADDR flag for the TLSAcceptor.
+TEST_F(TLSAcceptorTest, reuseAddress) {
+    // We need at least two acceptors using common address. Let's create the
+    // second endpoint which has the same address but different port.
+    ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
+    TCPEndpoint endpoint2(asio_endpoint2);
+
+    // Create and open two acceptors.
+    TestTLSAcceptor acceptor1(io_service_);
+    TestTLSAcceptor acceptor2(io_service_);
+    ASSERT_NO_THROW(acceptor1.open(endpoint_));
+    ASSERT_NO_THROW(acceptor2.open(endpoint2));
+
+    // Set SO_REUSEADDR socket option so as acceptors can bind to the
+    /// same address.
+    ASSERT_NO_THROW(
+        acceptor1.setOption(TestTLSAcceptor::ReuseAddress(true))
+    );
+    ASSERT_NO_THROW(
+        acceptor2.setOption(TestTLSAcceptor::ReuseAddress(true))
+    );
+    ASSERT_NO_THROW(acceptor1.bind(endpoint_));
+    ASSERT_NO_THROW(acceptor2.bind(endpoint2));
+
+    // Create third acceptor, but don't set the SO_REUSEADDR. It should
+    // refuse to bind.
+    TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
+    TestTLSAcceptor acceptor3(io_service_);
+    ASSERT_NO_THROW(acceptor3.open(endpoint3));
+    EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
+}
+
+// Test that TLSAcceptor::getProtocol returns IPPROTO_TCP.
+TEST_F(TLSAcceptorTest, getProtocol) {
+    EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
+}
+
+// Test that TLSAcceptor::getNative returns valid socket descriptor.
+TEST_F(TLSAcceptorTest, getNative) {
+    // Initially the descriptor should be invalid (negative).
+    ASSERT_LT(acceptor_.getNative(), 0);
+    // Now open the socket and make sure the returned descriptor is now valid.
+    ASSERT_NO_THROW(acceptorOpen());
+    EXPECT_GE(acceptor_.getNative(), 0);
+}
+
+// macOS 10.12.3 has a bug which causes the connections to not enter
+// the TIME-WAIT state and they never get closed.
+#if !defined (OS_OSX)
+
+// Test that TLSAcceptor::close works properly.
+TEST_F(TLSAcceptorTest, close) {
+    // Initialize acceptor.
+    acceptorOpen();
+    acceptor_.bind(endpoint_);
+    acceptor_.listen();
+
+    // Start accepting new connections.
+    accept();
+
+    // Create 10 new TLS connections (client side).
+    for (unsigned int i = 0; i < 10; ++i) {
+        connect();
+    }
+
+    // Close the acceptor before connections are accepted.
+    acceptor_.close();
+
+    // Run the IO service.
+    io_service_.run();
+
+    // The connections should have been aborted.
+    EXPECT_EQ(1, connections_num_);
+    EXPECT_EQ(1, aborted_connections_num_);
+    EXPECT_EQ(1, connections_.size());
+}
+
+#endif
+
+}
diff --git a/src/lib/asiolink/asiolink/tests/tls_socket_unittest.cc b/src/lib/asiolink/asiolink/tests/tls_socket_unittest.cc
new file mode 100644 (file)
index 0000000..c2572c0
--- /dev/null
@@ -0,0 +1,560 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @brief Test of TLSSocket
+///
+/// Tests the functionality of a TLSSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tls_socket.h>
+#include <asiolink/testutils/test_tls.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cstddef>
+#include <cstdlib>
+#include <errno.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string>
+#include <vector>
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5303;
+
+/// @todo Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O completes.
+/// The arguments to the completion callback are stored for later retrieval.
+class TLSCallback {
+public:
+    /// @brief Operations the server is doing
+    enum Operation {
+        ACCEPT = 0,     ///< accept() was issued
+        OPEN = 1,       ///< Client connected to server
+        HANDSHAKE = 2,  ///< TLS handshake completed
+        READ = 3,       ///< Asynchronous read completed
+        WRITE = 4,      ///< Asynchronous write completed
+        NONE = 5        ///< "Not set" state
+    };
+
+    /// @brief Minimum size of buffers
+    enum {
+        MIN_SIZE = (64 * 1024 + 2)          ///< 64kB + two bytes for a count
+    };
+
+    struct PrivateData {
+        PrivateData() :
+            error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0),
+            name_(""), queued_(NONE), called_(NONE), data_(MIN_SIZE, 0)
+        {}
+
+        boost::system::error_code  error_code_;    ///< Completion error code
+        size_t                     length_;        ///< Bytes transferred in this I/O
+        size_t                     cumulative_;    ///< Cumulative bytes transferred
+        size_t                     expected_;      ///< Expected amount of data
+        size_t                     offset_;        ///< Where to put data in buffer
+        std::string                name_;          ///< Which of the objects this is
+        Operation                  queued_;        ///< Queued operation
+        Operation                  called_;        ///< Which callback called
+        std::vector<uint8_t>       data_;          ///< Receive buffer
+    };
+
+    /// @brief Constructor
+    ///
+    /// Constructs the object.  It also creates the data member pointed to by
+    /// a shared pointer.  When used as a callback object, this is copied as it
+    /// is passed into the asynchronous function.  This means that there are two
+    /// objects and inspecting the one we passed in does not tell us anything.
+    ///
+    /// Therefore we use a boost::shared_ptr.  When the object is copied, the
+    /// shared pointer is copied, which leaves both objects pointing to the same
+    /// data.
+    ///
+    /// @param which Which of the two callback objects this is
+    TLSCallback(std::string which) : ptr_(new PrivateData())
+    {
+        ptr_->name_ = which;
+    }
+
+    /// @brief Destructor
+    ///
+    /// No code needed, destroying the shared pointer destroys the private data.
+    virtual ~TLSCallback()
+    {}
+
+    /// @brief Client Callback Function
+    ///
+    /// Called when an asynchronous operation is completed by the client, this
+    /// stores the origin of the operation in the client_called_ data member.
+    ///
+    /// @param ec I/O completion error code passed to callback function.
+    /// @param length Number of bytes transferred
+    void operator()(boost::system::error_code ec = boost::system::error_code(),
+                            size_t length = 0)
+    {
+        setCode(ec.value());
+        ptr_->called_ = ptr_->queued_;
+        ptr_->length_ = length;
+    }
+
+    /// @brief Get I/O completion error code
+    int getCode() {
+        return (ptr_->error_code_.value());
+    }
+
+    /// @brief Set I/O completion code
+    ///
+    /// @param code New value of completion code
+    void setCode(int code) {
+        ptr_->error_code_ = boost::system::error_code(code, boost::system::error_code().category());
+    }
+
+    /// @brief Get number of bytes transferred in I/O
+    size_t& length() {
+        return (ptr_->length_);
+    }
+
+    /// @brief Get cumulative number of bytes transferred in I/O
+    size_t& cumulative() {
+        return (ptr_->cumulative_);
+    }
+
+    /// @brief Get expected amount of data
+    size_t& expected() {
+        return (ptr_->expected_);
+    }
+
+    /// @brief Get offset into data
+    size_t& offset() {
+        return (ptr_->offset_);
+    }
+
+    /// @brief Get data member
+    uint8_t* data() {
+        return (&ptr_->data_[0]);
+    }
+
+    /// @brief Get flag to say what was queued
+    Operation& queued() {
+        return (ptr_->queued_);
+    }
+
+    /// @brief Get flag to say when callback was called
+    Operation& called() {
+        return (ptr_->called_);
+    }
+
+    /// @brief Return instance of callback name
+    std::string& name() {
+        return (ptr_->name_);
+    }
+
+private:
+    boost::shared_ptr<PrivateData>  ptr_;   ///< Pointer to private data
+};
+
+
+// Read Server Data
+//
+// Called in the part of the test that has the client send a message to the
+// server, this loops until all the data has been read (synchronously) by the
+// server.
+//
+// "All the data read" means that the server has received a message that is
+// preceded by a two-byte count field and that the total amount of data received
+// from the remote end is equal to the value in the count field plus two bytes
+// for the count field itself.
+//
+// @param stream Stream on which the server is reading data
+// @param server_cb Structure in which server data is held.
+void
+serverRead(TlsStreamImpl& stream, TLSCallback& server_cb) {
+
+    // As we may need to read multiple times, keep a count of the cumulative
+    // amount of data read and do successive reads into the appropriate part
+    // of the buffer.
+    //
+    // Note that there are no checks for buffer overflow - this is a test
+    // program and we have sized the buffer to be large enough for the test.
+    server_cb.cumulative() = 0;
+
+    bool complete = false;
+    while (!complete) {
+
+        // Read block of data and update cumulative amount of data received.
+        server_cb.length() = stream.read_some(
+            boost::asio::buffer(server_cb.data() + server_cb.cumulative(),
+                                TLSCallback::MIN_SIZE - server_cb.cumulative()));
+        server_cb.cumulative() += server_cb.length();
+
+        // If we have read at least two bytes, we can work out how much we
+        // should be reading.
+        if (server_cb.cumulative() >= 2) {
+            server_cb.expected() = readUint16(server_cb.data(), server_cb.length());
+            if ((server_cb.expected() + 2) == server_cb.cumulative()) {
+
+                // Amount of data read from stream equals the size of the
+                // message (as indicated in the first two bytes of the message)
+                // plus the size of the count field.  Therefore we have received
+                // all the data.
+                complete = true;
+            }
+        }
+    }
+}
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TLSSocket, processReceivedData) {
+    // Amount of "real" data in the buffer
+    const uint16_t PACKET_SIZE = 16382;
+
+    // Used to instantiate socket
+    IOService service;
+    TlsContextPtr context(new TlsContext(CLIENT));
+    // Socket under test
+    TLSSocket<TLSCallback> test(service, context);
+    // Buffer to check
+    uint8_t inbuff[PACKET_SIZE + 2];
+    // Where data is put
+    OutputBufferPtr outbuff(new OutputBuffer(16));
+    // Expected amount of data
+    size_t expected;
+    // Where to put next data
+    size_t offset;
+    // Cumulative data received
+    size_t cumulative;
+
+    // Set some dummy values in the buffer to check
+    for (size_t i = 0; i < sizeof(inbuff); ++i) {
+        inbuff[i] = i % 256;
+    }
+
+    // Check that the method will handle various receive sizes.
+    writeUint16(PACKET_SIZE, inbuff, sizeof(inbuff));
+
+    cumulative = 0;
+    offset = 0;
+    expected = 0;
+    outbuff->clear();
+    bool complete = test.processReceivedData(inbuff, 1, cumulative, offset,
+                                             expected, outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(1, cumulative);
+    EXPECT_EQ(1, offset);
+    EXPECT_EQ(0, expected);
+    EXPECT_EQ(0, outbuff->getLength());
+
+    // Now pretend that we've received one more byte.
+    complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected,
+                                        outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(2, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(0, outbuff->getLength());
+
+    // Add another two bytes.  However, this time note that we have to offset
+    // in the input buffer because it is expected that the next chunk of data
+    // from the connection will be read into the start of the buffer.
+    complete = test.processReceivedData(inbuff + cumulative, 2, cumulative,
+                                        offset, expected, outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(4, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(2, outbuff->getLength());
+
+    const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+    EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+
+    // And add the remaining data.  Remember that "inbuff" is "PACKET_SIZE + 2"
+    // long.
+    complete = test.processReceivedData(inbuff + cumulative,
+                                        PACKET_SIZE + 2 - cumulative,
+                                        cumulative, offset, expected, outbuff);
+    EXPECT_TRUE(complete);
+    EXPECT_EQ(PACKET_SIZE + 2, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(PACKET_SIZE, outbuff->getLength());
+    dataptr = static_cast<const uint8_t*>(outbuff->getData());
+    EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+}
+
+/// @todo Need to add a test to check the cancel() method
+
+// Tests the operation of a TLSSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(TLSSocket, sequenceTest) {
+
+    // Common objects.
+    // Service object for async control
+    IOService service;
+
+    // The client - the TLSSocket being tested
+    TlsContextPtr client_ctx;
+    test::configClient(client_ctx);
+    // Socket under test
+    TLSSocket<TLSCallback> client(service, client_ctx);
+    // Async I/O callback function
+    TLSCallback client_cb("Client");
+    // Where client receives message from
+    TCPEndpoint client_remote_endpoint;
+    // Received data is put here
+    OutputBufferPtr client_buffer(new OutputBuffer(128));
+    // The server - with which the client communicates.
+    // Address of target server
+    IOAddress server_address(SERVER_ADDRESS);
+    // Server callback
+    TLSCallback server_cb("Server");
+    // Endpoint describing server
+    TCPEndpoint server_endpoint(server_address, SERVER_PORT);
+    // Address where server received message from
+    TCPEndpoint server_remote_endpoint;
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    // Stream used for server.
+    TlsStreamImpl server(service.get_io_service(), server_ctx->getContext());
+
+    // Step 1.  Create the connection between the client and the server.  Set
+    // up the server to accept incoming connections and have the client open
+    // a channel to it.
+
+    // Set up server - open socket and queue an accept.
+    server_cb.queued() = TLSCallback::ACCEPT;
+    server_cb.called() = TLSCallback::NONE;
+    server_cb.setCode(42);  // Some error
+    tcp::acceptor acceptor(service.get_io_service(),
+                           tcp::endpoint(tcp::v4(), SERVER_PORT));
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    acceptor.async_accept(server.lowest_layer(), server_cb);
+
+    // Set up client - connect to the server.
+    client_cb.queued() = TLSCallback::OPEN;
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.setCode(43);  // Some error
+    EXPECT_FALSE(client.isOpenSynchronous());
+    client.open(&server_endpoint, client_cb);
+
+    // Run the open and the accept callback and check that they ran.
+    while ((server_cb.called() == TLSCallback::NONE) ||
+           (client_cb.called() == TLSCallback::NONE)) {
+        service.run_one();
+    }
+    EXPECT_EQ(TLSCallback::ACCEPT, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+
+    EXPECT_EQ(TLSCallback::OPEN, client_cb.called());
+
+    // On some operating system the async_connect may return EINPROGRESS.
+    // This doesn't necessarily indicate an error. In most cases trying
+    // to asynchronously write and read from the socket would work just
+    // fine.
+    if ((client_cb.getCode()) != 0 && (client_cb.getCode() != EINPROGRESS)) {
+        ADD_FAILURE() << "expected error code of 0 or " << EINPROGRESS
+            << " as a result of async_connect, got " << client_cb.getCode();
+    }
+
+    // Perform handshake.
+    client_cb.queued() = TLSCallback::HANDSHAKE;
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.setCode(43);  // Some error
+    client.handshake(client_cb);
+
+    server_cb.queued() = TLSCallback::HANDSHAKE;
+    server_cb.called() = TLSCallback::NONE;
+    server_cb.setCode(42);  // Some error
+    server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+
+    while ((server_cb.called() == TLSCallback::NONE) ||
+           (client_cb.called() == TLSCallback::NONE)) {
+        service.run_one();
+    }
+    EXPECT_EQ(TLSCallback::HANDSHAKE, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+
+    EXPECT_EQ(TLSCallback::HANDSHAKE, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+
+    // Step 2.  Get the client to write to the server asynchronously.  The
+    // server will loop reading the data synchronously.
+
+    // Write asynchronously to the server.
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.queued() = TLSCallback::WRITE;
+    client_cb.setCode(143);  // Arbitrary number
+    client_cb.length() = 0;
+    client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+
+    // Wait for the client callback to complete. (Must do this first on
+    // Solaris: if we do the synchronous read first, the test hangs.)
+    while (client_cb.called() == TLSCallback::NONE) {
+        service.run_one();
+    }
+
+    // Synchronously read the data from the server.;
+    serverRead(server, server_cb);
+
+    // Check the client state
+    EXPECT_EQ(TLSCallback::WRITE, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length());
+
+    // ... and check what the server received.
+    EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative());
+    EXPECT_TRUE(equal(OUTBOUND_DATA,
+                (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)),
+                (server_cb.data() + 2)));
+
+    // Step 3.  Get the server to write all the data asynchronously and have the
+    // client loop (asynchronously) reading the data.  Note that we copy the
+    // data into the server's internal buffer in order to precede it with a two-
+    // byte count field.
+
+    // Have the server write asynchronously to the client.
+    server_cb.called() = TLSCallback::NONE;
+    server_cb.queued() = TLSCallback::WRITE;
+    server_cb.length() = 0;
+    server_cb.cumulative() = 0;
+
+    writeUint16(sizeof(INBOUND_DATA), server_cb.data(), TLSCallback::MIN_SIZE);
+    copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
+        (server_cb.data() + 2));
+    boost::asio::async_write(server,
+                             boost::asio::buffer(server_cb.data(),
+                                                 (sizeof(INBOUND_DATA) + 2)),
+                             server_cb);
+
+    // Have the client read asynchronously.
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.queued() = TLSCallback::READ;
+    client_cb.length() = 0;
+    client_cb.cumulative() = 0;
+    client_cb.expected() = 0;
+    client_cb.offset() = 0;
+
+    client.asyncReceive(client_cb.data(), TLSCallback::MIN_SIZE,
+                        client_cb.offset(), &client_remote_endpoint,
+                        client_cb);
+
+    // Run the callbacks. Several options are possible depending on how ASIO
+    // is implemented and whether the message gets fragmented:
+    //
+    // 1) The send handler may complete immediately, regardless of whether the
+    // data has been read by the client.  (This is the most likely.)
+    // 2) The send handler may only run after all the data has been read by
+    // the client. (This could happen if the client's TCP buffers were too
+    // small so the data was not transferred to the "remote" system until the
+    // remote buffer has been emptied one or more times.)
+    // 3) The client handler may be run a number of times to handle the message
+    // fragments and the server handler may run between calls of the client
+    // handler.
+    //
+    // So loop, running one handler at a time until we are certain that all the
+    // handlers have run.
+
+    bool server_complete = false;
+    bool client_complete = false;
+    while (!server_complete || !client_complete) {
+        service.run_one();
+
+        // Has the server run?
+        if (!server_complete) {
+            if (server_cb.called() != TLSCallback::NONE) {
+
+                // Yes.  Check that the send completed successfully and that
+                // all the data that was expected to have been sent was in fact
+                // sent.
+                EXPECT_EQ(TLSCallback::WRITE, server_cb.called());
+                EXPECT_EQ(0, server_cb.getCode());
+                EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length());
+                server_complete = true;
+            }
+        }
+
+        // Has the client run?
+        if (!client_complete) {
+
+            if (client_cb.called() == TLSCallback::NONE) {
+                // No. Run the service another time.
+                continue;
+            }
+
+            // Client callback must have run.  Check that it ran OK.
+            EXPECT_EQ(TLSCallback::READ, client_cb.called());
+            EXPECT_EQ(0, client_cb.getCode());
+
+            // Check if we need to queue another read, copying the data into
+            // the output buffer as we do so.
+            client_complete = client.processReceivedData(client_cb.data(),
+                                                         client_cb.length(),
+                                                         client_cb.cumulative(),
+                                                         client_cb.offset(),
+                                                         client_cb.expected(),
+                                                         client_buffer);
+
+            // If the data is not complete, queue another read.
+            if (!client_complete) {
+                client_cb.called() = TLSCallback::NONE;
+                client_cb.queued() = TLSCallback::READ;
+                client_cb.length() = 0;
+                client.asyncReceive(client_cb.data(), TLSCallback::MIN_SIZE ,
+                                    client_cb.offset(), &client_remote_endpoint,
+                                    client_cb);
+            }
+        }
+    }
+
+    // Both the send and the receive have completed.  Check that the received
+    // is what was sent.
+
+    // Check the client state
+    EXPECT_EQ(TLSCallback::READ, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative());
+    EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength());
+
+    // ... and check what the server sent.
+    EXPECT_EQ(TLSCallback::WRITE, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length());
+
+    // ... and that what was sent is what was received.
+    const uint8_t* received = static_cast<const uint8_t*>(client_buffer->getData());
+    EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)),
+                      received));
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
diff --git a/src/lib/asiolink/asiolink/tests/tls_unittest.cc b/src/lib/asiolink/asiolink/tests/tls_unittest.cc
new file mode 100644 (file)
index 0000000..ff06b96
--- /dev/null
@@ -0,0 +1,1304 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/crypto_tls.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/testutils/test_tls.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <list>
+#include <string>
+#include <vector>
+
+#ifdef WITH_OPENSSL
+#include <openssl/opensslv.h>
+#endif
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::cryptolink;
+using namespace std;
+
+namespace { // anonymous namespace.
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Name of the environment variable controlling the display
+/// (default off) of TLS error messages.
+const char KEA_TLS_CHECK_VERBOSE[] = "KEA_TLS_CHECK_VERBOSE";
+
+/// @brief Test TLS context class exposing protected methods.
+class TestTlsContext : public TlsContext {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param role The TLS role client or server.
+    explicit TestTlsContext(TlsRole role) : TlsContext(role) { }
+
+    /// @brief Destructor.
+    virtual ~TestTlsContext() { }
+
+    /// @brief Make protected methods visible in tests.
+    using TlsContext::setCertRequired;
+    using TlsContext::loadCaFile;
+    using TlsContext::loadCaPath;
+    using TlsContext::loadCertFile;
+    using TlsContext::loadKeyFile;
+};
+
+/// @brief Class of test callbacks.
+class TestCallback {
+public:
+    /// @brief State part.
+    class State {
+    public:
+        /// @brief Constructor.
+        State() : called_(false), error_code_() {
+        }
+
+        /// @brief Destructor.
+        virtual ~State() {
+        }
+
+        /// @brief Called flag.
+        ///
+        /// Initialized to false, set to true when the callback is called.
+        bool called_;
+
+        /// @brief Last error code.
+        boost::system::error_code error_code_;
+    };
+
+    /// @brief Constructor.
+    ///
+    /// Used to shared pointer to state to allow the callback object to
+    /// be copied keeping the state member values.
+    TestCallback() : state_(new State()) {
+    }
+
+    /// @brief Destructor.
+    virtual ~TestCallback() {
+    }
+
+    /// @brief Callback function (one argument).
+    ///
+    /// @parame ec Boost completion code.
+    void operator()(const boost::system::error_code& ec) {
+        state_->called_ = true;
+        state_->error_code_ = ec;
+    }
+
+    /// @brief Callback function (two arguments).
+    ///
+    /// @parame ec Boost completion code.
+    void operator()(const boost::system::error_code& ec, size_t) {
+        state_->called_ = true;
+        state_->error_code_ = ec;
+    }
+
+    /// @brief Get called value.
+    inline bool getCalled() const {
+        return (state_->called_);
+    }
+
+    /// @brief Get error code.
+    inline const boost::system::error_code& getCode() const {
+        return (state_->error_code_);
+    }
+
+protected:
+    /// @brief Pointer to state.
+    boost::shared_ptr<State> state_;
+};
+
+/// @brief The type of a test to be run.
+typedef function<void()> Test;
+
+/// @brief Class of an expected behavior.
+///
+/// Some TLS tests can not use the standard GTEST macros because they
+/// show different behaviors depending on the crypto backend and the
+/// boost library versions. Worse in some cases the behavior can not
+/// be deduced from them so #ifdef's do not work...
+///
+/// Until this is adopted / widespread the policy is to use these flexible
+/// expected behavior tests ONLY when needed.
+class Expected {
+private:
+    /// Constructor.
+    ///
+    /// @param throwing True when an exception should be thrown.
+    /// @param timeout True when a timeout should occur.
+    /// @param no_error True when no error should be returned.
+    /// @param message Expected message.
+    Expected(bool throwing, bool timeout, bool no_error,
+             const string& message)
+        : throwing_(throwing), timeout_(timeout), no_error_(no_error),
+          message_(message) {
+    }
+
+    /// @brief The throwing flag.
+    bool throwing_;
+
+    /// @brief The timeout flag.
+    bool timeout_;
+
+    /// @brief The no error flag.
+    bool no_error_;
+
+    /// @brief The expected error message.
+    string message_;
+
+public:
+    /// @brief Create an expected throwing exception behavior.
+    ///
+    /// @param message Expected message.
+    static Expected createThrow(const string& message) {
+        return (Expected(true, false, false, message));
+    }
+
+    /// @brief Create an expected timeout behavior.
+    static Expected createTimeout() {
+        return (Expected(false, true, false, ""));
+    }
+
+    /// @brief Create an expected no error behavior.
+    static Expected createNoError() {
+        return (Expected(false, false, true, ""));
+    }
+
+    /// @brief Create an expected error message behavior.
+    ///
+    /// @param message Expected message.
+    static Expected createError(const string& message) {
+        return (Expected(false, false, false, message));
+    }
+
+    /// @brief Get the throwing flag.
+    ///
+    /// @return The throwing flag.
+    bool getThrow() const {
+        return (throwing_);
+    }
+
+    /// @brief Get the timeout flag.
+    ///
+    /// @return The timeout flag.
+    bool getTimeout() const {
+        return (timeout_);
+    }
+
+    /// @brief Get the no error flag.
+    ///
+    /// @return The no error flag.
+    bool getNoError() const {
+        return (no_error_);
+    }
+
+    /// @brief Get the expected error message.
+    ///
+    /// @return The expected error message.
+    const string& getMessage() const {
+        return (message_);
+    }
+};
+
+/// @brief Class of expected behaviors.
+class Expecteds {
+private:
+    /// @brief List of expected behaviors.
+    list<Expected> list_;
+
+    /// @brief The error message for the verbose mode.
+    string errmsg_;
+
+public:
+    /// Constructor.
+    ///
+    /// @return An empty expected behavior list.
+    Expecteds() : list_(), errmsg_("") {
+    }
+
+    /// @brief Clear the list.
+    void clear() {
+        list_.clear();
+        errmsg_.clear();
+    }
+
+    /// @brief Add an expected throwing exception behavior.
+    ///
+    /// @param message Expected message.
+    void addThrow(const string& message) {
+        list_.push_back(Expected::createThrow(message));
+    }
+
+    /// @brief Add an expected timeout behavior.
+    void addTimeout() {
+        list_.push_back(Expected::createTimeout());
+    }
+
+    /// @brief Add an expected no error behavior.
+    void addNoError() {
+        list_.push_back(Expected::createNoError());
+    }
+
+    /// @brief Add an expected error message behavior.
+    ///
+    /// @param message Expected message.
+    void addError(const string& message) {
+        list_.push_back(Expected::createError(message));
+    }
+
+    /// @brief Display error messages.
+    ///
+    /// @return True if error messages are displayed.
+    static bool displayErrMsg() {
+        return (getenv(KEA_TLS_CHECK_VERBOSE));
+    }
+
+    /// @brief Has an error message.
+    ///
+    /// @return True when there is a cached error message.
+    bool hasErrMsg() const {
+        return (!errmsg_.empty());
+    }
+
+    /// @brief Get error message.
+    ///
+    /// @return The cached error message.
+    const string& getErrMsg() const {
+        return (errmsg_);
+    }
+
+    /// @brief Run a test which can throw.
+    ///
+    /// @param test The test to run.
+    void runCanThrow(const Test& test) {
+        // Check consistency.
+        for (auto const& exp : list_) {
+            if (!exp.getThrow() && !exp.getNoError()) {
+                ADD_FAILURE() << "inconsistent runCanThrow settings";
+            }
+        }
+
+        // Collect the test behavior.
+        bool thrown = false;
+        try {
+            test();
+        } catch (const LibraryError& ex) {
+            thrown = true;
+            errmsg_ = ex.what();
+        } catch (const exception& ex) {
+            thrown = true;
+            errmsg_ = ex.what();
+            ADD_FAILURE() << "expect only LibraryError exception";
+        }
+
+        // Check the no error case.
+        if (!thrown) {
+            for (auto const& exp : list_) {
+                if (exp.getNoError()) {
+                    // No error was expected: good.
+                    return;
+                }
+            }
+            // No error was not expected: bad.
+            ADD_FAILURE() << "no exception?";
+            return;
+        }
+
+        // Check the thrown message.
+        for (auto const& exp : list_) {
+            if (!exp.getThrow()) {
+                continue;
+            }
+            if (errmsg_ == exp.getMessage()) {
+                // Got an expected message: good.
+                return;
+            }
+        }
+        // The message was not expected: bad.
+        ADD_FAILURE() << "exception with unknown '" << errmsg_ << "'";
+    }
+
+    /// @brief Check the result of an asynchronous operation.
+    ///
+    /// @param party The name of the party.
+    /// @param callback The test callback of the an asynchronous.
+    void checkAsync(const string& party, const TestCallback& callback) {
+        // Check timeout i.e. the callback was not called.
+        if (!callback.getCalled()) {
+            bool expected = false;
+            for (auto const& exp : list_) {
+                if (exp.getTimeout()) {
+                    expected = true;
+                    break;
+                }
+            }
+            if (!expected) {
+                ADD_FAILURE() << "unexpected timeout";
+            }
+        }
+
+        // Check the no error case.
+        const boost::system::error_code& ec = callback.getCode();
+        if (!ec) {
+            for (auto const& exp : list_) {
+                if (exp.getTimeout() || exp.getNoError()) {
+                    // Expected timeout or no error: good.
+                    return;
+                }
+            }
+            // Should have failed but did not: bad.
+            ADD_FAILURE() << party << " did not failed as expected";
+            return;
+        }
+
+        // Got an error but was this one expected?
+        errmsg_ = ec.message();
+        for (auto const& exp : list_) {
+            if (exp.getTimeout() || exp.getNoError()) {
+                continue;
+            }
+            if (errmsg_ == exp.getMessage()) {
+                // This error message was expected: good.
+                return;
+            }
+        }
+        ADD_FAILURE() << party << " got unexpected error '" << errmsg_ << "'";
+    }
+
+};
+
+////////////////////////////////////////////////////////////////////////
+//                              TlsContext tests                      //
+////////////////////////////////////////////////////////////////////////
+
+// Test if we can get a client context.
+TEST(TLSTest, clientContext) {
+    TlsContextPtr ctx;
+    EXPECT_NO_THROW(ctx.reset(new TlsContext(TlsRole::CLIENT)));
+}
+
+// Test if we can get a server context.
+TEST(TLSTest, serverContext) {
+    TlsContextPtr ctx;
+    EXPECT_NO_THROW(ctx.reset(new TlsContext(TlsRole::SERVER)));
+}
+
+// Test if the cert required flag is handled as expected.
+TEST(TLSTest, certRequired) {
+    auto check = [] (TlsContext& ctx) -> bool {
+#ifdef WITH_BOTAN
+        /// @todo: Implement it
+        return (ctx.getCertRequired());
+#else // WITH_OPENSSL
+        ::SSL_CTX* ssl_ctx = ctx.getNativeContext();
+        if (!ssl_ctx) {
+            ADD_FAILURE() << "null SSL_CTX";
+            return (false);
+        }
+        int mode = SSL_CTX_get_verify_mode(ssl_ctx);
+        switch (mode) {
+        case SSL_VERIFY_NONE:
+            return (false);
+        case (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT):
+            return (true);
+        default:
+            ADD_FAILURE() << "unknown ssl_verify_mode: " << mode;
+            return (false);
+        }
+#endif
+    };
+
+    TestTlsContext ctx(TlsRole::CLIENT);
+    EXPECT_TRUE(ctx.getCertRequired());
+    EXPECT_TRUE(check(ctx));
+    ASSERT_NO_THROW(ctx.setCertRequired(false));
+    EXPECT_FALSE(ctx.getCertRequired());
+    EXPECT_FALSE(check(ctx));
+    ASSERT_NO_THROW(ctx.setCertRequired(true));
+    EXPECT_TRUE(ctx.getCertRequired());
+    EXPECT_TRUE(check(ctx));
+}
+
+// Test if the certificate authority can be loaded.
+TEST(TLSTest, loadCAFile) {
+    string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+    TestTlsContext ctx(TlsRole::CLIENT);
+    EXPECT_NO_THROW(ctx.loadCaFile(ca));
+}
+
+// Test that no certificate authority gives an error.
+TEST(TLSTest, loadNoCAFile) {
+    Expecteds exps;
+    // Botan error.
+    exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file");
+    // OpenSSL error.
+    exps.addThrow("No such file or directory");
+    exps.runCanThrow([] {
+        string ca("/no-such-file");
+        TestTlsContext ctx(TlsRole::CLIENT);
+        ctx.loadCaFile(ca);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+// Test that a directory can be loaded.
+TEST(TLSTest, loadCAPath) {
+    string ca(TEST_CA_DIR);
+    TestTlsContext ctx(TlsRole::CLIENT);
+    EXPECT_NO_THROW(ctx.loadCaPath(ca));
+}
+
+// Test that a certificate is wanted.
+TEST(TLSTest, loadKeyCA) {
+    Expecteds exps;
+    // Botan error.
+    exps.addThrow("Flatfile_Certificate_Store::Flatfile_Certificate_Store cert file is empty");
+    // LibreSSL or old OpenSSL does not check.
+    exps.addNoError();
+    // Recent OpenSSL error.
+    exps.addThrow("no certificate or crl found");
+    exps.runCanThrow([] {
+        string ca(string(TEST_CA_DIR) + "/kea-ca.key");
+        TestTlsContext ctx(TlsRole::CLIENT);
+        ctx.loadCaFile(ca);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+// Test if the end entity certificate can be loaded.
+TEST(TLSTest, loadCertFile) {
+    string cert(string(TEST_CA_DIR) + "/kea-client.crt");
+    TestTlsContext ctx(TlsRole::CLIENT);
+    EXPECT_NO_THROW(ctx.loadCertFile(cert));
+}
+
+// Test that no end entity certificate gives an error.
+TEST(TLSTest, loadNoCertFile) {
+    Expecteds exps;
+    // Botan error.
+    exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file");
+    // OpenSSL error.
+    exps.addThrow("No such file or directory");
+    exps.runCanThrow([] {
+        string cert("/no-such-file");
+        TestTlsContext ctx(TlsRole::CLIENT);
+        ctx.loadCertFile(cert);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+// Test that a certificate is wanted.
+TEST(TLSTest, loadCsrCertFile) {
+    Expecteds exps;
+    // Botan error.
+    exps.addThrow("Expected a certificate, got 'CERTIFICATE REQUEST'");
+    // OpenSSL error.
+    exps.addThrow("no start line");
+    exps.runCanThrow([] {
+        string cert(string(TEST_CA_DIR) + "/kea-client.csr");
+        TestTlsContext ctx(TlsRole::CLIENT);
+        ctx.loadCertFile(cert);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+// Test if the private key can be loaded.
+TEST(TLSTest, loadKeyFile) {
+    string key(string(TEST_CA_DIR) + "/kea-client.key");
+    TestTlsContext ctx(TlsRole::CLIENT);
+    EXPECT_NO_THROW(ctx.loadKeyFile(key));
+}
+
+// Test that no private key gives an error.
+TEST(TLSTest, loadNoKeyFile) {
+    Expecteds exps;
+    // Botan error.
+    exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file");
+    // OpenSSL error.
+    exps.addThrow("No such file or directory");
+    // Another possible error.
+    exps.addThrow("PEM lib");
+    exps.runCanThrow([] {
+        string key("/no-such-file");
+        TestTlsContext ctx(TlsRole::CLIENT);
+        ctx.loadKeyFile(key);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+// Test that a private key is wanted.
+TEST(TLSTest, loadCertKeyFile) {
+    Expecteds exps;
+    // Botan error.
+    string botan_error = "PKCS #8 private key decoding failed with PKCS #8: ";
+    botan_error += "Unknown PEM label CERTIFICATE";
+    exps.addThrow(botan_error);
+    // OpenSSL error.
+    exps.addThrow("no start line");
+    // Another possible error.
+    exps.addThrow("No such file or directory");
+    exps.runCanThrow([] {
+        string key(string(TEST_CA_DIR) + "/kea-client.crt");
+        TestTlsContext ctx(TlsRole::CLIENT);
+        ctx.loadKeyFile(key);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+// Test that the certificate and private key must match.
+TEST(TLSTest, loadMismatch) {
+    Expecteds exps;
+    exps.addNoError();
+    exps.runCanThrow([] {
+        string cert(string(TEST_CA_DIR) + "/kea-server.crt");
+        TestTlsContext ctx(TlsRole::SERVER);
+        ctx.loadCertFile(cert);
+    });
+    // Keep no error for at least Botan.
+    // OpenSSL error.
+    exps.addThrow("key values mismatch");
+    exps.runCanThrow([] {
+        string key(string(TEST_CA_DIR) + "/kea-client.key");
+        TestTlsContext ctx(TlsRole::SERVER);
+        // In fact OpenSSL checks only RSA key values...
+        // The explicit check function is SSL_CTX_check_private_key.
+        ctx.loadKeyFile(key);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+// Test the configure class method.
+TEST(TLSTest, configure) {
+    TlsContextPtr ctx;
+    string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+    string cert(string(TEST_CA_DIR) + "/kea-client.crt");
+    string key(string(TEST_CA_DIR) + "/kea-client.key");
+    EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::CLIENT,
+                                          ca, cert, key, true));
+    ASSERT_TRUE(ctx);
+    EXPECT_EQ(TlsRole::CLIENT, ctx->getRole());
+    EXPECT_TRUE(ctx->getCertRequired());
+
+    // Retry using the directory and the server.
+    ctx.reset();
+    ca = TEST_CA_DIR;
+    cert = string(TEST_CA_DIR) + "/kea-server.crt";
+    key = string(TEST_CA_DIR) + "/kea-server.key";
+    EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::SERVER,
+                                          ca, cert, key, false));
+    ASSERT_TRUE(ctx);
+    EXPECT_EQ(TlsRole::SERVER, ctx->getRole());
+    EXPECT_FALSE(ctx->getCertRequired());
+}
+
+// Test the configure class method error case.
+TEST(TLSTest, configureError) {
+    // The error case.
+    Expecteds exps;
+    // Botan error.
+    exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file");
+    // OpenSSL error.
+    exps.addThrow("No such file or directory");
+    exps.runCanThrow([] {
+        TlsContextPtr ctx1;
+        string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+        string cert = "/no-such-file";
+        string key = string(TEST_CA_DIR) + "/kea-client.key";
+        TlsContext::configure(ctx1, TlsRole::CLIENT,
+                              ca, cert, key, true);
+        // The context is reseted on errors.
+        EXPECT_FALSE(ctx1);
+    });
+    if (Expecteds::displayErrMsg()) {
+        std::cout << exps.getErrMsg() << "\n";
+    }
+}
+
+////////////////////////////////////////////////////////////////////////
+//                      Basic handshake failures                      //
+////////////////////////////////////////////////////////////////////////
+
+// Test if we can get a stream.
+TEST(TLSTest, stream) {
+    IOService service;
+    TlsContextPtr ctx(new TlsContext(TlsRole::CLIENT));
+    boost::scoped_ptr<TlsStream<TestCallback> > st;
+    EXPECT_NO_THROW(st.reset(new TlsStream<TestCallback>(service, ctx)));
+}
+
+// Test what happens when handshake is forgotten.
+TEST(TLSTest, noHandshake) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<TestCallback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    TestCallback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    TlsContextPtr client_ctx;
+    test::configClient(client_ctx);
+    TlsStream<TestCallback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    TestCallback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Setup a timeout.
+    IntervalTimer timer1(service);
+    bool timeout = false;
+    timer1.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Send on the client.
+    char send_buf[] = "some text...";
+    TestCallback send_cb;
+    async_write(client, boost::asio::buffer(send_buf), send_cb);
+    while (!timeout && !send_cb.getCalled()) {
+        service.run_one();
+    }
+    timer1.cancel();
+
+    Expecteds exps;
+    // Botan error.
+    exps.addError("InvalidObjectState");
+    // OpenSSL error.
+    exps.addError("uninitialized");
+    exps.checkAsync("send", send_cb);
+    if (Expecteds::displayErrMsg()) {
+        std::cout << "send: " << exps.getErrMsg() << "\n";
+    }
+
+    // Setup a second timeout.
+    IntervalTimer timer2(service);
+    timeout = false;
+    timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Receive on the server.
+    vector<char> receive_buf(64);
+    TestCallback receive_cb;
+    server.async_read_some(boost::asio::buffer(receive_buf), receive_cb);
+    while (!timeout && !receive_cb.getCalled()) {
+        service.run_one();
+    }
+    timer2.cancel();
+
+    exps.clear();
+    // On Botan and some OpenSSL the receive party hangs.
+    exps.addTimeout();
+    // OpenSSL error,
+    exps.addError("uninitialized");
+    exps.checkAsync("receive", receive_cb);
+    if (Expecteds::displayErrMsg()) {
+        if (timeout) {
+            std::cout << "receive timeout\n";
+        } else {
+            std::cout << "receive: " << exps.getErrMsg() << "\n";
+        }
+    }
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the server was not configured.
+TEST(TLSTest, serverNotConfigured) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+    // Skip config.
+    TlsStream<TestCallback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    TestCallback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    TlsContextPtr client_ctx;
+    test::configClient(client_ctx);
+    TlsStream<TestCallback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    TestCallback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Setup a timeout.
+    IntervalTimer timer(service);
+    bool timeout = false;
+    timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Perform TLS handshakes.
+    TestCallback server_cb;
+    server.handshake(server_cb);
+    TestCallback client_cb;
+    client.handshake(client_cb);
+    while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+        service.run_one();
+    }
+    timer.cancel();
+
+    Expecteds exps;
+    // Botan error.
+    exps.addError("handshake_failure");
+    // LibreSSL error.
+    exps.addError("no shared cipher");
+    // OpenSSL error.
+    exps.addError("sslv3 alert handshake failure");
+    exps.checkAsync("server", server_cb);
+    if (Expecteds::displayErrMsg()) {
+        std::cout << "server: " << exps.getErrMsg() << "\n";
+    }
+
+    exps.clear();
+    // On Botan and some OpenSSL the client hangs.
+    exps.addTimeout();
+    // OpenSSL error.
+    exps.addError("sslv3 alert handshake failure");
+    exps.checkAsync("client", client_cb);
+    if (Expecteds::displayErrMsg()) {
+        if (timeout) {
+            std::cout << "client timeout\n";
+        } else {
+            std::cout << "client: " << exps.getErrMsg() << "\n";
+        }
+    }
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client was not configured.
+TEST(TLSTest, clientNotConfigured) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<TestCallback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    TestCallback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    TlsContextPtr client_ctx(new TlsContext(TlsRole::CLIENT));
+    // Skip config.
+    TlsStream<TestCallback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    TestCallback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Setup a timeout.
+    IntervalTimer timer(service);
+    bool timeout = false;
+    timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Perform TLS handshakes.
+    TestCallback server_cb;
+    server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+    TestCallback client_cb;
+    client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+    while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+        service.run_one();
+    }
+    timer.cancel();
+
+    Expecteds exps;
+    // On Botan and some OpenSSL the server hangs.
+    exps.addTimeout();
+    // OpenSSL error.
+    exps.addError("tlsv1 alert unknown ca");
+    exps.checkAsync("server", server_cb);
+    if (Expecteds::displayErrMsg()) {
+        if (timeout) {
+            std::cout << "server timeout\n";
+        } else {
+            std::cout << "server: " << exps.getErrMsg() << "\n";
+        }
+    }
+
+    exps.clear();
+    // Botan error (unfortunately a bit generic).
+    exps.addError("bad_certificate");
+    // LibreSSL error.
+    exps.addError("tlsv1 alert unknown ca");
+    // OpenSSL error.
+    exps.addError("certificate verify failed");
+    // The client should not hang.
+    exps.checkAsync("client", client_cb);
+    if (Expecteds::displayErrMsg()) {
+        std::cout << "client: " << exps.getErrMsg() << "\n";
+    }
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client is HTTP (vs HTTPS).
+TEST(TLSTest, clientHTTPnoS) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<TestCallback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    TestCallback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    tcp::socket client(service.get_io_service());
+
+    // Connect to.
+    client.open(tcp::v4());
+    TestCallback connect_cb;
+    client.async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Setup a timeout.
+    IntervalTimer timer(service);
+    bool timeout = false;
+    timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Perform server TLS handshake.
+    TestCallback server_cb;
+    server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+
+    // Client sending a HTTP GET.
+    char send_buf[] = "GET / HTTP/1.1\r\n";
+    TestCallback client_cb;
+    client.async_send(boost::asio::buffer(send_buf), client_cb);
+
+    while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+        service.run_one();
+    }
+    timer.cancel();
+
+    Expecteds exps;
+    // Botan server hangs.
+    exps.addTimeout();
+    // LibreSSL error.
+    exps.addError("tlsv1 alert protocol version");
+    // OpenSSL error (OpenSSL recognizes HTTP).
+    exps.addError("http request");
+    // Another OpenSSL error (not all OpenSSL recognizes HTTP).
+    exps.addError("wrong version number");
+    exps.checkAsync("server", server_cb);
+    if (Expecteds::displayErrMsg()) {
+        if (timeout) {
+            std::cout << "server timeout\n";
+        } else {
+            std::cout << "server: " << exps.getErrMsg() << "\n";
+        }
+    }
+
+    // No error at the client.
+    EXPECT_TRUE(client_cb.getCalled());
+    EXPECT_FALSE(client_cb.getCode());
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client does not use HTTP nor HTTP.
+TEST(TLSTest, unknownClient) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<TestCallback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    TestCallback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    tcp::socket client(service.get_io_service());
+
+    // Connect to.
+    client.open(tcp::v4());
+    TestCallback connect_cb;
+    client.async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Setup a timeout.
+    IntervalTimer timer(service);
+    bool timeout = false;
+    timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Perform server TLS handshake.
+    TestCallback server_cb;
+    server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+
+    // Client sending something which is not a TLS ClientHello.
+    char send_buf[] = "hello my server...";
+    TestCallback client_cb;
+    client.async_send(boost::asio::buffer(send_buf), client_cb);
+
+    while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+        service.run_one();
+    }
+    timer.cancel();
+
+    Expecteds exps;
+    // Botan error.
+    exps.addError("record_overflow");
+    // LibreSSL error.
+    exps.addError("tlsv1 alert protocol version");
+    // Old OpenSSL error.
+    exps.addError("unknown protocol");
+    // Recent OpenSSL error.
+    exps.addError("wrong version number");
+    exps.checkAsync("server", server_cb);
+    if (Expecteds::displayErrMsg()) {
+        std::cout << "server: " << exps.getErrMsg() << "\n";
+    }
+
+    // No error on the client side.
+    EXPECT_TRUE(client_cb.getCalled());
+    EXPECT_FALSE(client_cb.getCode());
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a certificate from another CA.
+TEST(TLSTest, anotherClient) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<TestCallback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    TestCallback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part using a certificate signed by another CA.
+    TlsContextPtr client_ctx;
+    test::configOther(client_ctx);
+    TlsStream<TestCallback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    TestCallback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Setup a timeout.
+    IntervalTimer timer(service);
+    bool timeout = false;
+    timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Perform TLS handshakes.
+    TestCallback server_cb;
+    server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+    TestCallback client_cb;
+    client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+    while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+        service.run_one();
+    }
+    timer.cancel();
+
+    Expecteds exps;
+    // Botan error.
+    exps.addError("bad_certificate");
+    // LibreSSL error.
+    exps.addError("tlsv1 alert unknown ca");
+    // OpenSSL error.
+    // Full error is:
+    // error 20 at 0 depth lookup:unable to get local issuer certificate
+    exps.addError("certificate verify failed");
+    exps.checkAsync("server", server_cb);
+    if (Expecteds::displayErrMsg()) {
+        std::cout << "server: " << exps.getErrMsg() << "\n";
+    }
+
+    exps.clear();
+    // Botan client hangs.
+    exps.addTimeout();
+    // LibreSSL and recent OpenSSL do not fail.
+    exps.addNoError();
+    // Old OpenSSL error.
+    exps.addError("tlsv1 alert unknown ca");
+    exps.checkAsync("client", client_cb);
+    if (Expecteds::displayErrMsg()) {
+        if (timeout) {
+            std::cout << "client timeout\n";
+        } else if (exps.hasErrMsg()) {
+            std::cout << "client: " << exps.getErrMsg() << "\n";
+        }
+    }
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a self-signed certificate.
+TEST(TLSTest, selfSigned) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<TestCallback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    TestCallback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part using a self-signed certificate.
+    TlsContextPtr client_ctx;
+    test::configSelf(client_ctx);
+    TlsStream<TestCallback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    TestCallback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Setup a timeout.
+    IntervalTimer timer(service);
+    bool timeout = false;
+    timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+    // Perform TLS handshakes.
+    TestCallback server_cb;
+    server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+    TestCallback client_cb;
+    client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+    while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+        service.run_one();
+    }
+    timer.cancel();
+
+    Expecteds exps;
+    // Botan error.
+    exps.addError("bad_certificate");
+    // LibreSSL error.
+    exps.addError("tlsv1 alert unknown ca");
+    // OpenSSL error.
+    // Full error is:
+    // error 18 at 0 depth lookup:self signed certificate
+    exps.addError("certificate verify failed");
+    exps.checkAsync("server", server_cb);
+    if (Expecteds::displayErrMsg()) {
+        std::cout << "server: " << exps.getErrMsg() << "\n";
+    }
+
+    exps.clear();
+    // Botan client hangs.
+    exps.addTimeout();
+    // LibreSSL and recent OpenSSL do not fail.
+    exps.addNoError();
+    // Old OpenSSL error.
+    exps.addError("tlsv1 alert unknown ca");
+    exps.checkAsync("client", client_cb);
+    if (Expecteds::displayErrMsg()) {
+        if (timeout) {
+            std::cout << "client timeout\n";
+        } else if (exps.hasErrMsg()) {
+            std::cout << "client: " << exps.getErrMsg() << "\n";
+        }
+    }
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+} // end of anonymous namespace.
diff --git a/src/lib/asiolink/asiolink/tests/udp_endpoint_unittest.cc b/src/lib/asiolink/asiolink/tests/udp_endpoint_unittest.cc
new file mode 100644 (file)
index 0000000..cf932f1
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/udp_endpoint.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc::asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// boost::asio::ip::udp::endpoint object.
+
+TEST(UDPEndpointTest, v4Address) {
+    const string test_address("192.0.2.1");
+    const unsigned short test_port = 5301;
+
+    IOAddress address(test_address);
+    UDPEndpoint endpoint(address, test_port);
+
+    EXPECT_TRUE(address == endpoint.getAddress());
+    EXPECT_EQ(test_port, endpoint.getPort());
+    EXPECT_EQ(static_cast<short>(IPPROTO_UDP), endpoint.getProtocol());
+    EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(UDPEndpointTest, v6Address) {
+    const string test_address("2001:db8::1235");
+    const unsigned short test_port = 5302;
+
+    IOAddress address(test_address);
+    UDPEndpoint endpoint(address, test_port);
+
+    EXPECT_TRUE(address == endpoint.getAddress());
+    EXPECT_EQ(test_port, endpoint.getPort());
+    EXPECT_EQ(static_cast<short>(IPPROTO_UDP), endpoint.getProtocol());
+    EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/asiolink/tests/udp_socket_unittest.cc
new file mode 100644 (file)
index 0000000..1fb9616
--- /dev/null
@@ -0,0 +1,324 @@
+// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// \brief Test of UDPSocket
+///
+/// Tests the functionality of a UDPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <string>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <algorithm>
+#include <cstdlib>
+#include <cstddef>
+#include <vector>
+
+
+using namespace boost::asio;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5301;
+
+// TODO: Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+///
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O
+/// completes.  The arguments to the completion callback are stored for later
+/// retrieval.
+class UDPCallback {
+public:
+
+    struct PrivateData {
+        PrivateData() :
+            error_code_(), length_(0), called_(false), name_("")
+        {}
+
+        boost::system::error_code  error_code_;    ///< Completion error code
+        size_t                     length_;        ///< Number of bytes transferred
+        bool                       called_;        ///< Set true when callback called
+        std::string                name_;          ///< Which of the objects this is
+    };
+
+    /// \brief Constructor
+    ///
+    /// Constructs the object.  It also creates the data member pointed to by
+    /// a shared pointer.  When used as a callback object, this is copied as it
+    /// is passed into the asynchronous function.  This means that there are two
+    /// objects and inspecting the one we passed in does not tell us anything.
+    ///
+    /// Therefore we use a boost::shared_ptr.  When the object is copied, the
+    /// shared pointer is copied, which leaves both objects pointing to the same
+    /// data.
+    ///
+    /// \param which Which of the two callback objects this is
+    UDPCallback(const std::string& which) : ptr_(new PrivateData())
+    {
+        setName(which);
+    }
+
+    /// \brief Destructor
+    ///
+    /// No code needed, destroying the shared pointer destroys the private data.
+    virtual ~UDPCallback()
+    {}
+
+    /// \brief Callback Function
+    ///
+    /// Called when an asynchronous I/O completes, this stores the
+    /// completion error code and the number of bytes transferred.
+    ///
+    /// \param ec I/O completion error code passed to callback function.
+    /// \param length Number of bytes transferred
+    virtual void operator()(boost::system::error_code ec, size_t length = 0) {
+        ptr_->error_code_ = ec;
+        setLength(length);
+        setCalled(true);
+    }
+
+    /// \brief Get I/O completion error code
+    int getCode() {
+        return (ptr_->error_code_.value());
+    }
+
+    /// \brief Set I/O completion code
+    ///
+    /// \param code New value of completion code
+    void setCode(int code) {
+        ptr_->error_code_ = boost::system::error_code(code, boost::system::error_code().category());
+    }
+
+    /// \brief Get number of bytes transferred in I/O
+    size_t getLength() const {
+        return (ptr_->length_);
+    }
+
+    /// \brief Set number of bytes transferred in I/O
+    ///
+    /// \param length New value of length parameter
+    void setLength(size_t length) {
+        ptr_->length_ = length;
+    }
+
+    /// \brief Get flag to say when callback was called
+    bool getCalled() const {
+        return (ptr_->called_);
+    }
+
+    /// \brief Set flag to say when callback was called
+    ///
+    /// \param called New value of called parameter
+    void setCalled(bool called) {
+        ptr_->called_ = called;
+    }
+
+    /// \brief Return instance of callback name
+    std::string getName() const {
+        return (ptr_->name_);
+    }
+
+    /// \brief Set callback name
+    ///
+    /// \param name New value of the callback name
+    void setName(const std::string& name) {
+        ptr_->name_ = name;
+    }
+
+private:
+    boost::shared_ptr<PrivateData>  ptr_;   ///< Pointer to private data
+};
+
+// Receive complete method should return true regardless of what is in the first
+// two bytes of a buffer.
+
+TEST(UDPSocket, processReceivedData) {
+    IOService               service;        // Used to instantiate socket
+    UDPSocket<UDPCallback>  test(service);  // Socket under test
+    uint8_t                 inbuff[32];     // Buffer to check
+    OutputBufferPtr         outbuff(new OutputBuffer(16));
+                                            // Where data is put
+                                            // cppcheck-suppress variableScope
+    size_t                  expected;       // Expected amount of data
+                                            // cppcheck-suppress variableScope
+    size_t                  offset;         // Where to put next data
+                                            // cppcheck-suppress variableScope
+    size_t                  cumulative;     // Cumulative data received
+
+    // Set some dummy values in the buffer to check
+    for (uint8_t i = 0; i < sizeof(inbuff); ++i) {
+        inbuff[i] = i;
+    }
+
+    // Expect that the value is true whatever number is written in the first
+    // two bytes of the buffer.
+    uint16_t count = 0;
+    for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+        writeUint16(count, inbuff, sizeof(inbuff));
+
+        // Set some random values
+        cumulative = 5;
+        offset = 10;
+        expected = 15;
+        outbuff->clear();
+
+        bool completed = test.processReceivedData(inbuff, sizeof(inbuff),
+                                                  cumulative, offset, expected,
+                                                  outbuff);
+        EXPECT_TRUE(completed);
+        EXPECT_EQ(sizeof(inbuff), cumulative);
+        EXPECT_EQ(0, offset);
+        EXPECT_EQ(sizeof(inbuff), expected);
+
+        const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+        EXPECT_TRUE(equal(inbuff, inbuff + sizeof(inbuff) - 1, dataptr));
+    }
+}
+
+// TODO: Need to add a test to check the cancel() method
+
+// Tests the operation of a UDPSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(UDPSocket, SequenceTest) {
+
+    // Common objects.
+    IOService   service;                    // Service object for async control
+
+    // Server
+    IOAddress   server_address(SERVER_ADDRESS); // Address of target server
+    UDPCallback server_cb("Server");        // Server callback
+    UDPEndpoint server_endpoint(            // Endpoint describing server
+        server_address, SERVER_PORT);
+    UDPEndpoint server_remote_endpoint;     // Address where server received message from
+
+    // The client - the UDPSocket being tested
+    UDPSocket<UDPCallback>  client(service);// Socket under test
+    UDPCallback client_cb("Client");        // Async I/O callback function
+    UDPEndpoint client_remote_endpoint;     // Where client receives message from
+    size_t      client_cumulative = 0;      // Cumulative data received
+    size_t      client_offset = 0;          // Offset into buffer where data is put
+    size_t      client_expected = 0;        // Expected amount of data
+    OutputBufferPtr client_buffer(new OutputBuffer(16));
+                                            // Where data is put
+
+    // The server - with which the client communicates.  For convenience, we
+    // use the same io_service, and use the endpoint object created for
+    // the client to send to as the endpoint object in the constructor.
+    boost::asio::ip::udp::socket server(service.get_io_service(),
+        server_endpoint.getASIOEndpoint());
+    server.set_option(socket_base::reuse_address(true));
+
+    // Assertion to ensure that the server buffer is large enough
+    char data[UDPSocket<UDPCallback>::MIN_SIZE];
+    ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA));
+
+    // Open the client socket - the operation should be synchronous
+    EXPECT_TRUE(client.isOpenSynchronous());
+    client.open(&server_endpoint, client_cb);
+
+    // Issue read on the server.  Completion callback should not have run.
+    server_cb.setCalled(false);
+    server_cb.setCode(42); // Answer to Life, the Universe and Everything!
+    server.async_receive_from(buffer(data, sizeof(data)),
+        server_remote_endpoint.getASIOEndpoint(), server_cb);
+    EXPECT_FALSE(server_cb.getCalled());
+
+    // Write something to the server using the client - the callback should not
+    // be called until we call the io_service.run() method.
+    client_cb.setCalled(false);
+    client_cb.setCode(7);  // Arbitrary number
+    client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+    EXPECT_FALSE(client_cb.getCalled());
+
+    // Execute the two callbacks.
+    service.run_one();
+    service.run_one();
+
+    EXPECT_TRUE(client_cb.getCalled());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(OUTBOUND_DATA), client_cb.getLength());
+
+    EXPECT_TRUE(server_cb.getCalled());
+    EXPECT_EQ(0, server_cb.getCode());
+    EXPECT_EQ(sizeof(OUTBOUND_DATA), server_cb.getLength());
+
+    EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], OUTBOUND_DATA));
+
+    // Now return data from the server to the client.  Issue the read on the
+    // client.
+    client_cb.setLength(12345);             // Arbitrary number
+    client_cb.setCalled(false);
+    client_cb.setCode(32);                  // Arbitrary number
+    client.asyncReceive(data, sizeof(data), client_cumulative,
+        &client_remote_endpoint, client_cb);
+
+    // Issue the write on the server side to the source of the data it received.
+    server_cb.setLength(22345);             // Arbitrary number
+    server_cb.setCalled(false);
+    server_cb.setCode(232);                 // Arbitrary number
+    server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)),
+        server_remote_endpoint.getASIOEndpoint(), server_cb);
+
+    // Expect two callbacks to run.
+    service.run_one();
+    service.run_one();
+
+    EXPECT_TRUE(client_cb.getCalled());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA), client_cb.getLength());
+
+    EXPECT_TRUE(server_cb.getCalled());
+    EXPECT_EQ(0, server_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA), server_cb.getLength());
+
+    EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], INBOUND_DATA));
+
+    // Check that the address/port received by the client corresponds to the
+    // address and port the server is listening on.
+    EXPECT_TRUE(server_address == client_remote_endpoint.getAddress());
+    EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort());
+
+    // Check that the receive received a complete buffer's worth of data.
+    EXPECT_TRUE(client.processReceivedData(&data[0], client_cb.getLength(),
+                                           client_cumulative, client_offset,
+                                           client_expected, client_buffer));
+
+    EXPECT_EQ(client_cb.getLength(), client_cumulative);
+    EXPECT_EQ(0, client_offset);
+    EXPECT_EQ(client_cb.getLength(), client_expected);
+    EXPECT_EQ(client_cb.getLength(), client_buffer->getLength());
+
+    // ...and check that the data was copied to the output client buffer.
+    const char* client_char_data = static_cast<const char*>(client_buffer->getData());
+    EXPECT_TRUE(equal(&data[0], &data[client_cb.getLength() - 1], client_char_data));
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.close());
+    EXPECT_NO_THROW(server.close());
+}
diff --git a/src/lib/asiolink/asiolink/tests/unix_domain_socket_unittest.cc b/src/lib/asiolink/asiolink/tests/unix_domain_socket_unittest.cc
new file mode 100644 (file)
index 0000000..a147e3b
--- /dev/null
@@ -0,0 +1,310 @@
+// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <gtest/gtest.h>
+#include <testutils/sandbox.h>
+#include <array>
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+
+namespace  {
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref UnixDomainSocket class.
+class UnixDomainSocketTest : public ::testing::Test {
+public:
+    isc::test::Sandbox sandbox;
+
+    /// @brief Constructor.
+    ///
+    /// Removes unix socket descriptor before the test.
+    UnixDomainSocketTest() :
+        io_service_(),
+        test_socket_(new test::TestServerUnixSocket(io_service_,
+                                                    unixSocketFilePath())),
+        response_(),
+        read_buf_() {
+        test_socket_->startTimer(TEST_TIMEOUT);
+        removeUnixSocketFile();
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Removes unix socket descriptor after the test.
+    virtual ~UnixDomainSocketTest() {
+        removeUnixSocketFile();
+    }
+
+    /// @brief Returns socket file path.
+    ///
+    /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
+    /// socket file is created in the location pointed to by this variable.
+    /// Otherwise, it is created in the build directory.
+    std::string unixSocketFilePath() {
+        std::string socket_path;
+        const char* env = getenv("KEA_SOCKET_TEST_DIR");
+        if (env) {
+            socket_path = std::string(env) + "/test-socket";
+        } else {
+            socket_path = sandbox.join("test-socket");
+        }
+        return (socket_path);
+    }
+
+    /// @brief Removes unix socket descriptor.
+    void removeUnixSocketFile() {
+        static_cast<void>(remove(unixSocketFilePath().c_str()));
+    }
+
+    /// @brief Performs asynchronous receive on unix domain socket.
+    ///
+    /// This function performs partial read from the unix domain socket.
+    /// It uses @c read_buf_ or small size to ensure that the buffer fills
+    /// in before all that have been read. The partial responses are
+    /// appended to the @c response_ class member.
+    ///
+    /// If the response received so far is shorter than the expected
+    /// response, another partial read is scheduled.
+    ///
+    /// @param socket Reference to the unix domain socket.
+    /// @param expected_response Expected response.
+    void doReceive(UnixDomainSocket& socket,
+                   const std::string& expected_response) {
+        socket.asyncReceive(&read_buf_[0], read_buf_.size(),
+        [this, &socket, expected_response]
+            (const boost::system::error_code& ec, size_t length) {
+            if (!ec) {
+                // Append partial response received and see if the
+                // size of the response received so far is still
+                // smaller than expected. If it is, schedule another
+                // partial read.
+                response_.append(&read_buf_[0], length);
+                if (expected_response.size() > response_.size()) {
+                    doReceive(socket, expected_response);
+                }
+
+            } else if (ec.value() != boost::asio::error::operation_aborted) {
+                ADD_FAILURE() << "error occurred while asynchronously receiving"
+                    " data via unix domain socket: " << ec.message();
+            }
+        });
+    }
+
+    /// @brief IO service used by the tests.
+    IOService io_service_;
+
+    /// @brief Server side unix socket used in these tests.
+    test::TestServerUnixSocketPtr test_socket_;
+
+    /// @brief String containing a response received with @c doReceive.
+    std::string response_;
+
+    /// @brief Read buffer used by @c doReceive.
+    std::array<char, 2> read_buf_;
+};
+
+// This test verifies that the client can send data over the unix
+// domain socket and receive a response.
+TEST_F(UnixDomainSocketTest, sendReceive) {
+    // Start the server.
+    test_socket_->bindServerSocket();
+
+    // Setup client side.
+    UnixDomainSocket socket(io_service_);
+    ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+
+    // Send "foo".
+    const std::string outbound_data = "foo";
+    size_t sent_size = 0;
+    ASSERT_NO_THROW(sent_size = socket.write(outbound_data.c_str(),
+                                             outbound_data.size()));
+    // Make sure all data have been sent.
+    ASSERT_EQ(outbound_data.size(), sent_size);
+
+    // Run IO service to generate server's response.
+    while ((test_socket_->getResponseNum() < 1) &&
+           (!test_socket_->isStopped())) {
+        io_service_.run_one();
+    }
+
+    // Receive response from the socket.
+    std::array<char, 1024> read_buf;
+    size_t bytes_read = 0;
+    ASSERT_NO_THROW(bytes_read = socket.receive(&read_buf[0], read_buf.size()));
+    std::string response(&read_buf[0], bytes_read);
+
+    // The server should prepend "received" to the data we had sent.
+    EXPECT_EQ("received foo", response);
+}
+
+// This test verifies that the client can send the data over the unix
+// domain socket and receive a response asynchronously.
+TEST_F(UnixDomainSocketTest, asyncSendReceive) {
+    // Start the server.
+    test_socket_->bindServerSocket();
+
+    // Setup client side.
+    UnixDomainSocket socket(io_service_);
+
+    // We're going to asynchronously connect to the server. The boolean value
+    // below will be modified by the connect handler function (lambda) invoked
+    // when the connection is established or if an error occurs.
+    bool connect_handler_invoked = false;
+    ASSERT_NO_THROW(socket.asyncConnect(unixSocketFilePath(),
+        [&connect_handler_invoked](const boost::system::error_code& ec) {
+            // Indicate that the handler has been called so as the loop below gets
+            // interrupted.
+            connect_handler_invoked = true;
+            // Operation aborted indicates that IO service has been stopped. This
+            // shouldn't happen here.
+            if (ec && (ec.value() != boost::asio::error::operation_aborted)) {
+                ADD_FAILURE() << "error occurred while asynchronously connecting"
+                    " via unix domain socket: " << ec.message();
+            }
+        }
+    ));
+    // Run IO service until connect handler is invoked.
+    while (!connect_handler_invoked && (!test_socket_->isStopped())) {
+        io_service_.run_one();
+    }
+
+    // We are going to asynchronously send the 'foo' over the unix socket.
+    const std::string outbound_data = "foo";
+    size_t sent_size = 0;
+    ASSERT_NO_THROW(socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+        [&sent_size](const boost::system::error_code& ec, size_t length) {
+        // If we have been successful sending the data, record the number of
+        // bytes we have sent.
+        if (!ec) {
+            sent_size = length;
+
+        } else if (ec.value() != boost::asio::error::operation_aborted) {
+            ADD_FAILURE() << "error occurred while asynchronously sending the"
+            " data over unix domain socket: " << ec.message();
+        }
+    }
+    ));
+
+    // Run IO service to generate server's response.
+    while ((test_socket_->getResponseNum() < 1) &&
+           (!test_socket_->isStopped())) {
+        io_service_.run_one();
+    }
+
+    // There is no guarantee that all data have been sent so we only check that
+    // some data have been sent.
+    ASSERT_GT(sent_size, 0);
+
+    std::string expected_response = "received foo";
+    doReceive(socket, expected_response);
+
+    // Run IO service until we get the full response from the server.
+    while ((response_.size() < expected_response.size()) &&
+           !test_socket_->isStopped()) {
+        io_service_.run_one();
+    }
+
+    // Check that the entire response has been received and is correct.
+    EXPECT_EQ(expected_response, response_);
+}
+
+// This test verifies that UnixDomainSocketError exception is thrown
+// on attempt to connect, write or receive when the server socket
+// is not available.
+TEST_F(UnixDomainSocketTest, clientErrors) {
+    UnixDomainSocket socket(io_service_);
+    ASSERT_THROW(socket.connect(unixSocketFilePath()), UnixDomainSocketError);
+    const std::string outbound_data = "foo";
+    ASSERT_THROW(socket.write(outbound_data.c_str(), outbound_data.size()),
+                 UnixDomainSocketError);
+    std::array<char, 1024> read_buf;
+    ASSERT_THROW(socket.receive(&read_buf[0], read_buf.size()),
+                 UnixDomainSocketError);
+}
+
+// This test verifies that an error is returned on attempt to asynchronously
+// connect, write or receive when the server socket is not available.
+TEST_F(UnixDomainSocketTest, asyncClientErrors) {
+    UnixDomainSocket socket(io_service_);
+
+    // Asynchronous operations signal errors through boost::system::error_code
+    // object passed to the handler function. This object casts to boolean.
+    // In case of success the object casts to false. In case of an error it
+    // casts to true. The actual error codes can be retrieved by comparing the
+    // ec objects to predefined error objects. We don't check for the actual
+    // errors here, because it is not certain that the same error codes would
+    // be returned on various operating systems.
+
+    // In the following tests we use C++11 lambdas as callbacks.
+
+    // Connect
+    bool connect_handler_invoked = false;
+    socket.asyncConnect(unixSocketFilePath(),
+        [&connect_handler_invoked](const boost::system::error_code& ec) {
+        connect_handler_invoked = true;
+        EXPECT_TRUE(ec);
+    });
+    while (!connect_handler_invoked && !test_socket_->isStopped()) {
+        io_service_.run_one();
+    }
+
+    // Send
+    const std::string outbound_data = "foo";
+    bool send_handler_invoked = false;
+    socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+        [&send_handler_invoked]
+        (const boost::system::error_code& ec, size_t length) {
+        send_handler_invoked = true;
+        EXPECT_TRUE(ec);
+    });
+    while (!send_handler_invoked && !test_socket_->isStopped()) {
+        io_service_.run_one();
+    }
+
+    // Receive
+    bool receive_handler_invoked = false;
+    std::array<char, 1024> read_buf;
+    socket.asyncReceive(&read_buf[0], read_buf.size(),
+        [&receive_handler_invoked]
+        (const boost::system::error_code& ec, size_t length) {
+        receive_handler_invoked = true;
+        EXPECT_TRUE(ec);
+    });
+    while (!receive_handler_invoked && !test_socket_->isStopped()) {
+        io_service_.run_one();
+    }
+}
+
+// Check that native socket descriptor is returned correctly when
+// the socket is connected.
+TEST_F(UnixDomainSocketTest, getNative) {
+    // Start the server.
+    test_socket_->bindServerSocket();
+
+    // Setup client side.
+    UnixDomainSocket socket(io_service_);
+    ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+    ASSERT_GE(socket.getNative(), 0);
+}
+
+// Check that protocol returned is 0.
+TEST_F(UnixDomainSocketTest, getProtocol) {
+    UnixDomainSocket socket(io_service_);
+    EXPECT_EQ(0, socket.getProtocol());
+}
+
+}
diff --git a/src/lib/asiolink/asiolink/testutils/.gitignore b/src/lib/asiolink/asiolink/testutils/.gitignore
new file mode 100644 (file)
index 0000000..89d503c
--- /dev/null
@@ -0,0 +1,2 @@
+/openssl_sample_client
+/openssl_sample_server
diff --git a/src/lib/asiolink/asiolink/testutils/Makefile.am b/src/lib/asiolink/asiolink/testutils/Makefile.am
new file mode 100644 (file)
index 0000000..ef6f4f9
--- /dev/null
@@ -0,0 +1,74 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+TEST_CA_DIR = $(abs_srcdir)/ca
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST  =
+EXTRA_DIST += ca/00af7a28.0
+EXTRA_DIST += ca/0c7eedb9.0
+EXTRA_DIST += ca/28f5a777.0
+EXTRA_DIST += ca/2eefa08b.0
+EXTRA_DIST += ca/7a5b785e.0
+EXTRA_DIST += ca/ad950210.0
+EXTRA_DIST += ca/doc.txt
+EXTRA_DIST += ca/ext-addr-conf.cnf
+EXTRA_DIST += ca/ext-conf.cnf
+EXTRA_DIST += ca/kea-ca.crt
+EXTRA_DIST += ca/kea-ca.key
+EXTRA_DIST += ca/kea-client.crt
+EXTRA_DIST += ca/kea-client.csr
+EXTRA_DIST += ca/kea-client.key
+EXTRA_DIST += ca/kea-client.p12
+EXTRA_DIST += ca/kea-other.crt
+EXTRA_DIST += ca/kea-other.key
+EXTRA_DIST += ca/kea-self.crt
+EXTRA_DIST += ca/kea-self.key
+EXTRA_DIST += ca/kea-server-addr.crt
+EXTRA_DIST += ca/kea-server-addr.csr
+EXTRA_DIST += ca/kea-server.crt
+EXTRA_DIST += ca/kea-server.csr
+EXTRA_DIST += ca/kea-server.key
+EXTRA_DIST += ca/server-addr-conf.cnf
+EXTRA_DIST += ca/server-conf.cnf
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libasiolinktest.la
+
+libasiolinktest_la_SOURCES  = test_server_unix_socket.cc test_server_unix_socket.h
+libasiolinktest_la_SOURCES += timed_signal.cc timed_signal.h
+libasiolinktest_la_SOURCES += test_tls.cc test_tls.h
+
+libasiolinktest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libasiolinktest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libasiolinktest_la_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+libasiolinktest_la_LIBADD  = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libasiolinktest_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libasiolinktest_la_LIBADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+if HAVE_OPENSSL
+# Boost ASIO SSL sample server and client for C++11.
+# https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/example/cpp11/ssl/
+# openssl_sample_server <port>
+# openssl_sample_server <address> <port>
+
+noinst_PROGRAMS = openssl_sample_client openssl_sample_server
+
+openssl_sample_client_SOURCES = openssl_sample_client.cc
+openssl_sample_client_CPPFLAGS = $(AM_CPPFLAGS)
+openssl_sample_client_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+openssl_sample_client_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+openssl_sample_server_SOURCES = openssl_sample_server.cc
+openssl_sample_server_CPPFLAGS = $(AM_CPPFLAGS)
+openssl_sample_server_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+openssl_sample_server_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+endif
+endif
diff --git a/src/lib/asiolink/asiolink/testutils/ca/00af7a28.0 b/src/lib/asiolink/asiolink/testutils/ca/00af7a28.0
new file mode 100644 (file)
index 0000000..5d7534d
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHDCCAgSgAwIBAgIUe1AyLcAeSfKwCZNZLFTRkWMyOJQwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoM
+CElTQyBJbmMuMREwDwYDVQQDDAhrZWEtc2VsZjAeFw0yMTAzMDIxNDQ3MDdaFw0z
+MTAyMjgxNDQ3MDdaMEgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRl
+MREwDwYDVQQKDAhJU0MgSW5jLjERMA8GA1UEAwwIa2VhLXNlbGYwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+
+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3u
+tPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iP
+j7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZz
+SnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4
+ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBAHWFX55xUt1Opqtji+I2XvBrcexleSAME+irKwExe+tY
+laFEWb1eWyzFHiuOSuNLjcXt1PkUYZ0lYUg17cDj5urpAy+F07uCRQWTXBY8W53H
+IppYl4KjN3w4e5DSyDfiTv99MT8xVKJk+rVu75lQ0kgg68fZR6yK82SLjBQmjV2A
+OcSqHNHtnBU5RcdlZ+E05M1Vo1jHzxHpybkgNxjvmUgBRc9ieLbgSFRZji0nNmhA
+TSZ0DjRce6eyDI+OoEFJL0wXMl0ZOijeuCJr4C45h3TyreU2COC1GaoIeNwmGSIb
+mw0j+XR4rKHcgkUQ7L2DfwOjGFG7IeT+k0QdyeM2NU4=
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/0c7eedb9.0 b/src/lib/asiolink/asiolink/testutils/ca/0c7eedb9.0
new file mode 100644 (file)
index 0000000..3476032
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+DCCAeCgAwIBAgIBFDANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDEy
+N1oXDTMxMDIyODE1MDEyN1owNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRMwEQYDVQQDDAprZWEtc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMNzlab
+xu20h82Y/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oyCiWF
+pJLJ8WOF4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5GJtQ
+iJGapgWRbeyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKCj+IL
+MS46wYjjHTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9dNZB5
+2U9QkyPFHKrnNn400B/xBGNKoyTSYbLQEwIDAQABoxgwFjAUBgNVHREEDTALggls
+b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAKYtC4/KKZnTktvWankLnlVact5K
+L0bJT4qCDg/0gj0pj3rofqyOEoGIjZssQtAG/wmJNF6gNisX/1F23BdEdPAsOJQv
+KuRwr4zL3uj2Mkz585Or/iz633LnD8Ibv8KQsKLnJ/UnJikeH5UgxqcU9kA7ymAE
+pzilP23p3bINvyBMwWZUzT3CsYB7PrcRzx3ScZhbhYaN0f8lq83nspXr8U3FyH5U
+NkrgpuqIE9dFPiaY4CsjNIISpYANcVeWwyPKMk/uty3KbzbmDr7ssm1u1MyJjeVP
+jE/Dhq+WTbDGMfqR3gyXBWq7b1ROA7tk9kAMQg91PLAELSB6lRmzfxzrH/wYk6E/
+0gHgpznpDcA68uW/54eX8phJQQp7Ak7csElXjqXDJ1AWA8VVjRXHerOkq0cUWply
+YsJQCkx3jKdLDFfjtKZWVOjc9rGCnph4BfUej/Lt7z7tTr/Yh+oAR+UyowRzdZM/
+RSsui8vVbvKU+bRlyB5qmNR8cSI5oEA+kAs5DXK2bh5v1SGSxVjwKuwwLeu8eCr3
+HUYQMxKi7Y15+BqjbrOZCEfHE4WORkKze1dh9U/UU9h+LVd+TB7jprZc3ZOvuqYP
+Bb+ponHJJaRvHUKD/jL8kHQ7KX79wXNVkrevGcPe8qE1X/xu4ChK5PuDzq2HQPLs
+USYWw/aARNwslhV6
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/28f5a777.0 b/src/lib/asiolink/asiolink/testutils/ca/28f5a777.0
new file mode 100644 (file)
index 0000000..bdcc9bd
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID4DCCAcigAwIBAgIBFDANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJVUzER
+MA8GA1UECgwISVNDIEluYy4xETAPBgNVBAMMCG90aGVyLWNhMB4XDTIxMDMwMjE0
+NTI0OFoXDTMxMDIyODE0NTI0OFowNDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElT
+QyBJbmMuMRIwEAYDVQQDDAlrZWEtb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xm
+leGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG
++R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k
+2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbB
+CAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcU
+CUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJKoZIhvcNAQELBQAD
+ggIBAMYcxVfoCIn+NPlsoRB2m5vAOuJTuBNigf8Fm0HYougE2W+p50+5USx2BCM8
+M1Cet+8X0dktHbRdDL5aZrRbYnz/OENBD4tKuWMQoP/qzafRiKSkDckxYM6AR4T+
+fzPgLjUde2NE1cDeRlJUmereRXiD2qefEFH55StLl8YnnciAMGTRjwBuLiReF+qE
+noaD8ZIKZ5pBMzoxyOe+39tLJkzhESdZ8gJZRXGm+ickAlP96w8z8TlQiWHG3Caw
+kM7SZSyVYdyfiF32J6A7hwlG3qud83GcunfrjOurWBe1lv51pb/OFGe6wlRD/pcS
+UcKZ07KXXYMXV40O6A5Dv0yJB8ocKhOkfU5MvotAAm2GL2ZXizfmEAz23X9I8830
+B5ggVxgp/bO/exC1sBJjUgF4qVPByE1MdDDWYvPKT8cYg5j8pD9rDn7WGVAmgCk9
+59lEI0HBP33ulBRoxrOQ7kV3pUlV8oP3wG/joz8PwSNAbbtQuUnAmjElONPyTrMN
+2Yqah89SqH9ygzz/UomdrKYuoTu/QEfLLtBcyBLKHrRT8ODvsp2kY9RpveCctsAR
+2gmnYixj7GDdp5c6zTich1+QkVvFtrl3Zu+AWRekFAn92bwwOli14S3LgW2t4iXL
+InVUqNg6l6K9d+FdHogvITQLKKMpfIfsCKPqvacpqryyaith
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/2eefa08b.0 b/src/lib/asiolink/asiolink/testutils/ca/2eefa08b.0
new file mode 100644 (file)
index 0000000..e5762cd
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFMzCCAxugAwIBAgIJAJHdRK24tsELMA0GCSqGSIb3DQEBCwUAMDAxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQKDAdJU0MgSW5jMQ8wDQYDVQQDDAZrZWEtY2EwHhcNMjEw
+MzAyMTQ1OTM3WhcNMzEwMjI4MTQ1OTM3WjAwMQswCQYDVQQGEwJVUzEQMA4GA1UE
+CgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMIICIjANBgkqhkiG9w0BAQEFAAOC
+Ag8AMIICCgKCAgEAvKQ/vJpJnXjZ+/LxZNfPc/QYSChSEQ8qoxh8prBYvPXyDu9O
+RHOaDtd5AWusQLCI3iNYMDaJwrazj0g91jPKcxfvFZbnzFHTAZrDnmJwcTw96Ufr
+P4b7PyXpUSF1/YfDf+/M3C7Wm9IJ/e704XHln/vFCw2dR/N5VOrXXJRcCd5NOES/
+ICXexe62Mv7OjUQS8u6ovejtaaMkvoV2hGSG2LXdgVOCv0U8ybRs03Xl8BVM4lFY
+VO9HjnQ7O9AeGMqebvuyNAyGK9Dv+ERu65M9hB+pW//d+tVv3Dkfou+d5cOXPFXj
+f6vIK+2ClxkBH4A5dhsRJ7vPI41mwXA+H0g+MzxJ8Lg0pzJuLher03RZq3pBHvEc
+/jekP4u6mPrc+5J84jQ0hFwH4XIpxaKJsUiE/r1nFDiWRV27PgXMQgEbjdotxFX4
+IDBNKPtQNrybxiQHsYoZPdKcEfh8XyVT4NHrcbqN1SNf2ZIfDkm09aeDYXDdINAD
++0yZE+3YMeH4oWPpOIfW4OVzEDyfBGHyo2klTZfI5zdd54Kp4dKkzSlmIPC7Oubd
+ZZGoSlZfUlWVcRkqMbUAsZ8H2sdz0l+4k8+VmyiA4EWAiO6SV5xmYSncPQIN5dE2
+PbIxjKosl9JGhajs2gxCqlK+ZA3zgoFHhG1mKGWW7ucMic8Jy4oEq1XsoI0CAwEA
+AaNQME4wHQYDVR0OBBYEFA2rYljxKlzKLA/dsiAmRtO876ifMB8GA1UdIwQYMBaA
+FA2rYljxKlzKLA/dsiAmRtO876ifMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAGqY1cv913Hj1+FDmD5fhzW6D/SeyL/vh3bCJ6ZJmnHFXxHZbK4lufdi
+v3HRJ4iSPnU40ZWVukWE+vKrZOJeBM2ip/cqv8iAiZg2NaQ56AcDgrpOfJcXOJzD
+83kZI8W3dF/zk1flJM3rsi5QlwkCaxBvwA+QInejN+oncA90CphumNqblPQp1Ifm
+dt+b1BIk6QJFYT0oEXnNj+5EmSu+zJ+fR5bJoZX0YTcP6YAHjdZo2qAHTeM6yX8s
+bLnX97IopyPZ/xgG2kdlp2TZZdeysaICOZ16LldE7fp2OD2ifjrAqF9eezwa2ybi
+wNhduRUn0Nmuw/Vy3X5l3gUekc3mS9br8ooHy6N+8pnq04gGWK3AAZLY5v7uvzmD
+BC6eA0IJAvLyeiuTpBlkHZTFxk7ENaStEMFjvPiLrgquHLmJQzsgKoUtR7TGdEJ+
+DOeLAhuXjpaZ/kefSODmm09BP0d/q3iFU3gp1xGu2svUK0/BC6NQNuTIIap+L/I+
+tKq+SpPpp7laJ7M04TqAlI+EMQ4KFRDbmlWAy5uq/ynEpEJ1FFuyg6Zo+fxracTR
+ytP3p/LUEYl1VQbtn9IEcrkzZNEshBglRSJ09u1nLccy3WoX03P0iQiF4oNCEPMg
+PdPlvvf1t3FbcEn5AFOsMRW4U7MBPD/gvy0EVuEJ/boydq8qMzyi
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/7a5b785e.0 b/src/lib/asiolink/asiolink/testutils/ca/7a5b785e.0
new file mode 100644 (file)
index 0000000..29ff5e5
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID3TCCAcWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDAz
+M1oXDTMxMDIyODE1MDAzM1owNDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0lTQyBJ
+bmMxEzARBgNVBAMMCmtlYS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDKbsDkElojvFhVt234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfz
+G1k16qMB5jXF7dRhzq4FiPbZMs5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PE
+l47e4Ae3Cl96dWUfQKQmGIzzHfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyG
+JKXSuqCsaXBRK7Amn8/jxMgdhds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJS
+RKaG9hVRrAe4GPisOjUzerADkPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zq
+qTgTp1MvttSv8JEN6OMhM+bpCbaiWp4DAgMBAAEwDQYJKoZIhvcNAQELBQADggIB
+AENl7hCBjAft1uC/XAO/yBkkDrTk6R21+mdJMghJ9ojFP33QvYYv0pDNeCZ/IJEK
+G2ML8gFzd2YulF1qzBMuFvESRQyqJMnIWJS8FSEIKEyqj5RMTnVWjFM6V2yGhBA5
+XXAL4CVVNz/NqWV/Ebd1XB1OB/y5uz+ZowpWktHtqCKYhDzDtK600GswMOJ5UsZF
+X6JtkvG86nVfuyOIK3NtMXQE/ptAgwa87hVecu7yY/u6PmRwS7YbVBsh9VplnAsQ
+bLARtTGCWHL3otZaDi81dghHkHYmv1NmaubgKnFffKxJGLCtyHF0pqS7C0v7lLOo
+qOhSd3qaFEU1yWpXCFlyglDnadFQs8pdWIPBngwQC2luF1N7Kppz5zzGF5MHNt+E
+LuPlRAwgs8aRRPsySGYKvtCeNYAgjsbec9f0P7lMEGr+AqbZF9qNbbQQkq0dHrMH
+goazCek3XtlMAYYUdmkqQ5a44XRQUu4FuTVqzCH8nqhkeHcWTwO9BHayUebxiBk8
+njDwLtHiQ8u9TjVf/35UOdqFSxra+wZJPKYbH++82KG6rbEotGp3jv0uxasgiHVL
+qrD3dkQAU8zF7cllsUkRE3Gw4tDaZXkZCawiMfLiGK1FVApXkUnKilASDsaH6i3x
+Ui8LM1F9vbtJnzftTa3yi0FR6Gmi5Mc+R42gpE8xCa4y
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/ad950210.0 b/src/lib/asiolink/asiolink/testutils/ca/ad950210.0
new file mode 100644 (file)
index 0000000..2332046
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECjCCAfKgAwIBAgIBHjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDE0
+OVoXDTMxMDIyODE1MDE0OVowOjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRgwFgYDVQQDDA9rZWEtc2VydmVyLWFkZHIwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQC95FGAg3RLyh4FdVogSewbUMq4PMoqougt0FA1+hmsrMMs
+gw3OVpvG7bSHzZj85TTYR0TNqv3w6y3OCUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SD
+SjIKJYWkksnxY4XiPu+ZlRa5JLdW2GUH4wtaU5nnMALI/JY+jhnaegdpiN+JDEEf
+yvkYm1CIkZqmBZFt7Ij6bzftuOKanPpXUAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC9
+8oKP4gsxLjrBiOMdO8IblZIIb9KSR6vYcAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFe
+H101kHnZT1CTI8Ucquc2fjTQH/EEY0qjJNJhstATAgMBAAGjJTAjMCEGA1UdEQQa
+MBiHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggIBAAaf
+GIHwgnSo4zo6cIfpzirVpSqjzOrsAqzSswigZdj7dwx959sgSJzZssDf/TA98iXM
+YQEkBao6jPuo8fTlCF0XGCUGAfq/f6Yn1Nhkk0qUdxLrNsEjKPXjISZPaVZllZBR
++mRMKObn0l86vJ/0zGzPRxH2P5CKg9g3sT8zkg1fGIE/SNr8abZV5Cf3spYQ9PF9
+zQ2TdpgaEGGufKR6VAIJH4CVShMfvBF0qFbzMC7R/CTdSvEBXagWclBT7PqcVGlV
+rK/NB6rt8W8hLQQE6bRunJmkLrmLKLVjFtPZPq5hm3jE8fnGxfzvThiZHTj+oFGw
+KXcbuSvwgYuLKym648V+VDGiDWdpS2dIwQi2JeHTt7Y4P+8dqPfHY7oDy2+67J6o
+ElTXvloGVNCedQtpp9gNrtil5avXrU9HCfD9avYlsn89kqYZ3Ht1GBYPyqeSZDCo
+a+sffazhYPfqFdH0U7wpq6Gf8/JMSAuQmAR2UAwhjoQatqDqEJ3pAFsI3YcQOZqm
+kj3/T0iYkU8YdJkxI2YgVCRRIzTKHkGMVc/iz+C0OJwFeJDuj+dj+EXXtyi3sjhL
+oTQT2y01nW2TPrHqlG3/fQyPx1gKXrij+1uOZJpZcgKE7/YBGByRiUdOyRJ0E6h6
+oimhTLT6mC9wteMiRmj68z5tTC1P0H4nuOU7OqwL
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/doc.txt b/src/lib/asiolink/asiolink/testutils/ca/doc.txt
new file mode 100644 (file)
index 0000000..1ca812f
--- /dev/null
@@ -0,0 +1,123 @@
+Similar to doc/examples/https/nginx/kea-nginx.conf
+ password is keatest
+ Country Name is US
+ Organization Name is ISC Inc.
+ Common Name is the key name.
+
+Some critical details:
+ - recent versions of OpenSSL requires at least 2038 bit RSA
+ - certificate version should be 3 (enforced by Botan for leaves),
+  if openssl creates a version 1 add an extension
+ - RSA allows a simpler format than PKCS#8 for RSA private keys
+  but Botan and other algorithms require PKCS#8
+ - some tools check the alternate subject name of the server so put
+  a correct value in it
+
+Files:
+ - doc.txt this file
+ - ext-addr-conf.cnf extension definition file to add an IP address subject
+  alternative name to the server certificate (IP 127.0.0.1)
+ - ext-conf.cnf extension definition file to add a subject alternative
+  name to the server certificate (DNS localhost)
+ - kea-ca.crt Certification Authority (CA) certificate
+ - kea-ca.key Certification Authority (CA) private key (password keatest)
+ - kea-client.crt client certificate
+ - kea-client.csr client PKCS#10 certificate request
+ - kea-client.key client private key (not encrypted)
+ - kea-client.p12 client PKCS#12 archive with the certificate and the private
+  key (required by curl on macOS or iOS when built with Secure Transport)
+ - kea-other.crt test client certificate (signed by another CA)
+ - kea-other.key test client private key (signed by another CA, not encrypted)
+ - kea-self.crt test client certificate (self-signed)
+ - kea-self.key test client private key (self-signed, not encrypted)
+ - kea-server-addr.crt server certificate using the 127.0.0.1 IP address
+ - kea-server-addr.csr server PKCS#10 certificate request using the
+  127.0.0.1 IP address
+ - kea-server.crt server certificate using the localhost DNS name
+ - kea-server.csr server PKCS#10 certificate request using the localhost
+  DNS name
+ - kea-server.key server private key (all certificates, not encrypted)
+ - server-addr-conf.cnf OpenSSL configuration file to add an IP address
+  subject alternative name (127.0.0.1 and ::1)
+ - server-conf.cnf OpenSSL configuration file to add a DNS subject
+  alternative name (localhost)
+
+Procedure to build CA, server and client files:
+
+1 - create a CA self signed certificate (password is keatest)
+ openssl genrsa -aes128 -out kea-ca.key 4096
+ openssl req -new -x509 -days 3650 -key kea-ca.key -out kea-ca.crt \
+ -extensions v3_ca -config server-conf.cnf
+
+2 - create a key for the client and convert to PKCS#8
+ openssl genrsa -aes128 -out kea-client-aes.key 2048
+ openssl pkcs8 -in kea-client-aes.key -out kea-client.key -nocrypt
+ rm kea-client-aes.key
+
+3 - create a certificate for the client
+ openssl req -new -key kea-client.key -out kea-client.csr
+ openssl x509 -req -days 3650 -in kea-client.csr -CA kea-ca.crt \
+  -CAkey kea-ca.key -set_serial 10 -out kea-client.crt \
+  -extfile /dev/null -sha256
+
+4 - create a PKCS#12 bundle on macOS (password is keatest)
+ openssl pkcs12 -in kea-client.crt -inkey kea-client.key -export \
+  -out kea-client.p12
+
+5 - create a key for the server and convert to PKCS#8 (same than 2)
+ openssl genrsa -aes128 -out kea-server-aes.key 2048
+ openssl pkcs8 -in kea-server-aes.key -out kea-server.key -nocrypt
+ rm kea-server-aes.key
+
+6 - create a certificate with a subject alternate name set to localhost
+ for the server
+ openssl req -new -key kea-server.key -out kea-server.csr \
+  -config server-conf.cnf
+ openssl x509 -req -days 3650 -in kea-server.csr -CA kea-ca.crt \
+  -CAkey kea-ca.key -set_serial 20 -out kea-server.crt \
+  -extfile ext-conf.cnf -sha256
+
+7 - create a certificate with a subject alternate name set to 127.0.0.1
+ and ::1 for the server
+ openssl req -new -key kea-server.key -out kea-server-addr.csr \
+  -config server-addr-conf.cnf
+ openssl x509 -req -days 3650 -in kea-server-addr.csr -CA kea-ca.crt \
+  -CAkey kea-ca.key -set_serial 30 -out kea-server-addr.crt \
+  -extfile ext-addr-conf.cnf -sha256
+
+8 - use c_rehash or openssl rehash to create hashes
+
+Setup the control agent: kea-ctrl-agent.json sample.
+
+Using curl.
+Note the localhost is important: using 127.0.0.1 instead can make the
+subjectAltName check to fail. curl is also picky about http vs https.
+
+to send a command (e.g. list-commands) directly to the control agent
+listening at port 8000:
+
+curl -D - -X POST -H Content-Type:application/json \
+ -d '{ "command": "list-commands" }' http://localhost:8000
+
+With the CA only (so authenticating the server only):
+curl -D - -X POST -H Content-Type:application/json --cacert kea-ca.crt \
+ -d '{ "command": "list-commands" }' https://localhost:8443
+
+With mutual authentication using OpenSSL:
+curl -D - -X POST -H Content-Type:application/json \
+ --cacert kea-ca.crt --cert kea-client.crt --key kea-client.key \
+
+With the mutual authentication on macOS (when the OpenSSL one fails):
+curl -D - -X POST -H Content-Type:application/json \
+ --cacert kea-ca.crt --cert kea-client.p12:keatest --cert-type P12 \
+ -d '{ "command": "list-commands" }' https://localhost:8443
+
+To the control agent:
+echo | kea-shell
+
+With server authentication only:
+echo | kea-shell --ca kea-ca.crt --port 8443 --host localhost
+
+With the mutual authentication:
+echo | kea-shell --ca kea-ca.crt --port 8443 --host localhost \
+ --cert kea-client.crt --key kea-client.key
diff --git a/src/lib/asiolink/asiolink/testutils/ca/ext-addr-conf.cnf b/src/lib/asiolink/asiolink/testutils/ca/ext-addr-conf.cnf
new file mode 100644 (file)
index 0000000..a6b78c1
--- /dev/null
@@ -0,0 +1 @@
+subjectAltName=IP:127.0.0.1,IP:::1
diff --git a/src/lib/asiolink/asiolink/testutils/ca/ext-conf.cnf b/src/lib/asiolink/asiolink/testutils/ca/ext-conf.cnf
new file mode 100644 (file)
index 0000000..aafe5bd
--- /dev/null
@@ -0,0 +1 @@
+subjectAltName=DNS:localhost
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-ca.crt b/src/lib/asiolink/asiolink/testutils/ca/kea-ca.crt
new file mode 100644 (file)
index 0000000..e5762cd
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFMzCCAxugAwIBAgIJAJHdRK24tsELMA0GCSqGSIb3DQEBCwUAMDAxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQKDAdJU0MgSW5jMQ8wDQYDVQQDDAZrZWEtY2EwHhcNMjEw
+MzAyMTQ1OTM3WhcNMzEwMjI4MTQ1OTM3WjAwMQswCQYDVQQGEwJVUzEQMA4GA1UE
+CgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMIICIjANBgkqhkiG9w0BAQEFAAOC
+Ag8AMIICCgKCAgEAvKQ/vJpJnXjZ+/LxZNfPc/QYSChSEQ8qoxh8prBYvPXyDu9O
+RHOaDtd5AWusQLCI3iNYMDaJwrazj0g91jPKcxfvFZbnzFHTAZrDnmJwcTw96Ufr
+P4b7PyXpUSF1/YfDf+/M3C7Wm9IJ/e704XHln/vFCw2dR/N5VOrXXJRcCd5NOES/
+ICXexe62Mv7OjUQS8u6ovejtaaMkvoV2hGSG2LXdgVOCv0U8ybRs03Xl8BVM4lFY
+VO9HjnQ7O9AeGMqebvuyNAyGK9Dv+ERu65M9hB+pW//d+tVv3Dkfou+d5cOXPFXj
+f6vIK+2ClxkBH4A5dhsRJ7vPI41mwXA+H0g+MzxJ8Lg0pzJuLher03RZq3pBHvEc
+/jekP4u6mPrc+5J84jQ0hFwH4XIpxaKJsUiE/r1nFDiWRV27PgXMQgEbjdotxFX4
+IDBNKPtQNrybxiQHsYoZPdKcEfh8XyVT4NHrcbqN1SNf2ZIfDkm09aeDYXDdINAD
++0yZE+3YMeH4oWPpOIfW4OVzEDyfBGHyo2klTZfI5zdd54Kp4dKkzSlmIPC7Oubd
+ZZGoSlZfUlWVcRkqMbUAsZ8H2sdz0l+4k8+VmyiA4EWAiO6SV5xmYSncPQIN5dE2
+PbIxjKosl9JGhajs2gxCqlK+ZA3zgoFHhG1mKGWW7ucMic8Jy4oEq1XsoI0CAwEA
+AaNQME4wHQYDVR0OBBYEFA2rYljxKlzKLA/dsiAmRtO876ifMB8GA1UdIwQYMBaA
+FA2rYljxKlzKLA/dsiAmRtO876ifMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAGqY1cv913Hj1+FDmD5fhzW6D/SeyL/vh3bCJ6ZJmnHFXxHZbK4lufdi
+v3HRJ4iSPnU40ZWVukWE+vKrZOJeBM2ip/cqv8iAiZg2NaQ56AcDgrpOfJcXOJzD
+83kZI8W3dF/zk1flJM3rsi5QlwkCaxBvwA+QInejN+oncA90CphumNqblPQp1Ifm
+dt+b1BIk6QJFYT0oEXnNj+5EmSu+zJ+fR5bJoZX0YTcP6YAHjdZo2qAHTeM6yX8s
+bLnX97IopyPZ/xgG2kdlp2TZZdeysaICOZ16LldE7fp2OD2ifjrAqF9eezwa2ybi
+wNhduRUn0Nmuw/Vy3X5l3gUekc3mS9br8ooHy6N+8pnq04gGWK3AAZLY5v7uvzmD
+BC6eA0IJAvLyeiuTpBlkHZTFxk7ENaStEMFjvPiLrgquHLmJQzsgKoUtR7TGdEJ+
+DOeLAhuXjpaZ/kefSODmm09BP0d/q3iFU3gp1xGu2svUK0/BC6NQNuTIIap+L/I+
+tKq+SpPpp7laJ7M04TqAlI+EMQ4KFRDbmlWAy5uq/ynEpEJ1FFuyg6Zo+fxracTR
+ytP3p/LUEYl1VQbtn9IEcrkzZNEshBglRSJ09u1nLccy3WoX03P0iQiF4oNCEPMg
+PdPlvvf1t3FbcEn5AFOsMRW4U7MBPD/gvy0EVuEJ/boydq8qMzyi
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-ca.key b/src/lib/asiolink/asiolink/testutils/ca/kea-ca.key
new file mode 100644 (file)
index 0000000..4ac82d3
--- /dev/null
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,1E4500430B45CC59A1AFA62E20D0632E
+
+L1O4pVdZnk9nHSyH4fkoEehRNfhE4xbYt28YMtVctjeOQQWCf6m89k/rtOlSb9c6
+82WMHWiACuWNGxcd3RLZl0dWTPZYE4xk6T3TzTk/GwkDbQRf/6hfzGcRnObVRGYq
+kzBq6zXtoqFbq2jAACqCSoRlZgpLOv8hUdUcnto707iT0ebmwbNgPsxCBXjvxOYO
+Pvkihpfd7QY5GD8fn14y/y/im/9sqZgpNfhEVeO//Dpo1Nvo6DasU1gTnEoOkRRK
+/IBl12N4FxdiAjg16SfDw/M3/uka6ftekdr4PwD616qiUsBdKsuslp9aN82k+5RK
+X3iuODmMc/42SUoSskbL5mkuroOZxihwbiKsejcmGOfVygYXuZ9a9tLHLsdKLoWO
+1mmTMU4fzNpwXPor4h0yEDaortX2KwBVPnSWOMCJtwreukgt0GHfePfbd08Ojf6M
+pyZZ7gVv/q573RSgQL6nipU+4Il6T+cK4Iwdui9WSFahiOKgALuhTX0eY7CmlfcR
+hgNqmJhXEuXbEiQONcDA7iEAggdha4W3bm8blCj7QEBpr45fAyDSZxP/dNrIoZWC
+BxbrTq+YqzLyhUOOE7THdR5qpCha5Tsoyv8n7K91v77wZjmL1poyqHbXqvWDIJni
++LAPJDd6/Z0lqXLyTV3U9FcE6cAz6kkl5J1aeWFzfWSPtdiSzMPFkaz1MUPPllHF
+nyoA1R8PAD1yPj2accSIi8nBMYpOUrwMZcS+MbSW4GsbPEOqkluLgLLas/H9eohp
+SdyPsSnNBmWaCAwNHGWRAyRRefeMsrjtlF2AfVMsrCIzUNiSiw0MHsZQV6zlI23i
+/xyYxMn3fDmMxqJCJ8FkEHxVx5SeyzbysYmCfBsquKnfzE8JAyjmRQzdqfXHt5H9
+MEctsLiTQ+WPwWMN/6zHjuJMpJFZTfK/y0RUgTUyf02t0C4Bobx30DOx0SM4B7Rx
+QQ7uwMlarE8Pg7tCDA0kC2aGCSaHo2u0qssmLVGhNKNkBVKkr7SpS4CM7dcIh+Yk
+30Q4UQfCzRbS17RD1LfdUg+SPCeDFoKdh4f4FVoHXrbeEOhPJVeCjPli78nnPuZ0
+kGvndf/v+4DH40Wvt5aZj90mes6q+2Hy4GlgciELEWhMcj2QSiRISNi5UFNYRsSL
+RsEhuksONQVrFnRS3n3WvQrZ8X4OLAfatlFewpR9UVvgfWXLuWLy6etDWa056wDa
+4OW715YaEedSsF8WrfhRXmU/IDJ19oiQzsQiyeiKoFW3OVRyf2ngb8psUOwLbgA6
+kjcrzt77RsYKlP7TYC2hvycqnvvDhKCe6yQmd6vS1lOdBm8VZWzJCGFfoeucx4i2
+DS5ryWhU9d4VoCxFYEEsNhC8GKkrcATikhLnB8riJgt5PrJenYMBd9EsuwAo3Xaa
++95SeiAdka2XIN2dBDOJ4qAJYKhHyZF/fJpJP/1s3zGsdBN3mkY3C1C3/dYR1fan
+7fK9Qx2fcZjeMTkdm91Ito7ui2LQDVjJoTEaZ0LyMh3Gz7hALuDfPeS3Eft3QXMB
+Do3Tki68lvtc9DadlDQfTm84WvS4BVyOhQVQqhS2Ttq+ICGrNekPg1zyMUI2N0bo
+8ulenrCKStFBqgyWq1aczcLNEDth0GWOFjLdgWUwI2pcN3tuouLHXpfKKARxxdis
+Un3Dj5nhg6G2vGhTTTRdxMQeiT0Dr6Q2tD9VUNojVZwJ1c50dgZ6hlhzU5pv+1vU
+krBjlx9szF2ikx2pUp8RHDAziKkv17zXDjvEJpE/pvYWHBfBPoQr5NPaPGYnbFIX
+qaLYtWOAFlL3BI1XSO/32nYee0+WjnKMr4IOvXJfnaa94S+wU6pJEbTGHP+1aGNS
+wsslmcfRDmmeblGd40Bo4ENCc93KxBf3V7g7/JnSUZO39TyfvMnyy1E3JC6fu/A4
+VvnlnFM+6ZjdhkiZ4RJqd2rc2AhA6HhOslJSa0kPRc6UQQqAci+7YHZBc/PELhpD
+LpFbBXbqyi1jNQNodhhJtkD8VkvYHOisqzHFTITZp5epK8mjLkBhIW2VUVZ+dDK+
+3kFrKB+CaEvE1OBAlDYeVxMAvT1rmyjT04mqPRnp0G57+5VQQFYrKfVevDddLIt2
+tQphIcgZYAHTU+2otlPAOXqgPJWRoKNTw6Rtc6dELrAOE/kDFqZ4VKRnXRNFmxj3
+NSC8zapuNmkGQTo8CHzJuRI8sfNHjcDrMELHV1Fe8XSoqdovV2X+Xa/fesCaYfrp
+6506uFGZSR7SrMdT5MoXGri1IEvGXkGI30UDq5QTEzHiyyYgC7kZFn3E/zREbA0y
+/WahS8zICLsEK2ZknSv3q6e9aONokNbYu7PqvQtW5IPGrjdZxuQDtRXEYafiDLKT
+c3h9eE8OKk5Si49TRjsYbuR4+BBw9N0R0RIfs5TIDkkGeCu0M4yFPKQVhCN98OAk
+h0L+ZhQJZfbDE7QNBuvmRBNcpJYe7JTXl2/p6JjoxeyZTgShk81BiOmMCaWavKB+
+gIqy4X39y+J+AiYMiKy/+B5gtNaZaE9hka7RH2tV5nkiTBilZ6v5N1A4V4Q0PRFT
+HZAXgnUwI0HcIRfkqxlF3gXMzhG1+K2wxS9uVn5K0E27xNeswr+ksfLJsyWz+gdT
+/ZFgGyErUY6CLmYzmW+WfQox+qd9pd1TMISNuBWXrdoKkX8iFjj8SWyPcZvqMUkx
+lo8RVzb/6ugSTcbCQGpf+6H8ZuOe9hZwD9tKBh6XZbC5KtBQ8TtSnrmsk9ufIzn8
+ACrJFTVOG4u/g/xn1j3MY4NIaLA77YSCed+TzOXBPmG+LrJM67n1tMtGWEPoOnGi
+6pzJpF5cxsF4i0QoqdYFThqMb6mHtaVPsjjIpdzEXmYyQENLQECERE6lYlz9ZVkS
+NsOR3KMOxXZQ+iWmqCptazz0hVVmEBFisg6K6WuQR3BpXcf8N9UP7xUnStlUUaQ7
+G5nf6BZl3AIxZPay/NoM87n4I4lplPaQwyK/ReMztu78OQFyx9mC1BGOHxVtF6hO
+W+POZqc7ugCXiY8A08vSv5yt8paWDnU+hHXnEo04Hw0ex2KNOOZeL0Eg+idJTZe0
+/0yl0olct0HUgSyhU3wm0uWiHwulreoa3tNL+a4Xt7k5L2e5XcvAh3T2mgxzDq5q
+-----END RSA PRIVATE KEY-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-client.crt b/src/lib/asiolink/asiolink/testutils/ca/kea-client.crt
new file mode 100644 (file)
index 0000000..29ff5e5
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID3TCCAcWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDAz
+M1oXDTMxMDIyODE1MDAzM1owNDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0lTQyBJ
+bmMxEzARBgNVBAMMCmtlYS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDKbsDkElojvFhVt234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfz
+G1k16qMB5jXF7dRhzq4FiPbZMs5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PE
+l47e4Ae3Cl96dWUfQKQmGIzzHfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyG
+JKXSuqCsaXBRK7Amn8/jxMgdhds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJS
+RKaG9hVRrAe4GPisOjUzerADkPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zq
+qTgTp1MvttSv8JEN6OMhM+bpCbaiWp4DAgMBAAEwDQYJKoZIhvcNAQELBQADggIB
+AENl7hCBjAft1uC/XAO/yBkkDrTk6R21+mdJMghJ9ojFP33QvYYv0pDNeCZ/IJEK
+G2ML8gFzd2YulF1qzBMuFvESRQyqJMnIWJS8FSEIKEyqj5RMTnVWjFM6V2yGhBA5
+XXAL4CVVNz/NqWV/Ebd1XB1OB/y5uz+ZowpWktHtqCKYhDzDtK600GswMOJ5UsZF
+X6JtkvG86nVfuyOIK3NtMXQE/ptAgwa87hVecu7yY/u6PmRwS7YbVBsh9VplnAsQ
+bLARtTGCWHL3otZaDi81dghHkHYmv1NmaubgKnFffKxJGLCtyHF0pqS7C0v7lLOo
+qOhSd3qaFEU1yWpXCFlyglDnadFQs8pdWIPBngwQC2luF1N7Kppz5zzGF5MHNt+E
+LuPlRAwgs8aRRPsySGYKvtCeNYAgjsbec9f0P7lMEGr+AqbZF9qNbbQQkq0dHrMH
+goazCek3XtlMAYYUdmkqQ5a44XRQUu4FuTVqzCH8nqhkeHcWTwO9BHayUebxiBk8
+njDwLtHiQ8u9TjVf/35UOdqFSxra+wZJPKYbH++82KG6rbEotGp3jv0uxasgiHVL
+qrD3dkQAU8zF7cllsUkRE3Gw4tDaZXkZCawiMfLiGK1FVApXkUnKilASDsaH6i3x
+Ui8LM1F9vbtJnzftTa3yi0FR6Gmi5Mc+R42gpE8xCa4y
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-client.csr b/src/lib/asiolink/asiolink/testutils/ca/kea-client.csr
new file mode 100644 (file)
index 0000000..e607360
--- /dev/null
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICeTCCAWECAQAwNDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0lTQyBJbmMxEzAR
+BgNVBAMMCmtlYS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDKbsDkElojvFhVt234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfzG1k16qMB
+5jXF7dRhzq4FiPbZMs5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PEl47e4Ae3
+Cl96dWUfQKQmGIzzHfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyGJKXSuqCs
+aXBRK7Amn8/jxMgdhds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJSRKaG9hVR
+rAe4GPisOjUzerADkPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zqqTgTp1Mv
+ttSv8JEN6OMhM+bpCbaiWp4DAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAs7Ed
+zY2f2BN33Jd2/XAe3Vl/Jm7JgLN7GnvwzdoM/KewsTsSo0wrgqBU2r36F+W2+/T6
+rN8C0SseFfaURd3CQc66UcGzp4+FKxWIS9loO4P43t6MjBUQ/RiW3IQUAbkMIL52
+CG1HiyyOp7GNtXb861CCu25t82oXeW7WWvWJxaKeAk/hkr7lrVxCcU7XkVY6sDU0
+t4fP3W31p5ZkLUK4qELiZ3iJZLnf/5xaXgJpVlS3E4DUe8tyl3TjayYxroyRj+TT
+D0LWwE65QGygJM2cZrraIvue5kVan4C8XZvO/VvZoakWH/ZkGN8Pis33r8oEfrQL
+SyGt7oTSRYob5MTWmA==
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-client.key b/src/lib/asiolink/asiolink/testutils/ca/kea-client.key
new file mode 100644 (file)
index 0000000..a8768b3
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKbsDkElojvFhV
+t234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfzG1k16qMB5jXF7dRhzq4FiPbZ
+Ms5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PEl47e4Ae3Cl96dWUfQKQmGIzz
+HfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyGJKXSuqCsaXBRK7Amn8/jxMgd
+hds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJSRKaG9hVRrAe4GPisOjUzerAD
+kPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zqqTgTp1MvttSv8JEN6OMhM+bp
+CbaiWp4DAgMBAAECggEBAKJP05ILtQLaTenMvfwj8lH1LxPuja1y94ZwRedOdqUy
+26O5RS0RICwpTYqRrEolkBA39gbGdXoyq9rTheuc2Hmu9sOF/gH195pF08IOGPD6
+ClQRPpzX+8xxyTijYQw+4PeLkZ1Rc0yoeruk1WSARJWoR7pGY/hqaN5Lue4R0jqF
+KoVBrsR6ULsLscgjHP+OmIdFiTamnmjfDHcQTLKFH4inf5T0Q6UZnKq9qxoZ/o9i
+AELalOOK1U/v4CvGiQFPHX9V82ZWA5P4D7aSSqDgR7eyCYEaPLRpRq2v9A5IQQuc
+nD1wdkshqmSUOj5xL/KlJaIYBIZS8LKOYGZUOD8MMMECgYEA8VcBHU3CZU2zmZTk
+sD2SCzw/P93gsA5rcKtQQWMFIyt8CQHcl7zTHReaG3ZAhFU9DY0WSUzTtqpOhYy3
+E9KgugaWi+BMQC1zItadGsA2WF3RXAbSbrZiVGQitFxLmbXJL4QoRMIRaewagKt1
+hsF2kolWg13inEAddiCOXMos8fkCgYEA1rq3PRGI/oYdTvI41HTJcRiYuGeJacT0
+D7lQQqMS3hk6jisPGhwFlIQ0/Ax/vnvpW/eRO4vhpKQE8DkPEGyM6r8JPejcWeDZ
+pEs0vfuCVitUsCw0g+z9hVa4slnZ9clkxsY9tHZJWFdTxi9Bpjezo6XN9DTtl3hP
+lNSegykMDtsCgYAnnrfxHp3mUZ5FfVsZz9HVBFwB2SQU4xkiUw2G3oGuZ2oidGrJ
+gldKNGC5V216DCBMxDe/atxq5YSkihhYKcD3KTO33OfHtW5sbr018g457ZT8PaZ4
+RHraDeJgp7JFlsFjipetygpf0EH9k6hkqggUQHWydUxJiIENroSQmSRNyQKBgF5T
+dS0BZ/GPDo7gfrBtgRQKXwQaj1WELEY//I7ZPe+Mm5laNu8cQiNElFXoU7Fkk1VQ
+Al9rCjsdxgGUvxZS6PAx7ShiA3IEAPdYBhoywsWBkVk2gfc2AwQw3T+TktiSmI9t
+BCwjDgMdkXJszeTrcSFBM6DEI163fhX99IffXymjAoGAf9B0v+NIxRXgMac+1rLN
+MSzOOA2yq3tI+Ra8q0D4r4ShfauWll/rlEgx6L0FrAdTYfit8I3dBOqKYe3b/E0r
+IKjAX5rh9Es/PxsOo6qJYw9l4P4+xxZKsqqvdMNQ1+21ZC90TnHWdy3bRPh1D0Vj
+XDwyByyi4FuaEWhZgNA5+44=
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-client.p12 b/src/lib/asiolink/asiolink/testutils/ca/kea-client.p12
new file mode 100644 (file)
index 0000000..baf4420
Binary files /dev/null and b/src/lib/asiolink/asiolink/testutils/ca/kea-client.p12 differ
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-other.crt b/src/lib/asiolink/asiolink/testutils/ca/kea-other.crt
new file mode 100644 (file)
index 0000000..bdcc9bd
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID4DCCAcigAwIBAgIBFDANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJVUzER
+MA8GA1UECgwISVNDIEluYy4xETAPBgNVBAMMCG90aGVyLWNhMB4XDTIxMDMwMjE0
+NTI0OFoXDTMxMDIyODE0NTI0OFowNDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElT
+QyBJbmMuMRIwEAYDVQQDDAlrZWEtb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xm
+leGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG
++R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k
+2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbB
+CAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcU
+CUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJKoZIhvcNAQELBQAD
+ggIBAMYcxVfoCIn+NPlsoRB2m5vAOuJTuBNigf8Fm0HYougE2W+p50+5USx2BCM8
+M1Cet+8X0dktHbRdDL5aZrRbYnz/OENBD4tKuWMQoP/qzafRiKSkDckxYM6AR4T+
+fzPgLjUde2NE1cDeRlJUmereRXiD2qefEFH55StLl8YnnciAMGTRjwBuLiReF+qE
+noaD8ZIKZ5pBMzoxyOe+39tLJkzhESdZ8gJZRXGm+ickAlP96w8z8TlQiWHG3Caw
+kM7SZSyVYdyfiF32J6A7hwlG3qud83GcunfrjOurWBe1lv51pb/OFGe6wlRD/pcS
+UcKZ07KXXYMXV40O6A5Dv0yJB8ocKhOkfU5MvotAAm2GL2ZXizfmEAz23X9I8830
+B5ggVxgp/bO/exC1sBJjUgF4qVPByE1MdDDWYvPKT8cYg5j8pD9rDn7WGVAmgCk9
+59lEI0HBP33ulBRoxrOQ7kV3pUlV8oP3wG/joz8PwSNAbbtQuUnAmjElONPyTrMN
+2Yqah89SqH9ygzz/UomdrKYuoTu/QEfLLtBcyBLKHrRT8ODvsp2kY9RpveCctsAR
+2gmnYixj7GDdp5c6zTich1+QkVvFtrl3Zu+AWRekFAn92bwwOli14S3LgW2t4iXL
+InVUqNg6l6K9d+FdHogvITQLKKMpfIfsCKPqvacpqryyaith
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-other.key b/src/lib/asiolink/asiolink/testutils/ca/kea-other.key
new file mode 100644 (file)
index 0000000..212dbe6
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAoEENWQ6tl6aa
+RMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2
+aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXd
+cD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyv
+qVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11
+QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ
+/3udIh1vAgMBAAECggEAYUpPsPszM7Bt4GswDvUu/loTXcsq1vglirGAsmr+aEf7
+bqF473NyRONFD5bpgWUSFg2aDxMtn884VN3ir0rPIHjIxhnnhY2FF1TnH/B3OUxv
+bWfTYQK/ppv7THHkctqucFCh3POhcrOSqOaB1SB1EFmntJbDpG0EhPYXbC1nALy/
+1NstWOBhqcr6xTU2VUnzqSDxa49pJPuSZpEj4G0VdqmrlI/8wjl0mSLdY2VqXMlQ
+hSJG2aqRKiKe0kgQvUnHVEaic9YNNC2arxX/zp5c4USioxekrmCkoAKXpqbFswGS
+zcFWJQer+nvCkIX9zhr+3bFn4/dkDK4GD49Atw0kKQKBgQDew/iEDKgQ2obQFVW2
+2WheTSuE3sDsBdnod3YRZgIn7Vf7QyaNqzoOec8QgE3oMu2EdKKyLDyN8/3IBCVq
+zUIeWJLN3CCt6DNK348hEJc3Fc2hibv/Ea4TQ4rZyNxiYJfV86ndYvKMQSMmHz9l
+DKzrjB/x1LaBBuO4qLUzgK7vwwKBgQDdXSwJo9MzCeDvqQtUxNbMXKJOfEqFusaW
+/NidbS9MnphImcsQobMfcN/h74r2aH+xGHBk5pPecfU/qK48dzEWncrWppRTfJ9V
+eU5VYlnkvI/mHDKJqoEZEycRqlUCRvlu8DGqEOHelW7ZCjcGD4DckRDmZCaeD78O
+xKkiyu2M5QKBgBi2GI1dcg9cjnPqyfVcrK05VkiJBVGpXIDjL5/Cdx7Cv23KBy7T
+/b65WHT2Jq5JZ/u3jIzDR3xfwpk7jIMKffkrzi0z7BQenAIERrZeRsf/jS4MP2SO
+K4dLiM2b8IahPHapbwB2B33zg9iowrmM7Gm8w5ZqCEzL3NsRK/ion79NAoGAYEtw
+pbzjWfd5Jyg1Kqn5+qptXJEK5gOq8fGJ1WmywrTW7/Ye9NwyjIHQknteyvQIYCSO
+eAYp2wFdu1SIfvsmmn0HyLpsGalDsq3zWodPLYatXl9zyJkoUZ0YSMH8+uGfDhhk
+smNnrij5MGcWKofB+bENVfvJJMcayLTaEq2OCtUCgYEAoXRn4p1P3kdtLXYnTAsn
+gsDIVEzFAnb4DXuvK3ozA3mUgIwGHQiiwcN+mCHml3hkMnmkDSx4bakbAUuQ+EIz
+kS66aKRTvXdIZujGSNLyjN9SQkaJfKDYLW32WmxTowoLh5MyDpaVOMFegVzex5c/
+zeY0qPLtGf+qogMcoftZeR4=
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-self.crt b/src/lib/asiolink/asiolink/testutils/ca/kea-self.crt
new file mode 100644 (file)
index 0000000..5d7534d
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHDCCAgSgAwIBAgIUe1AyLcAeSfKwCZNZLFTRkWMyOJQwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoM
+CElTQyBJbmMuMREwDwYDVQQDDAhrZWEtc2VsZjAeFw0yMTAzMDIxNDQ3MDdaFw0z
+MTAyMjgxNDQ3MDdaMEgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRl
+MREwDwYDVQQKDAhJU0MgSW5jLjERMA8GA1UEAwwIa2VhLXNlbGYwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+
+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3u
+tPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iP
+j7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZz
+SnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4
+ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBAHWFX55xUt1Opqtji+I2XvBrcexleSAME+irKwExe+tY
+laFEWb1eWyzFHiuOSuNLjcXt1PkUYZ0lYUg17cDj5urpAy+F07uCRQWTXBY8W53H
+IppYl4KjN3w4e5DSyDfiTv99MT8xVKJk+rVu75lQ0kgg68fZR6yK82SLjBQmjV2A
+OcSqHNHtnBU5RcdlZ+E05M1Vo1jHzxHpybkgNxjvmUgBRc9ieLbgSFRZji0nNmhA
+TSZ0DjRce6eyDI+OoEFJL0wXMl0ZOijeuCJr4C45h3TyreU2COC1GaoIeNwmGSIb
+mw0j+XR4rKHcgkUQ7L2DfwOjGFG7IeT+k0QdyeM2NU4=
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-self.key b/src/lib/asiolink/asiolink/testutils/ca/kea-self.key
new file mode 100644 (file)
index 0000000..212dbe6
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAoEENWQ6tl6aa
+RMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2
+aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXd
+cD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyv
+qVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11
+QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ
+/3udIh1vAgMBAAECggEAYUpPsPszM7Bt4GswDvUu/loTXcsq1vglirGAsmr+aEf7
+bqF473NyRONFD5bpgWUSFg2aDxMtn884VN3ir0rPIHjIxhnnhY2FF1TnH/B3OUxv
+bWfTYQK/ppv7THHkctqucFCh3POhcrOSqOaB1SB1EFmntJbDpG0EhPYXbC1nALy/
+1NstWOBhqcr6xTU2VUnzqSDxa49pJPuSZpEj4G0VdqmrlI/8wjl0mSLdY2VqXMlQ
+hSJG2aqRKiKe0kgQvUnHVEaic9YNNC2arxX/zp5c4USioxekrmCkoAKXpqbFswGS
+zcFWJQer+nvCkIX9zhr+3bFn4/dkDK4GD49Atw0kKQKBgQDew/iEDKgQ2obQFVW2
+2WheTSuE3sDsBdnod3YRZgIn7Vf7QyaNqzoOec8QgE3oMu2EdKKyLDyN8/3IBCVq
+zUIeWJLN3CCt6DNK348hEJc3Fc2hibv/Ea4TQ4rZyNxiYJfV86ndYvKMQSMmHz9l
+DKzrjB/x1LaBBuO4qLUzgK7vwwKBgQDdXSwJo9MzCeDvqQtUxNbMXKJOfEqFusaW
+/NidbS9MnphImcsQobMfcN/h74r2aH+xGHBk5pPecfU/qK48dzEWncrWppRTfJ9V
+eU5VYlnkvI/mHDKJqoEZEycRqlUCRvlu8DGqEOHelW7ZCjcGD4DckRDmZCaeD78O
+xKkiyu2M5QKBgBi2GI1dcg9cjnPqyfVcrK05VkiJBVGpXIDjL5/Cdx7Cv23KBy7T
+/b65WHT2Jq5JZ/u3jIzDR3xfwpk7jIMKffkrzi0z7BQenAIERrZeRsf/jS4MP2SO
+K4dLiM2b8IahPHapbwB2B33zg9iowrmM7Gm8w5ZqCEzL3NsRK/ion79NAoGAYEtw
+pbzjWfd5Jyg1Kqn5+qptXJEK5gOq8fGJ1WmywrTW7/Ye9NwyjIHQknteyvQIYCSO
+eAYp2wFdu1SIfvsmmn0HyLpsGalDsq3zWodPLYatXl9zyJkoUZ0YSMH8+uGfDhhk
+smNnrij5MGcWKofB+bENVfvJJMcayLTaEq2OCtUCgYEAoXRn4p1P3kdtLXYnTAsn
+gsDIVEzFAnb4DXuvK3ozA3mUgIwGHQiiwcN+mCHml3hkMnmkDSx4bakbAUuQ+EIz
+kS66aKRTvXdIZujGSNLyjN9SQkaJfKDYLW32WmxTowoLh5MyDpaVOMFegVzex5c/
+zeY0qPLtGf+qogMcoftZeR4=
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-server-addr.crt b/src/lib/asiolink/asiolink/testutils/ca/kea-server-addr.crt
new file mode 100644 (file)
index 0000000..2332046
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECjCCAfKgAwIBAgIBHjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDE0
+OVoXDTMxMDIyODE1MDE0OVowOjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRgwFgYDVQQDDA9rZWEtc2VydmVyLWFkZHIwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQC95FGAg3RLyh4FdVogSewbUMq4PMoqougt0FA1+hmsrMMs
+gw3OVpvG7bSHzZj85TTYR0TNqv3w6y3OCUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SD
+SjIKJYWkksnxY4XiPu+ZlRa5JLdW2GUH4wtaU5nnMALI/JY+jhnaegdpiN+JDEEf
+yvkYm1CIkZqmBZFt7Ij6bzftuOKanPpXUAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC9
+8oKP4gsxLjrBiOMdO8IblZIIb9KSR6vYcAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFe
+H101kHnZT1CTI8Ucquc2fjTQH/EEY0qjJNJhstATAgMBAAGjJTAjMCEGA1UdEQQa
+MBiHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggIBAAaf
+GIHwgnSo4zo6cIfpzirVpSqjzOrsAqzSswigZdj7dwx959sgSJzZssDf/TA98iXM
+YQEkBao6jPuo8fTlCF0XGCUGAfq/f6Yn1Nhkk0qUdxLrNsEjKPXjISZPaVZllZBR
++mRMKObn0l86vJ/0zGzPRxH2P5CKg9g3sT8zkg1fGIE/SNr8abZV5Cf3spYQ9PF9
+zQ2TdpgaEGGufKR6VAIJH4CVShMfvBF0qFbzMC7R/CTdSvEBXagWclBT7PqcVGlV
+rK/NB6rt8W8hLQQE6bRunJmkLrmLKLVjFtPZPq5hm3jE8fnGxfzvThiZHTj+oFGw
+KXcbuSvwgYuLKym648V+VDGiDWdpS2dIwQi2JeHTt7Y4P+8dqPfHY7oDy2+67J6o
+ElTXvloGVNCedQtpp9gNrtil5avXrU9HCfD9avYlsn89kqYZ3Ht1GBYPyqeSZDCo
+a+sffazhYPfqFdH0U7wpq6Gf8/JMSAuQmAR2UAwhjoQatqDqEJ3pAFsI3YcQOZqm
+kj3/T0iYkU8YdJkxI2YgVCRRIzTKHkGMVc/iz+C0OJwFeJDuj+dj+EXXtyi3sjhL
+oTQT2y01nW2TPrHqlG3/fQyPx1gKXrij+1uOZJpZcgKE7/YBGByRiUdOyRJ0E6h6
+oimhTLT6mC9wteMiRmj68z5tTC1P0H4nuOU7OqwL
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-server-addr.csr b/src/lib/asiolink/asiolink/testutils/ca/kea-server-addr.csr
new file mode 100644 (file)
index 0000000..d6ba063
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICyzCCAbMCAQAwOjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRgw
+FgYDVQQDDA9rZWEtc2VydmVyLWFkZHIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC95FGAg3RLyh4FdVogSewbUMq4PMoqougt0FA1+hmsrMMsgw3OVpvG
+7bSHzZj85TTYR0TNqv3w6y3OCUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SDSjIKJYWk
+ksnxY4XiPu+ZlRa5JLdW2GUH4wtaU5nnMALI/JY+jhnaegdpiN+JDEEfyvkYm1CI
+kZqmBZFt7Ij6bzftuOKanPpXUAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC98oKP4gsx
+LjrBiOMdO8IblZIIb9KSR6vYcAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFeH101kHnZ
+T1CTI8Ucquc2fjTQH/EEY0qjJNJhstATAgMBAAGgTDBKBgkqhkiG9w0BCQ4xPTA7
+MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMCEGA1UdEQQaMBiHBH8AAAGHEAAAAAAA
+AAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBADlAkM7Vt3acIbgx9uz/nzEU
+biTUETzQnCU/mJZU+F8nuZtIlH9TAej4oT0J1uBuneGdkgGSm3lONUNxYJ7Uz8dm
+wyudv4cpvtacAzPqZNb0aapX3qD9/lUbXfReoOUmt+asdmF2ncmn3l465ercxtUg
+zhbU5uQUEk7C7f4OZQ3b08yG+tblFhpO7Xm4JD6nJk9iQ6gB4WBUDSr7mdm7PMmV
+T8xesD7lDZVjSdXql9p/6YxJJR3360jycLXeTQbom6gfvsfQcs91yfGHRel2yoDx
+ZBcmjfkYK7mwagpB/QCsZDuC4cxZyFM7lV/ukIysviW7WzrtT9mvfTEcTqmPsPU=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-server.crt b/src/lib/asiolink/asiolink/testutils/ca/kea-server.crt
new file mode 100644 (file)
index 0000000..3476032
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+DCCAeCgAwIBAgIBFDANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDEy
+N1oXDTMxMDIyODE1MDEyN1owNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRMwEQYDVQQDDAprZWEtc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMNzlab
+xu20h82Y/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oyCiWF
+pJLJ8WOF4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5GJtQ
+iJGapgWRbeyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKCj+IL
+MS46wYjjHTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9dNZB5
+2U9QkyPFHKrnNn400B/xBGNKoyTSYbLQEwIDAQABoxgwFjAUBgNVHREEDTALggls
+b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAKYtC4/KKZnTktvWankLnlVact5K
+L0bJT4qCDg/0gj0pj3rofqyOEoGIjZssQtAG/wmJNF6gNisX/1F23BdEdPAsOJQv
+KuRwr4zL3uj2Mkz585Or/iz633LnD8Ibv8KQsKLnJ/UnJikeH5UgxqcU9kA7ymAE
+pzilP23p3bINvyBMwWZUzT3CsYB7PrcRzx3ScZhbhYaN0f8lq83nspXr8U3FyH5U
+NkrgpuqIE9dFPiaY4CsjNIISpYANcVeWwyPKMk/uty3KbzbmDr7ssm1u1MyJjeVP
+jE/Dhq+WTbDGMfqR3gyXBWq7b1ROA7tk9kAMQg91PLAELSB6lRmzfxzrH/wYk6E/
+0gHgpznpDcA68uW/54eX8phJQQp7Ak7csElXjqXDJ1AWA8VVjRXHerOkq0cUWply
+YsJQCkx3jKdLDFfjtKZWVOjc9rGCnph4BfUej/Lt7z7tTr/Yh+oAR+UyowRzdZM/
+RSsui8vVbvKU+bRlyB5qmNR8cSI5oEA+kAs5DXK2bh5v1SGSxVjwKuwwLeu8eCr3
+HUYQMxKi7Y15+BqjbrOZCEfHE4WORkKze1dh9U/UU9h+LVd+TB7jprZc3ZOvuqYP
+Bb+ponHJJaRvHUKD/jL8kHQ7KX79wXNVkrevGcPe8qE1X/xu4ChK5PuDzq2HQPLs
+USYWw/aARNwslhV6
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-server.csr b/src/lib/asiolink/asiolink/testutils/ca/kea-server.csr
new file mode 100644 (file)
index 0000000..458b369
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICuTCCAaECAQAwNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRMw
+EQYDVQQDDAprZWEtc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMNzlabxu20h82Y
+/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oyCiWFpJLJ8WOF
+4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5GJtQiJGapgWR
+beyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKCj+ILMS46wYjj
+HTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9dNZB52U9QkyPF
+HKrnNn400B/xBGNKoyTSYbLQEwIDAQABoD8wPQYJKoZIhvcNAQkOMTAwLjAJBgNV
+HRMEAjAAMAsGA1UdDwQEAwIF4DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZI
+hvcNAQELBQADggEBAECqICoEZb0XeGwoBedtG2Exb4RUeoTAfL24q5a8cOtv0+Mw
+i7y9LNihtRqP2kzhoZ7IhzSUZGVuh4BIUywpJHuWfM9b+fe+hxSGdqCeULKS3InK
+4RWRh9jr12L7hEKfAG7VtL03/+Lm5DHLr47X6RkeZ5GwP29qqLwJcrK9qeFi26Bs
+TrEafPInhF7PgyFjH2YVZVotNaOFMRvwEQwAMtuF7SAqRHr+8VHXP3yi9UjHvxRs
+BpbVD6fEWNkLLJhoSqERgjWnsFlU3O+kj9R+iKA+6arxr4d+HS+dyYitFtVJaR6C
+0+De9msTbJmn+2mu4zQ09Sdf0pN5lb/I3pgcbLU=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/kea-server.key b/src/lib/asiolink/asiolink/testutils/ca/kea-server.key
new file mode 100644 (file)
index 0000000..7709e16
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC95FGAg3RLyh4F
+dVogSewbUMq4PMoqougt0FA1+hmsrMMsgw3OVpvG7bSHzZj85TTYR0TNqv3w6y3O
+CUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SDSjIKJYWkksnxY4XiPu+ZlRa5JLdW2GUH
+4wtaU5nnMALI/JY+jhnaegdpiN+JDEEfyvkYm1CIkZqmBZFt7Ij6bzftuOKanPpX
+UAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC98oKP4gsxLjrBiOMdO8IblZIIb9KSR6vY
+cAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFeH101kHnZT1CTI8Ucquc2fjTQH/EEY0qj
+JNJhstATAgMBAAECggEAdhnidsNLOTfjpBFwlFRlfDerXRqxwgK/1H6S5H9AKJzq
+Zmy70XEcQYTlmvDMDb2gOENbD28hsQ0T1+j+DtV3A/u0b/9etdBtAEozCqUriE9x
+nZYvuQ/NJqYE4xS62BO8gRCwqUWkoWbErzsOfIcyWQ8LLGWsLAvFGJR8t65hGKJq
+7/eYAdSd4mJhMv6mpBWR7OdKRarVxin0DucbLj7eXHPKPeu7//9LbxXhRKGHnc+A
+8PmCFYo+oslhtLplzVPxzSa2D9xvBkFTK5dbwCRP+J9i4kQGfehBaW4jZpzBXtjL
+6idGAU6x9nBSJfWhDuR42HsPn7lcnoKlnErG0inDQQKBgQDzBbo7TsBB8DR003Ix
+seKN9Vpul4bUUp+pl4iECdtgvBTUMmKKkeV/I0OjvAzQAzbum6iEwCOjXpl9pxSx
+6u4iJRK+/xtxZAT2Ddc3tEaql8kcc+VEjZVZPSIcSSOQw6Sri+K59ZzEbP+fDgFk
+REGdiYA4A2ZvBdftmdVsKeuMRQKBgQDICEM881RNAZNwwT3yVU+iEe+16S5qoDjX
+mi0K0cDffQjiYbwjdcZhtjKZdieZsjPLQof5mMxl1NeJaao9giHQ/tk55cpc17xl
+N50LB9f7XAamOTTUoIjOl2hLxflZ43bf/W2GgX4dMp2FEFC17rxYUPfoO1vSvUfB
+74goLyHsdwKBgHSLSZ1Jje/RRwbDpF7qpPBZOo4Qwsst+H23OvO/WmKQsBh3NUSo
+5PtMqRJri2VyNTTGl1FaZ3zgUBGvP8B3Hs5nIw9PfhSp16s8RfrjzIPhGMQ5XDi9
+AWNzatlPxeuVt3HBOvDdNdoJP6lCaS5xgVoQZ9n033ncvommnXAqxlhVAoGATBqI
+qlnRivK8i7uZu+clQv4b+1PaKwsGVVD9Lg6bmOvTQ333vG4EqgxNuAEyE9Guzvhj
+D11I9r1Bu7AN6xTllMRBFTwN/8C8lq3P+/BiBen/RaKiLPte0WrdbWbG9aILCjE7
+SF9gAe/N6mBItM89rUQw7ZQX3VfSQ0DExrUX7QUCgYAAhwKg3c9rMVZKw0w7gR9l
+/hVSCdOD0OHeYwfwzeEQbJshJJUrPk0gsEI4pEo0s8u6PhPBNy6t6U2Mw5S+R3/7
+JCC7UH0iY24d1K8mNL1PNYKBnbpDXrCgzO/Ip6TLnXiyy3/Uu1a7CKK2YZLSukp/
+e8iWSpxQT1Zwt3cfL8/EqA==
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/asiolink/testutils/ca/server-addr-conf.cnf b/src/lib/asiolink/asiolink/testutils/ca/server-addr-conf.cnf
new file mode 100644 (file)
index 0000000..12a34f4
--- /dev/null
@@ -0,0 +1,355 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME                   = .
+RANDFILE               = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file              = $ENV::HOME/.oid
+oid_section            = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions           = 
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca     = CA_default            # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir            = ./demoCA              # Where everything is kept
+certs          = $dir/certs            # Where the issued certs are kept
+crl_dir                = $dir/crl              # Where the issued crl are kept
+database       = $dir/index.txt        # database index file.
+#unique_subject        = no                    # Set to 'no' to allow creation of
+                                       # several ctificates with same subject.
+new_certs_dir  = $dir/newcerts         # default place for new certs.
+
+certificate    = $dir/cacert.pem       # The CA certificate
+serial         = $dir/serial           # The current serial number
+crlnumber      = $dir/crlnumber        # the current crl number
+                                       # must be commented out to leave a V1 CRL
+crl            = $dir/crl.pem          # The current CRL
+private_key    = $dir/private/cakey.pem# The private key
+RANDFILE       = $dir/private/.rand    # private random number file
+
+x509_extensions        = usr_cert              # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt       = ca_default            # Subject Name options
+cert_opt       = ca_default            # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions       = crl_ext
+
+default_days   = 365                   # how long to certify for
+default_crl_days= 30                   # how long before next CRL
+default_md     = sha256                # use SHA-256 by default
+preserve       = no                    # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy         = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName            = match
+stateOrProvinceName    = match
+organizationName       = match
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+####################################################################
+[ req ]
+default_bits           = 1024
+default_keyfile        = privkey.pem
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+x509_extensions        = v3_ca # The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options. 
+# default: PrintableString, T61String, BMPString.
+# pkix  : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName                    = Country Name (2 letter code)
+countryName_default            = AU
+countryName_min                        = 2
+countryName_max                        = 2
+
+stateOrProvinceName            = State or Province Name (full name)
+#stateOrProvinceName_default   = Some-State
+
+localityName                   = Locality Name (eg, city)
+
+0.organizationName             = Organization Name (eg, company)
+0.organizationName_default     = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName            = Second Organization Name (eg, company)
+#1.organizationName_default    = World Wide Web Pty Ltd
+
+organizationalUnitName         = Organizational Unit Name (eg, section)
+#organizationalUnitName_default        =
+
+commonName                     = Common Name (e.g. server FQDN or YOUR name)
+commonName_max                 = 64
+
+emailAddress                   = Email Address
+emailAddress_max               = 64
+
+# SET-ex3                      = SET extension number 3
+
+[ req_attributes ]
+challengePassword              = A challenge password
+challengePassword_min          = 4
+challengePassword_max          = 20
+
+unstructuredName               = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_name
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1      # the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir            = ./demoCA              # TSA root directory
+serial         = $dir/tsaserial        # The current serial number (mandatory)
+crypto_device  = builtin               # OpenSSL engine to use for signing
+signer_cert    = $dir/tsacert.pem      # The TSA signing certificate
+                                       # (optional)
+certs          = $dir/cacert.pem       # Certificate chain to include in reply
+                                       # (optional)
+signer_key     = $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy = tsa_policy1           # Policy if request did not specify it
+                                       # (optional)
+other_policies = tsa_policy2, tsa_policy3      # acceptable policies (optional)
+digests                = md5, sha1             # Acceptable message digests (mandatory)
+accuracy       = secs:1, millisecs:500, microsecs:100  # (optional)
+clock_precision_digits  = 0    # number of digits after dot. (optional)
+ordering               = yes   # Is ordering defined for timestamps?
+                               # (optional, default: no)
+tsa_name               = yes   # Must the TSA name be included in the reply?
+                               # (optional, default: no)
+ess_cert_id_chain      = no    # Must the ESS cert id chain be included?
+                               # (optional, default: no)
+
+[ alt_name ]
+IP.1 = 127.0.0.1
+IP.2 = ::1
diff --git a/src/lib/asiolink/asiolink/testutils/ca/server-conf.cnf b/src/lib/asiolink/asiolink/testutils/ca/server-conf.cnf
new file mode 100644 (file)
index 0000000..843b641
--- /dev/null
@@ -0,0 +1,354 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME                   = .
+RANDFILE               = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file              = $ENV::HOME/.oid
+oid_section            = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions           = 
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca     = CA_default            # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir            = ./demoCA              # Where everything is kept
+certs          = $dir/certs            # Where the issued certs are kept
+crl_dir                = $dir/crl              # Where the issued crl are kept
+database       = $dir/index.txt        # database index file.
+#unique_subject        = no                    # Set to 'no' to allow creation of
+                                       # several ctificates with same subject.
+new_certs_dir  = $dir/newcerts         # default place for new certs.
+
+certificate    = $dir/cacert.pem       # The CA certificate
+serial         = $dir/serial           # The current serial number
+crlnumber      = $dir/crlnumber        # the current crl number
+                                       # must be commented out to leave a V1 CRL
+crl            = $dir/crl.pem          # The current CRL
+private_key    = $dir/private/cakey.pem# The private key
+RANDFILE       = $dir/private/.rand    # private random number file
+
+x509_extensions        = usr_cert              # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt       = ca_default            # Subject Name options
+cert_opt       = ca_default            # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions       = crl_ext
+
+default_days   = 365                   # how long to certify for
+default_crl_days= 30                   # how long before next CRL
+default_md     = sha256                # use SHA-256 by default
+preserve       = no                    # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy         = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName            = match
+stateOrProvinceName    = match
+organizationName       = match
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+####################################################################
+[ req ]
+default_bits           = 1024
+default_keyfile        = privkey.pem
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+x509_extensions        = v3_ca # The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options. 
+# default: PrintableString, T61String, BMPString.
+# pkix  : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName                    = Country Name (2 letter code)
+countryName_default            = AU
+countryName_min                        = 2
+countryName_max                        = 2
+
+stateOrProvinceName            = State or Province Name (full name)
+#stateOrProvinceName_default   = Some-State
+
+localityName                   = Locality Name (eg, city)
+
+0.organizationName             = Organization Name (eg, company)
+0.organizationName_default     = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName            = Second Organization Name (eg, company)
+#1.organizationName_default    = World Wide Web Pty Ltd
+
+organizationalUnitName         = Organizational Unit Name (eg, section)
+#organizationalUnitName_default        =
+
+commonName                     = Common Name (e.g. server FQDN or YOUR name)
+commonName_max                 = 64
+
+emailAddress                   = Email Address
+emailAddress_max               = 64
+
+# SET-ex3                      = SET extension number 3
+
+[ req_attributes ]
+challengePassword              = A challenge password
+challengePassword_min          = 4
+challengePassword_max          = 20
+
+unstructuredName               = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_name
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1      # the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir            = ./demoCA              # TSA root directory
+serial         = $dir/tsaserial        # The current serial number (mandatory)
+crypto_device  = builtin               # OpenSSL engine to use for signing
+signer_cert    = $dir/tsacert.pem      # The TSA signing certificate
+                                       # (optional)
+certs          = $dir/cacert.pem       # Certificate chain to include in reply
+                                       # (optional)
+signer_key     = $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy = tsa_policy1           # Policy if request did not specify it
+                                       # (optional)
+other_policies = tsa_policy2, tsa_policy3      # acceptable policies (optional)
+digests                = md5, sha1             # Acceptable message digests (mandatory)
+accuracy       = secs:1, millisecs:500, microsecs:100  # (optional)
+clock_precision_digits  = 0    # number of digits after dot. (optional)
+ordering               = yes   # Is ordering defined for timestamps?
+                               # (optional, default: no)
+tsa_name               = yes   # Must the TSA name be included in the reply?
+                               # (optional, default: no)
+ess_cert_id_chain      = no    # Must the ESS cert id chain be included?
+                               # (optional, default: no)
+
+[ alt_name ]
+DNS.1 = localhost
diff --git a/src/lib/asiolink/asiolink/testutils/openssl_sample_client.cc b/src/lib/asiolink/asiolink/testutils/openssl_sample_client.cc
new file mode 100644 (file)
index 0000000..b1c7645
--- /dev/null
@@ -0,0 +1,186 @@
+//
+// client.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <config.h>
+
+#include <iostream>
+
+#ifdef HAVE_GENERIC_TLS_METHOD
+
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+using boost::asio::ip::tcp;
+using std::placeholders::_1;
+using std::placeholders::_2;
+
+inline std::string CA_(const std::string& filename) {
+  return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+enum { max_length = 1024 };
+
+class client
+{
+public:
+  client(boost::asio::io_service& io_context,
+      boost::asio::ssl::context& context,
+      const tcp::endpoint& endpoint)
+    : socket_(io_context, context)
+  {
+    socket_.set_verify_mode(boost::asio::ssl::verify_peer |
+                            boost::asio::ssl::verify_fail_if_no_peer_cert);
+    socket_.set_verify_callback(
+        std::bind(&client::verify_certificate, this, _1, _2));
+
+    connect(endpoint);
+  }
+
+private:
+  bool verify_certificate(bool preverified,
+      boost::asio::ssl::verify_context& ctx)
+  {
+    // The verify callback can be used to check whether the certificate that is
+    // being presented is valid for the peer. For example, RFC 2818 describes
+    // the steps involved in doing this for HTTPS. Consult the OpenSSL
+    // documentation for more details. Note that the callback is called once
+    // for each certificate in the certificate chain, starting from the root
+    // certificate authority.
+
+    // In this example we will simply print the certificate's subject name.
+    char subject_name[256];
+    X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
+    X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
+    std::cout << "Verifying " << subject_name << "\n";
+
+    return preverified;
+  }
+
+  void connect(const tcp::endpoint& endpoint)
+  {
+    socket_.lowest_layer().async_connect(endpoint,
+        [this](const boost::system::error_code& error)
+        {
+          if (!error)
+          {
+            handshake();
+          }
+          else
+          {
+            std::cout << "Connect failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  void handshake()
+  {
+    socket_.async_handshake(boost::asio::ssl::stream_base::client,
+        [this](const boost::system::error_code& error)
+        {
+          if (!error)
+          {
+            send_request();
+          }
+          else
+          {
+            std::cout << "Handshake failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  void send_request()
+  {
+    std::cout << "Enter message: ";
+    std::cin.getline(request_, max_length);
+    size_t request_length = std::strlen(request_);
+
+    boost::asio::async_write(socket_,
+        boost::asio::buffer(request_, request_length),
+        [this](const boost::system::error_code& error, std::size_t length)
+        {
+          if (!error)
+          {
+            receive_response(length);
+          }
+          else
+          {
+            std::cout << "Write failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  void receive_response(std::size_t length)
+  {
+    boost::asio::async_read(socket_,
+        boost::asio::buffer(reply_, length),
+        [this](const boost::system::error_code& error, std::size_t length)
+        {
+          if (!error)
+          {
+            std::cout << "Reply: ";
+            std::cout.write(reply_, length);
+            std::cout << "\n";
+          }
+          else
+          {
+            std::cout << "Read failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  boost::asio::ssl::stream<tcp::socket> socket_;
+  char request_[max_length];
+  char reply_[max_length];
+};
+
+int main(int argc, char* argv[])
+{
+  try
+  {
+    if (argc != 3)
+    {
+      std::cerr << "Usage: client <addr> <port>\n";
+      return 1;
+    }
+
+    boost::asio::io_service io_context;
+
+    using namespace std; // For atoi.
+    tcp::endpoint endpoint(
+      boost::asio::ip::address::from_string(argv[1]), atoi(argv[2]));
+
+    boost::asio::ssl::context ctx(boost::asio::ssl::context::method::tls);
+    ctx.load_verify_file(CA_("kea-ca.crt"));
+    ctx.use_certificate_chain_file(CA_("kea-client.crt"));
+    ctx.use_private_key_file(CA_("kea-client.key"),
+                             boost::asio::ssl::context::pem);
+
+    client c(io_context, ctx, endpoint);
+
+    io_context.run();
+  }
+  catch (std::exception& e)
+  {
+    std::cerr << "Exception: " << e.what() << "\n";
+  }
+
+  return 0;
+}
+#else // !HAVE_GENERIC_TLS_METHOD
+
+int main()
+{
+  std::cerr << "this tool requires recent boost version (>= 1.64)\n";
+  return 0;
+}
+#endif
diff --git a/src/lib/asiolink/asiolink/testutils/openssl_sample_server.cc b/src/lib/asiolink/asiolink/testutils/openssl_sample_server.cc
new file mode 100644 (file)
index 0000000..f2f0c67
--- /dev/null
@@ -0,0 +1,192 @@
+//
+// server.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+// Use the cpp03 version because the cpp11 version does not compile with
+// some g++ e.g. on Fedora 33.
+
+#include <config.h>
+
+#include <iostream>
+
+#ifdef HAVE_GENERIC_TLS_METHOD
+
+#include <cstdlib>
+#include <boost/bind/bind.hpp>
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+inline std::string CA_(const std::string& filename) {
+  return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
+
+class session
+{
+public:
+  session(boost::asio::io_service& io_context,
+      boost::asio::ssl::context& context)
+    : socket_(io_context, context)
+  {
+  }
+
+  ssl_socket::lowest_layer_type& socket()
+  {
+    return socket_.lowest_layer();
+  }
+
+  void start()
+  {
+    socket_.async_handshake(boost::asio::ssl::stream_base::server,
+        boost::bind(&session::handle_handshake, this,
+          boost::asio::placeholders::error));
+  }
+
+  void handle_handshake(const boost::system::error_code& error)
+  {
+    if (!error)
+    {
+      socket_.async_read_some(boost::asio::buffer(data_, max_length),
+          boost::bind(&session::handle_read, this,
+            boost::asio::placeholders::error,
+            boost::asio::placeholders::bytes_transferred));
+    }
+    else
+    {
+      std::cerr << "handshake error '" << error.message() << "'\n";
+      delete this;
+    }
+  }
+
+  void handle_read(const boost::system::error_code& error,
+      size_t bytes_transferred)
+  {
+    if (!error)
+    {
+      boost::asio::async_write(socket_,
+          boost::asio::buffer(data_, bytes_transferred),
+          boost::bind(&session::handle_write, this,
+            boost::asio::placeholders::error));
+    }
+    else
+    {
+      delete this;
+    }
+  }
+
+  void handle_write(const boost::system::error_code& error)
+  {
+    if (!error)
+    {
+      socket_.async_read_some(boost::asio::buffer(data_, max_length),
+          boost::bind(&session::handle_read, this,
+            boost::asio::placeholders::error,
+            boost::asio::placeholders::bytes_transferred));
+    }
+    else
+    {
+      delete this;
+    }
+  }
+
+private:
+  ssl_socket socket_;
+  enum { max_length = 1024 };
+  char data_[max_length];
+};
+
+class server
+{
+public:
+  server(boost::asio::io_service& io_context, unsigned short port)
+    : io_context_(io_context),
+      acceptor_(io_context,
+          boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
+      context_(boost::asio::ssl::context::method::tls)
+  {
+    //context_.set_options(
+    //    boost::asio::ssl::context::default_workarounds
+    //    | boost::asio::ssl::context::no_sslv2
+    //    | boost::asio::ssl::context::single_dh_use);
+    //context_.set_password_callback(boost::bind(&server::get_password, this));
+    context_.set_verify_mode(boost::asio::ssl::verify_peer |
+                             boost::asio::ssl::verify_fail_if_no_peer_cert);
+    context_.load_verify_file(CA_("kea-ca.crt"));
+    context_.use_certificate_chain_file(CA_("kea-server.crt"));
+    context_.use_private_key_file(CA_("kea-server.key"),
+                                  boost::asio::ssl::context::pem);
+    //context_.use_tmp_dh_file("dh2048.pem");
+
+    start_accept();
+  }
+
+  void start_accept()
+  {
+    session* new_session = new session(io_context_, context_);
+    acceptor_.async_accept(new_session->socket(),
+        boost::bind(&server::handle_accept, this, new_session,
+          boost::asio::placeholders::error));
+  }
+
+  void handle_accept(session* new_session,
+      const boost::system::error_code& error)
+  {
+    if (!error)
+    {
+      new_session->start();
+    }
+    else
+    {
+      delete new_session;
+    }
+
+    start_accept();
+  }
+
+private:
+  boost::asio::io_service& io_context_;
+  boost::asio::ip::tcp::acceptor acceptor_;
+  boost::asio::ssl::context context_;
+};
+
+int main(int argc, char* argv[])
+{
+  try
+  {
+    if (argc != 2)
+    {
+      std::cerr << "Usage: server <port>\n";
+      return 1;
+    }
+
+    boost::asio::io_service io_context;
+
+    using namespace std; // For atoi.
+    server s(io_context, atoi(argv[1]));
+
+    io_context.run();
+  }
+  catch (std::exception& e)
+  {
+    std::cerr << "Exception: " << e.what() << "\n";
+  }
+
+  return 0;
+}
+
+#else // !HAVE_GENERIC_TLS_METHOD
+
+int main()
+{
+  std::cerr << "this tool requires recent boost version (>= 1.64)\n";
+  return 0;
+}
+#endif
+
diff --git a/src/lib/asiolink/asiolink/testutils/test_server_unix_socket.cc b/src/lib/asiolink/asiolink/testutils/test_server_unix_socket.cc
new file mode 100644 (file)
index 0000000..7f7007d
--- /dev/null
@@ -0,0 +1,331 @@
+// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <set>
+#include <sstream>
+
+using namespace boost::asio::local;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief ASIO unix domain socket.
+typedef stream_protocol::socket UnixSocket;
+
+/// @brief Pointer to the ASIO unix domain socket.
+typedef boost::shared_ptr<UnixSocket> UnixSocketPtr;
+
+/// @brief Callback function invoked when response is sent from the server.
+typedef std::function<void()> SentResponseCallback;
+
+/// @brief Connection to the server over unix domain socket.
+///
+/// It reads the data over the socket, sends responses and closes a socket.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// It starts asynchronous read operation.
+    ///
+    /// @param unix_socket Pointer to the unix domain socket into which
+    /// connection has been accepted.
+    /// @param custom_response Custom response that the server should send.
+    /// @param sent_response_callback Callback function to be invoked when
+    /// server sends a response.
+    Connection(const UnixSocketPtr& unix_socket,
+               const std::string custom_response,
+               SentResponseCallback sent_response_callback)
+        : socket_(unix_socket), custom_response_(custom_response),
+          sent_response_callback_(sent_response_callback) {
+    }
+
+    /// @brief Starts asynchronous read from the socket.
+    void start() {
+       socket_->async_read_some(boost::asio::buffer(&raw_buf_[0], raw_buf_.size()),
+           std::bind(&Connection::readHandler, shared_from_this(),
+                     ph::_1,   // error
+                     ph::_2)); // bytes_transferred
+    }
+
+    /// @brief Closes the socket.
+    void stop() {
+        try {
+            socket_->close();
+
+        } catch (...) {
+            // ignore errors when closing the socket.
+        }
+    }
+
+    /// @brief Handler invoked when data have been received over the socket.
+    ///
+    /// This is the handler invoked when the data have been received over the
+    /// socket. If custom response has been specified, this response is sent
+    /// back to the client. Otherwise, the handler echoes back the request
+    /// and prepends the word "received ". Finally, it calls a custom
+    /// callback function (specified in the constructor) to notify that the
+    /// response has been sent over the socket.
+    ///
+    /// @param bytes_transferred Number of bytes received.
+    void
+    readHandler(const boost::system::error_code& ec,
+                size_t bytes_transferred) {
+        // This is most likely due to the abort.
+        if (ec) {
+            // An error occurred so let's close the socket.
+            stop();
+            return;
+        }
+
+        if (!custom_response_.empty()) {
+            boost::asio::write(*socket_,
+               boost::asio::buffer(custom_response_.c_str(), custom_response_.size()));
+
+        } else {
+            std::string received(&raw_buf_[0], bytes_transferred);
+            std::string response("received " + received);
+            boost::asio::write(*socket_,
+                boost::asio::buffer(response.c_str(), response.size()));
+        }
+
+        /// @todo We're taking simplistic approach and send a response right away
+        /// after receiving data over the socket. Therefore, after responding we
+        /// do not schedule another read. We could extend this logic slightly to
+        /// parse the received data and see when we've got enough data before we
+        /// send a response. However, the current unit tests don't really require
+        /// that.
+
+        // Invoke callback function to notify that the response has been sent.
+        sent_response_callback_();
+    }
+
+private:
+
+    /// @brief Pointer to the unix domain socket.
+    UnixSocketPtr socket_;
+
+    /// @brief Custom response to be sent to the client.
+    std::string custom_response_;
+
+    /// @brief Receive buffer.
+    std::array<char, 1024> raw_buf_;
+
+    /// @brief Pointer to the callback function to be invoked when response
+    /// has been sent.
+    SentResponseCallback sent_response_callback_;
+
+};
+
+/// @brief Pointer to a Connection object.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Connection pool.
+///
+/// Holds all connections established with the server and gracefully
+/// terminates these connections.
+class ConnectionPool {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service Reference to the IO service.
+    ConnectionPool(IOService& io_service)
+        : io_service_(io_service), connections_(), next_socket_(),
+          response_num_(0) {
+    }
+
+    /// @brief Destructor.
+    ~ConnectionPool() {
+        stopAll();
+    }
+
+    /// @brief Creates new unix domain socket and returns it.
+    ///
+    /// This convenience method creates a socket which can be used to accept
+    /// new connections. If such socket already exists, it is returned.
+    ///
+    /// @return Pointer to the socket.
+    UnixSocketPtr getSocket() {
+        if (!next_socket_) {
+            next_socket_.reset(new UnixSocket(io_service_.get_io_service()));
+        }
+        return (next_socket_);
+    }
+
+    /// @brief Starts new connection.
+    ///
+    /// The socket returned by the @ref ConnectionPool::getSocket is used to
+    /// create new connection. Then, the @ref next_socket_ is reset, to force
+    /// the @ref ConnectionPool::getSocket to generate a new socket for a
+    /// next connection.
+    ///
+    /// @param custom_response Custom response to be sent to the client.
+    void start(const std::string& custom_response) {
+        ConnectionPtr conn(new Connection(next_socket_, custom_response, [this] {
+            ++response_num_;
+        }));
+        conn->start();
+
+        connections_.insert(conn);
+        next_socket_.reset();
+    }
+
+    /// @brief Stops the given connection.
+    ///
+    /// @param conn Pointer to the connection to be stopped.
+    void stop(const ConnectionPtr& conn) {
+        conn->stop();
+        connections_.erase(conn);
+    }
+
+    /// @brief Stops all connections.
+    void stopAll() {
+        for (auto conn = connections_.begin(); conn != connections_.end();
+             ++conn) {
+            (*conn)->stop();
+        }
+        connections_.clear();
+    }
+
+    /// @brief Returns number of responses sent so far.
+    size_t getResponseNum() const {
+        return (response_num_);
+    }
+
+private:
+
+    /// @brief Reference to the IO service.
+    IOService& io_service_;
+
+    /// @brief Container holding established connections.
+    std::set<ConnectionPtr> connections_;
+
+    /// @brief Holds pointer to the generated socket.
+    ///
+    /// This socket will be used by the next connection.
+    UnixSocketPtr next_socket_;
+
+    /// @brief Holds the number of sent responses.
+    size_t response_num_;
+};
+
+
+TestServerUnixSocket::TestServerUnixSocket(IOService& io_service,
+                                           const std::string& socket_file_path,
+                                           const std::string& custom_response)
+    : io_service_(io_service),
+      server_endpoint_(socket_file_path),
+      server_acceptor_(io_service_.get_io_service()),
+      test_timer_(io_service_),
+      custom_response_(custom_response),
+      connection_pool_(new ConnectionPool(io_service)),
+      stopped_(false),
+      running_(false) {
+}
+
+TestServerUnixSocket::~TestServerUnixSocket() {
+    server_acceptor_.close();
+}
+
+void
+TestServerUnixSocket::generateCustomResponse(const uint64_t response_size) {
+    std::ostringstream s;
+    s << "{";
+    while (s.tellp() < response_size) {
+        s << "\"param\": \"value\",";
+    }
+    s << "}";
+    custom_response_ = s.str();
+}
+
+void
+TestServerUnixSocket::startTimer(const long test_timeout) {
+    test_timer_.setup(std::bind(&TestServerUnixSocket::timeoutHandler, this),
+                      test_timeout, IntervalTimer::ONE_SHOT);
+}
+
+void
+TestServerUnixSocket::stopServer() {
+    test_timer_.cancel();
+    server_acceptor_.cancel();
+    connection_pool_->stopAll();
+}
+
+void
+TestServerUnixSocket::bindServerSocket(const bool use_thread) {
+    server_acceptor_.open();
+    server_acceptor_.bind(server_endpoint_);
+    server_acceptor_.listen();
+    accept();
+
+    // When threads are in use, we need to post a handler which will be invoked
+    // when the thread has already started and the IO service is running. The
+    // main thread can move forward when it receives this signal from the handler.
+    if (use_thread) {
+        io_service_.post(std::bind(&TestServerUnixSocket::signalRunning,
+                                   this));
+    }
+}
+
+void
+TestServerUnixSocket::acceptHandler(const boost::system::error_code& ec) {
+    if (ec) {
+        return;
+    }
+
+    connection_pool_->start(custom_response_);
+    accept();
+}
+
+void
+TestServerUnixSocket::accept() {
+    server_acceptor_.async_accept(*(connection_pool_->getSocket()),
+        std::bind(&TestServerUnixSocket::acceptHandler, this,
+                  ph::_1)); // error
+}
+
+void
+TestServerUnixSocket::signalRunning() {
+    {
+        std::lock_guard<std::mutex> lock(mutex_);
+        running_ = true;
+    }
+    condvar_.notify_one();
+}
+
+void
+TestServerUnixSocket::waitForRunning() {
+    std::unique_lock<std::mutex> lock(mutex_);
+    while (!running_) {
+        condvar_.wait(lock);
+    }
+}
+
+void
+TestServerUnixSocket::timeoutHandler() {
+    ADD_FAILURE() << "Timeout occurred while running the test!";
+    io_service_.stop();
+    stopped_ = true;
+}
+
+size_t
+TestServerUnixSocket::getResponseNum() const {
+    return (connection_pool_->getResponseNum());
+}
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/asiolink/testutils/test_server_unix_socket.h b/src/lib/asiolink/asiolink/testutils/test_server_unix_socket.h
new file mode 100644 (file)
index 0000000..c272ee7
--- /dev/null
@@ -0,0 +1,171 @@
+// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_SERVER_UNIX_SOCKET_H
+#define TEST_SERVER_UNIX_SOCKET_H
+
+#include <config.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <stdint.h>
+#include <string>
+#include <mutex>
+#include <condition_variable>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+class ConnectionPool;
+
+/// @brief Provides unix domain socket functionality for unit tests.
+///
+/// This class represents a server side socket. It can be used to
+/// test client's transmission over the unix domain socket. By default,
+/// the server side socket echoes the client's message so the client's
+/// message (prefixed with the word "received").
+///
+/// It is also possible to specify a custom response from the server
+/// instead of echoing back the request.
+///
+/// It is possible to make multiple connections to the server side
+/// socket simultaneously.
+///
+/// The test should perform IOService::run_one until it finds that
+/// the number of responses sent by the server is greater than
+/// expected. The number of responses sent so far can be retrieved
+/// using @ref TestServerUnixSocket::getResponseNum.
+///
+/// This class uses @c shared_from_this() to pass its instance to the
+/// @c std::bind function, thus the caller must store shared pointer
+/// to this object.
+class TestServerUnixSocket {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service.
+    /// @param socket_file_path Socket file path.
+    /// @param custom_response Custom response to be sent to the client.
+    TestServerUnixSocket(IOService& io_service,
+                         const std::string& socket_file_path,
+                         const std::string& custom_response = "");
+
+    /// @brief Destructor.
+    ///
+    /// Closes active connections.
+    ~TestServerUnixSocket();
+
+    /// @brief Starts timer for detecting test timeout.
+    ///
+    /// @param test_timeout Test timeout in milliseconds.
+    void startTimer(const long test_timeout);
+
+    /// @brief Cancels all asynchronous operations.
+    void stopServer();
+
+    /// @brief Generates response of a given length.
+    ///
+    /// Note: The response may be a few bytes larger than requested.
+    ///
+    /// @param response_size Desired response size.
+    void generateCustomResponse(const uint64_t response_size);
+
+    /// @brief Creates and binds server socket.
+    ///
+    /// @param use_thread Boolean value indicating if the IO service
+    /// is running in thread.
+    void bindServerSocket(const bool use_thread = false);
+
+    /// @brief Server acceptor handler.
+    ///
+    /// @param ec Error code.
+    void acceptHandler(const boost::system::error_code& ec);
+
+    /// @brief Callback function invoke upon test timeout.
+    ///
+    /// It stops the IO service and reports test timeout.
+    void timeoutHandler();
+
+    /// @brief Return number of responses sent so far to the clients.
+    size_t getResponseNum() const;
+
+    /// @brief Indicates if the server has been stopped.
+    bool isStopped() {
+        return (stopped_);
+    }
+
+    /// @brief Waits for the server signal that it is running.
+    ///
+    /// When the caller starts the service he indicates whether
+    /// IO service will be running in thread or not. If threads
+    /// are used the caller has to wait for the IO service to
+    /// actually run. In such case this function should be invoked
+    /// which waits for a posted callback to be executed. When this
+    /// happens it means that IO service is running and the main
+    /// thread can move forward.
+    void waitForRunning();
+
+private:
+
+    /// @brief Asynchronously accept new connections.
+    void accept();
+
+    /// @brief Handler invoked to signal that server is running.
+    ///
+    /// This is used only when thread is used to run IO service.
+    void signalRunning();
+
+    /// @brief IO service used by the tests.
+    IOService& io_service_;
+
+    /// @brief Server endpoint.
+    boost::asio::local::stream_protocol::endpoint server_endpoint_;
+    /// @brief Server acceptor.
+    boost::asio::local::stream_protocol::acceptor server_acceptor_;
+
+    /// @brief Asynchronous timer service to detect timeouts.
+    IntervalTimer test_timer_;
+
+    /// @brief Holds custom response to be sent to the client.
+    std::string custom_response_;
+
+    /// @brief Pool of connections.
+    boost::shared_ptr<ConnectionPool> connection_pool_;
+
+    /// @brief Indicates if IO service has been stopped as a result of
+    /// a timeout.
+    bool stopped_;
+
+    /// @brief Indicates if the server in a thread is running.
+    bool running_;
+
+    /// @brief Mutex used by the server.
+    ///
+    /// Mutex is used in situations when server's IO service is being run in a
+    /// thread to synchronize this thread with a main thread using
+    /// @ref signalRunning and @ref waitForRunning.
+    std::mutex mutex_;
+
+    /// @brief Conditional variable used by the server.
+    ///
+    /// Conditional variable is used in situations when server's IO service is
+    /// being run in a thread to synchronize this thread with a main thread
+    /// using @ref signalRunning and @ref waitForRunning.
+    std::condition_variable condvar_;
+};
+
+/// @brief Pointer to the @ref TestServerUnixSocket.
+typedef boost::shared_ptr<TestServerUnixSocket> TestServerUnixSocketPtr;
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TEST_SERVER_UNIX_SOCKET_H
diff --git a/src/lib/asiolink/asiolink/testutils/test_tls.cc b/src/lib/asiolink/asiolink/testutils/test_tls.cc
new file mode 100644 (file)
index 0000000..0f4d7a5
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/testutils/test_tls.h>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Configure the TLS server.
+void configServer(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-server.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-server.key");
+    TlsContext::configure(ctx, TlsRole::SERVER, ca, cert, key, true);
+}
+
+/// @brief Configure the TLS client.
+void configClient(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-client.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-client.key");
+    TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+/// @brief Configure another TLS client.
+void configOther(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-other.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-other.key");
+    TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+/// @brief Configure self-signed TLS client.
+void configSelf(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-self.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-self.key");
+    TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/asiolink/testutils/test_tls.h b/src/lib/asiolink/asiolink/testutils/test_tls.h
new file mode 100644 (file)
index 0000000..3bd9fbc
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_TLS_H
+#define TEST_TLS_H
+
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before test_tls.h
+#endif
+
+#include <asiolink/crypto_tls.h>
+
+#include <string>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Configure the TLS server.
+void configServer(TlsContextPtr& ctx);
+
+/// @brief Configure the TLS client.
+void configClient(TlsContextPtr& ctx);
+
+/// @brief Configure another TLS client.
+void configOther(TlsContextPtr& ctx);
+
+/// @brief Configure self-signed TLS client.
+void configSelf(TlsContextPtr& ctx);
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TEST_TLS_H
diff --git a/src/lib/asiolink/asiolink/testutils/timed_signal.cc b/src/lib/asiolink/asiolink/testutils/timed_signal.cc
new file mode 100644 (file)
index 0000000..28d4b1b
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/testutils/timed_signal.h>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/asiolink/testutils/timed_signal.h b/src/lib/asiolink/asiolink/testutils/timed_signal.h
new file mode 100644 (file)
index 0000000..3af545d
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TIMED_SIGNAL_H
+#define TIMED_SIGNAL_H
+
+#include <config.h>
+
+#include <asiolink/interval_timer.h>
+#include <signal.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Implements a time-delayed signal
+///
+/// Given an IOService, a signal number, and a time period, this class will
+/// send (raise) the signal to the current process.
+class TimedSignal {
+public:
+    /// @brief Constructor
+    ///
+    /// @param io_service  IOService to run the timer
+    /// @param signum OS signal value (e.g. SIGHUP, SIGUSR1 ...)
+    /// @param milliseconds time in milliseconds to wait until the signal is
+    /// raised.
+    /// @param mode selects between a one-shot signal or a signal which repeats
+    /// at "milliseconds" interval.
+    TimedSignal(asiolink::IOService& io_service, int signum, int milliseconds,
+                const asiolink::IntervalTimer::Mode& mode =
+                asiolink::IntervalTimer::ONE_SHOT)
+        : timer_(new asiolink::IntervalTimer(io_service)) {
+        timer_->setup(SendSignalCallback(signum), milliseconds, mode);
+    }
+
+    /// @brief Cancels the given timer.
+    void cancel() {
+        if (timer_) {
+            timer_->cancel();
+        }
+    }
+
+    /// @brief Destructor.
+    ~TimedSignal() {
+        cancel();
+    }
+
+    /// @brief Callback for the TimeSignal's internal timer.
+    class SendSignalCallback: public std::unary_function<void, void> {
+    public:
+
+        /// @brief Constructor
+        ///
+        /// @param signum OS signal value of the signal to send
+        SendSignalCallback(int signum) : signum_(signum) {
+        }
+
+        /// @brief Callback method invoked when the timer expires
+        ///
+        /// Calls raise with the given signal which should generate that
+        /// signal to the given process.
+        void operator()() {
+            ASSERT_EQ(0, raise(signum_));
+            return;
+        }
+
+    private:
+        /// @brief Stores the OS signal value to send.
+        int signum_;
+    };
+
+private:
+    /// @brief Timer which controls when the signal is sent.
+    asiolink::IntervalTimerPtr timer_;
+};
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TIMED_SIGNAL_H
diff --git a/src/lib/asiolink/asiolink/tls_acceptor.h b/src/lib/asiolink/asiolink/tls_acceptor.h
new file mode 100644 (file)
index 0000000..c575559
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TLS_ACCEPTOR_H
+#define TLS_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/tls_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <netinet/in.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Provides a service for accepting new TLS connections.
+///
+/// @tparam C Acceptor callback type.
+template<typename C>
+class TLSAcceptor : public TCPAcceptor<C> {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service.
+    explicit TLSAcceptor(IOService& io_service) : TCPAcceptor<C>(io_service) {
+    }
+
+    /// @brief Destructor.
+    virtual ~TLSAcceptor() { }
+
+    /// @brief Asynchronously accept new connection.
+    ///
+    /// This method accepts new connection into the specified socket. When the
+    /// new connection arrives or an error occurs the specified callback function
+    /// is invoked.
+    ///
+    /// @param socket Socket into which connection should be accepted.
+    /// @param callback Callback function to be invoked when the new connection
+    /// arrives.
+    /// @tparam SocketCallback Type of the callback for the @ref TLSSocket.
+    template<typename SocketCallback>
+    void asyncAccept(const TLSSocket<SocketCallback>& socket, C& callback) {
+        TCPAcceptor<C>::acceptor_->async_accept(socket.getASIOSocket(), callback);
+    }
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif
diff --git a/src/lib/asiolink/asiolink/tls_socket.h b/src/lib/asiolink/asiolink/tls_socket.h
new file mode 100644 (file)
index 0000000..67a9233
--- /dev/null
@@ -0,0 +1,526 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TLS_SOCKET_H
+#define TLS_SOCKET_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/crypto_tls.h>
+#include <asiolink/tcp_socket.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief The @c TLSSocket class is a concrete derived class of @c IOAsioSocket
+/// that represents a TLS socket.
+///
+/// @tparam C Callback type.
+template <typename C>
+class TLSSocket : public IOAsioSocket<C>, private boost::noncopyable {
+public:
+
+    /// @brief Constructor from a TLS stream.
+    ///
+    /// It is assumed that the caller will open and close the stream,
+    /// so these operations are a no-op for that stream.
+    ///
+    /// @param stream The TLS stream.
+    TLSSocket(TlsStream<C>& stream);
+
+    /// @brief Constructor.
+    ///
+    /// Used when the TLSSocket is being asked to manage its own internal
+    /// socket.  In this case, the open() and close() methods are used.
+    ///
+    /// @param service I/O Service object used to manage the socket.
+    /// @param context Pointer to TLS context.
+    TLSSocket(IOService& service, TlsContextPtr context);
+
+    /// @brief Destructor.
+    virtual ~TLSSocket() { }
+
+    /// @brief Return file descriptor of underlying socket.
+    virtual int getNative() const {
+#if BOOST_VERSION < 106600
+        return (socket_.native());
+#else
+        return (socket_.native_handle());
+#endif
+    }
+
+    /// @brief Return protocol of socket.
+    virtual int getProtocol() const {
+        return (IPPROTO_TCP);
+    }
+
+    /// @brief Is "open()" synchronous predicate.
+    ///
+    /// Indicates that the opening of a TLS socket is asynchronous.
+    virtual bool isOpenSynchronous() const {
+        return (false);
+    }
+
+    /// @brief Checks if the connection is usable.
+    ///
+    /// The connection is usable if the socket is open and the peer has not
+    /// closed its connection.
+    ///
+    /// @return true if the connection is usable.
+    bool isUsable() const {
+        // If the socket is open it doesn't mean that it is still
+        // usable. The connection could have been closed on the other
+        // end. We have to check if we can still use this socket.
+        if (socket_.is_open()) {
+            // Remember the current non blocking setting.
+            const bool non_blocking_orig = socket_.non_blocking();
+
+            // Set the socket to non blocking mode. We're going to
+            // test if the socket returns would_block status on the
+            // attempt to read from it.
+            socket_.non_blocking(true);
+
+            // Use receive with message peek flag to avoid removing
+            // the data awaiting to be read.
+            char data[2];
+            int err = 0;
+            int cc = recv(getNative(), data, sizeof(data), MSG_PEEK);
+            if (cc < 0) {
+                // Error case.
+                err = errno;
+            } else if (cc == 0) {
+                // End of file.
+                err = -1;
+            }
+
+            // Revert the original non_blocking flag on the socket.
+            socket_.non_blocking(non_blocking_orig);
+
+            // If the connection is alive we'd typically get
+            // would_block status code.  If there are any data that
+            // haven't been read we may also get success status. We're
+            // guessing that try_again may also be returned by some
+            // implementations in some situations. Any other error
+            // code indicates a problem with the connection so we
+            // assume that the connection has been closed.
+            return ((err == 0) || (err == EAGAIN) || (err == EWOULDBLOCK));
+        }
+
+        return (false);
+    }
+
+    /// @brief Open Socket.
+    ///
+    /// Opens the TLS socket.  This is an asynchronous operation, completion of
+    /// which will be signalled via a call to the callback function.
+    ///
+    /// @param endpoint Endpoint to which the socket will connect.
+    /// @param callback Callback object.
+    virtual void open(const IOEndpoint* endpoint, C& callback);
+
+    /// @brief Perform Handshake.
+    ///
+    /// Perform the TLS handshake. This is an asynchronous operation,
+    /// completion of which will be signalled via a call to the callback
+    /// function.
+    ///
+    /// @param callback Callback object.
+    virtual void handshake(C& callback);
+
+    /// @brief Send Asynchronously.
+    ///
+    /// Calls the underlying socket's async_send() method to send a
+    /// packet of data asynchronously to the remote endpoint.  The
+    /// callback will be called on completion.
+    ///
+    /// @param data Data to send.
+    /// @param length Length of data to send.
+    /// @param endpoint Target of the send. (Unused for a TLS socket because
+    /// that was determined when the connection was opened.)
+    /// @param callback Callback object.
+    /// @throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+    virtual void asyncSend(const void* data, size_t length,
+                           const IOEndpoint* endpoint, C& callback);
+
+    /// @brief Send Asynchronously without count.
+    ///
+    /// This variant of the method sends data over the TLS socket without
+    /// preceding the data with a data count. Eventually, we should migrate
+    /// the virtual method to not insert the count but there are existing
+    /// classes using the count. Once this migration is done, the existing
+    /// virtual method should be replaced by this method.
+    ///
+    /// @param data Data to send.
+    /// @param length Length of data to send.
+    /// @param callback Callback object.
+    /// @throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+    void asyncSend(const void* data, size_t length, C& callback);
+
+    /// @brief Receive Asynchronously.
+    ///
+    /// Calls the underlying socket's async_receive() method to read a packet
+    /// of data from a remote endpoint.  Arrival of the data is signalled via a
+    /// call to the callback function.
+    ///
+    /// @param data Buffer to receive incoming message.
+    /// @param length Length of the data buffer.
+    /// @param offset Offset into buffer where data is to be put.
+    /// @param endpoint Source of the communication.
+    /// @param callback Callback object.
+    virtual void asyncReceive(void* data, size_t length, size_t offset,
+                              IOEndpoint* endpoint, C& callback);
+
+    /// @brief Process received data packet.
+    ///
+    /// See the description of IOAsioSocket::receiveComplete for a complete
+    /// description of this method.
+    ///
+    /// @param staging Pointer to the start of the staging buffer.
+    /// @param length Amount of data in the staging buffer.
+    /// @param cumulative Amount of data received before the staging buffer is
+    /// processed.
+    /// @param offset Unused.
+    /// @param expected unused.
+    /// @param outbuff Output buffer.  Data in the staging buffer is be copied
+    ///        to this output buffer in the call.
+    ///
+    /// @return Always true.
+    virtual bool processReceivedData(const void* staging, size_t length,
+                                     size_t& cumulative, size_t& offset,
+                                     size_t& expected,
+                                     isc::util::OutputBufferPtr& outbuff);
+
+    /// @brief Cancel I/O On Socket.
+    virtual void cancel();
+
+    /// @brief Close socket.
+    virtual void close();
+
+    /// @brief TLS shutdown.
+    ///
+    /// The callback is called on completion i.e. when the peer performs
+    /// a shutdown or a close.
+    virtual void shutdown(C& callback);
+
+    /// @brief Returns reference to the underlying ASIO socket.
+    ///
+    /// @return Reference to underlying ASIO socket.
+    virtual typename TlsStream<C>::lowest_layer_type& getASIOSocket() const {
+        return (socket_);
+    }
+
+    /// @brief Returns reference to the underlying TLS stream.
+    ///
+    /// @return Reference to underlying TLS stream.
+    virtual TlsStream<C>& getTlsStream() const {
+        return (stream_);
+    }
+
+private:
+    /// Two variables to hold the stream - a stream and a pointer to it.  This
+    /// handles the case where a stream is passed to the TLSSocket on
+    /// construction, or where it is asked to manage its own stream.
+
+    /// @brief Pointer to own stream.
+    std::unique_ptr<TlsStream<C>> stream_ptr_;
+
+    /// @brief TLS stream.
+    TlsStream<C>& stream_;
+
+    /// @brief Underlying TCP socket.
+    typename TlsStream<C>::lowest_layer_type& socket_;
+
+    /// @todo Remove temporary buffer
+    /// The current implementation copies the buffer passed to asyncSend() into
+    /// a temporary buffer and precedes it with a two-byte count field.  As
+    /// ASIO should really be just about sending and receiving data, the TCP
+    /// code should not do this.  If the protocol using this requires a two-byte
+    /// count, it should add it before calling this code.  (This may be best
+    /// achieved by altering isc::dns::buffer to have pairs of methods:
+    /// getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
+    /// methods taking into account a two-byte count field.)
+    ///
+    /// The option of sending the data in two operations, the count followed by
+    /// the data was discounted as that would lead to two callbacks which would
+    /// cause problems with the stackless coroutine code.
+
+    /// @brief Send buffer.
+    isc::util::OutputBufferPtr send_buffer_;
+};
+
+// Constructor - caller manages socket.
+
+template <typename C>
+TLSSocket<C>::TLSSocket(TlsStream<C>& stream) :
+    stream_ptr_(), stream_(stream),
+    socket_(stream_.lowest_layer()), send_buffer_() {
+}
+
+// Constructor - create socket on the fly.
+
+template <typename C>
+TLSSocket<C>::TLSSocket(IOService& service, TlsContextPtr context) :
+    stream_ptr_(new TlsStream<C>(service, context)),
+    stream_(*stream_ptr_), socket_(stream_.lowest_layer()), send_buffer_()
+{
+}
+
+// Open the socket.
+
+template <typename C> void
+TLSSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
+    // If socket is open on this end but has been closed by the peer,
+    // we need to reconnect.
+    if (socket_.is_open() && !isUsable()) {
+        socket_.close();
+    }
+
+    // Ignore opens on already-open socket.  Don't throw a failure because
+    // of uncertainties as to what precedes when using asynchronous I/O.
+    // Also allows us a treat a passed-in socket as a self-managed socket.
+    if (!socket_.is_open()) {
+        if (endpoint->getFamily() == AF_INET) {
+            socket_.open(boost::asio::ip::tcp::v4());
+        } else {
+            socket_.open(boost::asio::ip::tcp::v6());
+        }
+
+        // Set options on the socket:
+
+        // Reuse address - allow the socket to bind to a port even if the port
+        // is in the TIMED_WAIT state.
+        socket_.set_option(boost::asio::socket_base::reuse_address(true));
+    }
+
+    // Upconvert to a TCPEndpoint.  We need to do this because although
+    // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
+    // contain a method for getting at the underlying endpoint type - that is in
+    /// the derived class and the two classes differ on return type.
+    isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+    const TCPEndpoint* tcp_endpoint =
+        static_cast<const TCPEndpoint*>(endpoint);
+
+    // Connect to the remote endpoint.  On success, the handler will be
+    // called (with one argument - the length argument will default to
+    // zero).
+    socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
+}
+
+// Perform the handshake.
+
+template <typename C> void
+TLSSocket<C>::handshake(C& callback) {
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen, "attempt to perform handshake on "
+                  "a TLS socket that is not open");
+    }
+    stream_.handshake(callback);
+}
+
+// Send a message.  Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+TLSSocket<C>::asyncSend(const void* data, size_t length, C& callback)
+{
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen,
+                  "attempt to send on a TLS socket that is not open");
+    }
+
+    try {
+        send_buffer_.reset(new isc::util::OutputBuffer(length));
+        send_buffer_->writeData(data, length);
+
+        // Send the data.
+        boost::asio::async_write(stream_,
+                                 boost::asio::buffer(send_buffer_->getData(),
+                                                     send_buffer_->getLength()),
+                                 callback);
+    } catch (const boost::numeric::bad_numeric_cast&) {
+        isc_throw(BufferTooLarge,
+                  "attempt to send buffer larger than 64kB");
+    }
+}
+
+template <typename C> void
+TLSSocket<C>::asyncSend(const void* data, size_t length,
+                        const IOEndpoint*, C& callback)
+{
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen,
+                  "attempt to send on a TLS socket that is not open");
+    }
+
+    /// Need to copy the data into a temporary buffer and precede it with
+    /// a two-byte count field.
+    /// @todo arrange for the buffer passed to be preceded by the count
+    try {
+        // Ensure it fits into 16 bits
+        uint16_t count = boost::numeric_cast<uint16_t>(length);
+
+        // Copy data into a buffer preceded by the count field.
+        send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
+        send_buffer_->writeUint16(count);
+        send_buffer_->writeData(data, length);
+
+        // ... and send it
+        boost::asio::async_write(stream_,
+                                 boost::asio::buffer(send_buffer_->getData(),
+                                                     send_buffer_->getLength()),
+                                 callback);
+    } catch (const boost::numeric::bad_numeric_cast&) {
+        isc_throw(BufferTooLarge,
+                  "attempt to send buffer larger than 64kB");
+    }
+}
+
+// Receive a message. Note that the "offset" argument is used as an index
+// into the buffer in order to decide where to put the data.  It is up to the
+// caller to initialize the data to zero
+template <typename C> void
+TLSSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+                           IOEndpoint* endpoint, C& callback)
+{
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen,
+                  "attempt to receive from a TLS socket that is not open");
+    }
+
+    // Upconvert to a TCPEndpoint.  We need to do this because although
+    // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+    // does not contain a method for getting at the underlying endpoint
+    // type - that is in the derived class and the two classes differ on
+    // return type.
+    isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+    TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
+
+    // Write the endpoint details from the communications link.  Ideally
+    // we should make IOEndpoint assignable, but this runs in to all sorts
+    // of problems concerning the management of the underlying Boost
+    // endpoint (e.g. if it is not self-managed, is the copied one
+    // self-managed?) The most pragmatic solution is to let Boost take care
+    // of everything and copy details of the underlying endpoint.
+    tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
+
+    // Ensure we can write into the buffer and if so, set the pointer to
+    // where the data will be written.
+    if (offset >= length) {
+        isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+                  "TCP receive buffer");
+    }
+    void* buffer_start =
+        static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+    // ... and kick off the read.
+    stream_.async_read_some(boost::asio::buffer(buffer_start, length - offset),
+                            callback);
+}
+
+// Is the receive complete?
+
+template <typename C> bool
+TLSSocket<C>::processReceivedData(const void* staging, size_t length,
+                                  size_t& cumulative, size_t& offset,
+                                  size_t& expected,
+                                  isc::util::OutputBufferPtr& outbuff)
+{
+    // Point to the data in the staging buffer and note how much there is.
+    const uint8_t* data = static_cast<const uint8_t*>(staging);
+    size_t data_length = length;
+
+    // Is the number is "expected" valid?  It won't be unless we have received
+    // at least two bytes of data in total for this set of receives.
+    if (cumulative < 2) {
+
+        // "expected" is not valid.  Did this read give us enough data to
+        // work it out?
+        cumulative += length;
+        if (cumulative < 2) {
+
+            // Nope, still not valid.  This must have been the first packet and
+            // was only one byte long.  Tell the fetch code to read the next
+            // packet into the staging buffer beyond the data that is already
+            // there so that the next time we are called we have a complete
+            // TCP count.
+            offset = cumulative;
+            return (false);
+        }
+
+        // Have enough data to interpret the packet count, so do so now.
+        expected = isc::util::readUint16(data, cumulative);
+
+        // We have two bytes less of data to process.  Point to the start of the
+        // data and adjust the packet size.  Note that at this point,
+        // "cumulative" is the true amount of data in the staging buffer, not
+        // "length".
+        data += 2;
+        data_length = cumulative - 2;
+    } else {
+
+        // Update total amount of data received.
+        cumulative += length;
+    }
+
+    // Regardless of anything else, the next read goes into the start of the
+    // staging buffer.
+    offset = 0;
+
+    // Work out how much data we still have to put in the output buffer. (This
+    // could be zero if we have just interpreted the TCP count and that was
+    // set to zero.)
+    if (expected >= outbuff->getLength()) {
+
+        // Still need data in the output packet.  Copy what we can from the
+        // staging buffer to the output buffer.
+        size_t copy_amount = std::min(expected - outbuff->getLength(),
+                                      data_length);
+        outbuff->writeData(data, copy_amount);
+    }
+
+    // We can now say if we have all the data.
+    return (expected == outbuff->getLength());
+}
+
+// Cancel I/O on the socket.  No-op if the socket is not open.
+
+template <typename C> void
+TLSSocket<C>::cancel() {
+    if (socket_.is_open()) {
+        socket_.cancel();
+    }
+}
+
+// TLS shutdown.  Can be used for orderly close.
+
+template <typename C> void
+TLSSocket<C>::shutdown(C& callback) {
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen, "attempt to perform shutdown on "
+                  "a TLS socket that is not open");
+    }
+    stream_.shutdown(callback);
+}
+
+// Close the socket down.  Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TLSSocket<C>::close() {
+    stream_.clear();
+    if (socket_.is_open() && stream_ptr_) {
+        socket_.close();
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // TLS_SOCKET_H
diff --git a/src/lib/asiolink/asiolink/udp_endpoint.h b/src/lib/asiolink/asiolink/udp_endpoint.h
new file mode 100644 (file)
index 0000000..894032b
--- /dev/null
@@ -0,0 +1,115 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UDP_ENDPOINT_H
+#define UDP_ENDPOINT_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c UDPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a UDP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class UDPEndpoint : public IOEndpoint {
+public:
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    //@{
+
+    /// \brief Default Constructor
+    ///
+    /// Creates an internal endpoint.  This is expected to be set by some
+    /// external call.
+    UDPEndpoint() :
+        asio_endpoint_placeholder_(new boost::asio::ip::udp::endpoint()),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief Constructor from a pair of address and port.
+    ///
+    /// \param address The IP address of the endpoint.
+    /// \param port The UDP port number of the endpoint.
+    UDPEndpoint(const IOAddress& address, const unsigned short port) :
+        asio_endpoint_placeholder_(
+            new boost::asio::ip::udp::endpoint(boost::asio::ip::address::from_string(address.toText()),
+                              port)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief Constructor from an ASIO UDP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c udp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the UDP endpoint.
+    UDPEndpoint(boost::asio::ip::udp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+    {}
+
+    /// \brief Constructor from an ASIO UDP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c udp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+    UDPEndpoint(const boost::asio::ip::udp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(new boost::asio::ip::udp::endpoint(asio_endpoint)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief The destructor.
+    virtual ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
+    //@}
+
+    virtual IOAddress getAddress() const {
+        return (asio_endpoint_.address());
+    }
+
+    virtual const struct sockaddr& getSockAddr() const {
+        return (*asio_endpoint_.data());
+    }
+
+    virtual uint16_t getPort() const {
+        return (asio_endpoint_.port());
+    }
+
+    virtual short getProtocol() const {
+        return (asio_endpoint_.protocol().protocol());
+    }
+
+    virtual short getFamily() const {
+        return (asio_endpoint_.protocol().family());
+    }
+
+    // This is not part of the exposed IOEndpoint API but allows
+    // direct access to the ASIO implementation of the endpoint
+    inline const boost::asio::ip::udp::endpoint& getASIOEndpoint() const {
+        return (asio_endpoint_);
+    }
+    inline boost::asio::ip::udp::endpoint& getASIOEndpoint() {
+        return (asio_endpoint_);
+    }
+
+private:
+    boost::asio::ip::udp::endpoint* asio_endpoint_placeholder_;
+    boost::asio::ip::udp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+} // namespace isc
+#endif // UDP_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/asiolink/udp_socket.h b/src/lib/asiolink/asiolink/udp_socket.h
new file mode 100644 (file)
index 0000000..66815ed
--- /dev/null
@@ -0,0 +1,324 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UDP_SOCKET_H
+#define UDP_SOCKET_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>             // for some IPC/network system calls
+
+#include <cstddef>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+
+#include <exceptions/isc_assert.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c UDPSocket class is a concrete derived class of \c IOAsioSocket
+/// that represents a UDP socket.
+///
+/// \param C Callback type
+template <typename C>
+class UDPSocket : public IOAsioSocket<C> {
+private:
+    /// \brief Class is non-copyable
+    UDPSocket(const UDPSocket&);
+    UDPSocket& operator=(const UDPSocket&);
+
+public:
+    enum {
+        MIN_SIZE = 4096         // Minimum send and receive size
+    };
+
+    /// \brief Constructor from an ASIO UDP socket.
+    ///
+    /// \param socket The ASIO representation of the UDP socket.  It is assumed
+    ///        that the caller will open and close the socket, so these
+    ///        operations are a no-op for that socket.
+    UDPSocket(boost::asio::ip::udp::socket& socket);
+
+    /// \brief Constructor
+    ///
+    /// Used when the UDPSocket is being asked to manage its own internal
+    /// socket.  In this case, the open() and close() methods are used.
+    ///
+    /// \param service I/O Service object used to manage the socket.
+    UDPSocket(IOService& service);
+
+    /// \brief Destructor
+    virtual ~UDPSocket();
+
+    /// \brief Return file descriptor of underlying socket
+    virtual int getNative() const {
+#if BOOST_VERSION < 106600
+        return (socket_.native());
+#else
+        return (socket_.native_handle());
+#endif
+    }
+
+    /// \brief Return protocol of socket
+    virtual int getProtocol() const {
+        return (IPPROTO_UDP);
+    }
+
+    /// \brief Is "open()" synchronous?
+    ///
+    /// Indicates that the opening of a UDP socket is synchronous.
+    virtual bool isOpenSynchronous() const {
+        return true;
+    }
+
+    /// \brief Open Socket
+    ///
+    /// Opens the UDP socket.  This is a synchronous operation.
+    ///
+    /// \param endpoint Endpoint to which the socket will send data.  This is
+    ///        used to determine the address family that should be used for the
+    ///        underlying socket.
+    /// \param callback Unused as the operation is synchronous.
+    virtual void open(const IOEndpoint* endpoint, C& callback);
+
+    /// \brief Send Asynchronously
+    ///
+    /// Calls the underlying socket's async_send_to() method to send a packet of
+    /// data asynchronously to the remote endpoint.  The callback will be called
+    /// on completion.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param endpoint Target of the send
+    /// \param callback Callback object.
+    virtual void asyncSend(const void* data, size_t length,
+                           const IOEndpoint* endpoint, C& callback);
+
+    /// \brief Receive Asynchronously
+    ///
+    /// Calls the underlying socket's async_receive_from() method to read a
+    /// packet of data from a remote endpoint.  Arrival of the data is signalled
+    /// via a call to the callback function.
+    ///
+    /// \param data Buffer to receive incoming message
+    /// \param length Length of the data buffer
+    /// \param offset Offset into buffer where data is to be put
+    /// \param endpoint Source of the communication
+    /// \param callback Callback object
+    virtual void asyncReceive(void* data, size_t length, size_t offset,
+                              IOEndpoint* endpoint, C& callback);
+
+    /// \brief Process received data
+    ///
+    /// See the description of IOAsioSocket::receiveComplete for a complete
+    /// description of this method.
+    ///
+    /// \param staging Pointer to the start of the staging buffer.
+    /// \param length Amount of data in the staging buffer.
+    /// \param cumulative Amount of data received before the staging buffer is
+    ///        processed.
+    /// \param offset Unused.
+    /// \param expected unused.
+    /// \param outbuff Output buffer.  Data in the staging buffer is be copied
+    ///        to this output buffer in the call.
+    ///
+    /// \return Always true
+    virtual bool processReceivedData(const void* staging, size_t length,
+                                     size_t& cumulative, size_t& offset,
+                                     size_t& expected,
+                                     isc::util::OutputBufferPtr& outbuff);
+
+    /// \brief Cancel I/O On Socket
+    virtual void cancel();
+
+    /// \brief Close socket
+    virtual void close();
+
+
+private:
+    // Two variables to hold the socket - a socket and a pointer to it.  This
+    // handles the case where a socket is passed to the UDPSocket on
+    // construction, or where it is asked to manage its own socket.
+
+    /// Pointer to own socket
+    std::unique_ptr<boost::asio::ip::udp::socket> socket_ptr_;
+
+    // Socket
+    boost::asio::ip::udp::socket& socket_;
+
+    // True when socket is open
+    bool isopen_;
+};
+
+// Constructor - caller manages socket
+
+template <typename C>
+UDPSocket<C>::UDPSocket(boost::asio::ip::udp::socket& socket) :
+    socket_ptr_(), socket_(socket), isopen_(true)
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+UDPSocket<C>::UDPSocket(IOService& service) :
+    socket_ptr_(new boost::asio::ip::udp::socket(service.get_io_service())),
+    socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor.
+
+template <typename C>
+UDPSocket<C>::~UDPSocket()
+{
+}
+
+// Open the socket.
+
+template <typename C> void
+UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
+
+    // Ignore opens on already-open socket.  (Don't throw a failure because
+    // of uncertainties as to what precedes when using asynchronous I/O.)
+    // It also allows us a treat a passed-in socket in exactly the same way as
+    // a self-managed socket (in that we can call the open() and close() methods
+    // of this class).
+    if (!isopen_) {
+        if (endpoint->getFamily() == AF_INET) {
+            socket_.open(boost::asio::ip::udp::v4());
+        }
+        else {
+            socket_.open(boost::asio::ip::udp::v6());
+        }
+        isopen_ = true;
+
+        // Ensure it can send and receive at least 4K buffers.
+        boost::asio::ip::udp::socket::send_buffer_size snd_size;
+        socket_.get_option(snd_size);
+        if (snd_size.value() < MIN_SIZE) {
+            snd_size = MIN_SIZE;
+            socket_.set_option(snd_size);
+        }
+
+        boost::asio::ip::udp::socket::receive_buffer_size rcv_size;
+        socket_.get_option(rcv_size);
+        if (rcv_size.value() < MIN_SIZE) {
+            rcv_size = MIN_SIZE;
+            socket_.set_option(rcv_size);
+        }
+    }
+}
+
+// Send a message.  Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+UDPSocket<C>::asyncSend(const void* data, size_t length,
+                        const IOEndpoint* endpoint, C& callback)
+{
+    if (isopen_) {
+
+        // Upconvert to a UDPEndpoint.  We need to do this because although
+        // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+        // does not contain a method for getting at the underlying endpoint
+        // type - that is in the derived class and the two classes differ on
+        // return type.
+        isc_throw_assert(endpoint->getProtocol() == IPPROTO_UDP);
+        const UDPEndpoint* udp_endpoint =
+            static_cast<const UDPEndpoint*>(endpoint);
+
+        // ... and send the message.
+        socket_.async_send_to(boost::asio::buffer(data, length),
+            udp_endpoint->getASIOEndpoint(), callback);
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to send on a UDP socket that is not open");
+    }
+}
+
+// Receive a message.   Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+UDPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+                           IOEndpoint* endpoint, C& callback)
+{
+    if (isopen_) {
+
+        // Upconvert the endpoint again.
+        isc_throw_assert(endpoint->getProtocol() == IPPROTO_UDP);
+        UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
+
+        // Ensure we can write into the buffer
+        if (offset >= length) {
+            isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+                                      "UDP receive buffer");
+        }
+        void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+        // Issue the read
+        socket_.async_receive_from(boost::asio::buffer(buffer_start, length - offset),
+            udp_endpoint->getASIOEndpoint(), callback);
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to receive from a UDP socket that is not open");
+    }
+}
+
+// Receive complete.  Just copy the data across to the output buffer and
+// update arguments as appropriate.
+
+template <typename C> bool
+UDPSocket<C>::processReceivedData(const void* staging, size_t length,
+                                  size_t& cumulative, size_t& offset,
+                                  size_t& expected,
+                                  isc::util::OutputBufferPtr& outbuff)
+{
+    // Set return values to what we should expect.
+    cumulative = length;
+    expected = length;
+    offset = 0;
+
+    // Copy data across
+    outbuff->writeData(staging, length);
+
+    // ... and mark that we have everything.
+    return (true);
+}
+
+// Cancel I/O on the socket.  No-op if the socket is not open.
+
+template <typename C> void
+UDPSocket<C>::cancel() {
+    if (isopen_) {
+        socket_.cancel();
+    }
+}
+
+// Close the socket down.  Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+UDPSocket<C>::close() {
+    if (isopen_ && socket_ptr_) {
+        socket_.close();
+        isopen_ = false;
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // UDP_SOCKET_H
diff --git a/src/lib/asiolink/asiolink/unix_domain_socket.cc b/src/lib/asiolink/asiolink/unix_domain_socket.cc
new file mode 100644 (file)
index 0000000..ca9e15c
--- /dev/null
@@ -0,0 +1,371 @@
+// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/unix_domain_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <functional>
+#include <iostream>
+
+using namespace boost::asio::local;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implementation of the unix domain socket.
+class UnixDomainSocketImpl : public boost::enable_shared_from_this<UnixDomainSocketImpl> {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service to be used by the socket class.
+    UnixDomainSocketImpl(IOService& io_service)
+        : socket_(io_service.get_io_service()) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes the socket.
+    ~UnixDomainSocketImpl() {
+        close();
+    }
+
+    /// @brief Asynchronously connects to an endpoint.
+    ///
+    /// This method schedules asynchronous connect and installs the
+    /// @ref UnixDomainSocketImpl::connectHandler as a callback.
+    ///
+    /// @param endpoint Reference to an endpoint to connect to.
+    /// @param handler User supplied handler to be invoked when the connection
+    /// is established or when error is signalled.
+    void asyncConnect(const stream_protocol::endpoint& endpoint,
+                      const UnixDomainSocket::ConnectHandler& handler);
+
+    /// @brief Local handler invoked as a result of asynchronous connection.
+    ///
+    /// This is a wrapper around the user supplied callback. It ignores
+    /// EINPROGRESS errors which are observed on some operating systems as
+    /// a result of trying to connect asynchronously. This error code doesn't
+    /// necessarily indicate a problem and the subsequent attempts to read
+    /// and write to the socket will succeed. Therefore, the handler simply
+    /// overrides this error code with success status. The user supplied
+    /// handler doesn't need to deal with the EINPROGRESS error codes.
+    ///
+    /// @param remote_handler User supplied callback.
+    /// @param ec Error code returned as a result of connection.
+    void connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+                        const boost::system::error_code& ec);
+
+    /// @brief Asynchronously sends data over the socket.
+    ///
+    /// This method schedules an asynchronous send and installs the
+    /// @ref UnixDomainSocketImpl::sendHandler as a callback.
+    ///
+    /// @param data Pointer to data to be sent.
+    /// @param length Number of bytes to be sent.
+    /// @param handler Callback to be invoked when data have been sent or an
+    /// sending error is signalled.
+    void asyncSend(const void* data, const size_t length,
+                   const UnixDomainSocket::Handler& handler);
+
+    /// @brief Asynchronously sends the data over the socket.
+    ///
+    /// This method is called by the @ref asyncSend and the @ref sendHandler
+    /// if the asynchronous send has to be repeated as a result of receiving
+    /// EAGAIN or EWOULDBLOCK.
+    ///
+    /// @param buffer Buffers holding the data to be sent.
+    /// @param handler User supplied callback to be invoked when data have
+    /// been sent or sending error is signalled.
+    void doSend(const boost::asio::const_buffers_1& buffer,
+                const UnixDomainSocket::Handler& handler);
+
+
+    /// @brief Local handler invoked as a result of asynchronous send.
+    ///
+    /// This handler is invoked as a result of asynchronous send. It is a
+    /// wrapper callback around the user supplied callback. It handles
+    /// EWOULDBLOCK and EAGAIN errors by retrying an asynchronous send.
+    /// These errors are often returned on some operating systems, even
+    /// though one would expect that asynchronous operation would not
+    /// return such errors. Because these errors are handled by the
+    /// wrapper callback, the user supplied callback never receives
+    /// these errors.
+    ///
+    /// @param remote_handler User supplied callback.
+    /// @param buffer Buffers holding the data to be sent.
+    /// @param ec Error code returned as a result of sending the data.
+    /// @param length Length of the data sent.
+    void sendHandler(const UnixDomainSocket::Handler& remote_handler,
+                     const boost::asio::const_buffers_1& buffer,
+                     const boost::system::error_code& ec,
+                     size_t length);
+
+    /// @brief Asynchronously receive data over the socket.
+    ///
+    /// This method schedules asynchronous receive and installs the
+    /// @ref UnixDomainSocketImpl::receiveHandler is a callback.
+    ///
+    /// @param data Pointer to a buffer into which the data should be read.
+    /// @param length Length of the buffer.
+    /// @param handler User supplied callback invoked when data have been
+    /// received or an error is signalled.
+    void asyncReceive(void* data, const size_t length,
+                      const UnixDomainSocket::Handler& handler);
+
+    /// @brief Asynchronously receives the data over the socket.
+    ///
+    /// This method is called @ref asyncReceive and @ref receiveHandler when
+    /// EWOULDBLOCK or EAGAIN is returned.
+    ///
+    /// @param buffer A buffer into which the data should be received.
+    /// @param handler User supplied callback invoked when data have been
+    /// received on an error is signalled.
+    void doReceive(const boost::asio::mutable_buffers_1& buffer,
+                   const UnixDomainSocket::Handler& handler);
+
+    /// @brief Local handler invoked as a result of asynchronous receive.
+    ///
+    /// This handler is invoked as a result of asynchronous receive. It is a
+    /// wrapper callback around the user supplied callback. It handles
+    /// EWOULDBLOCK and EAGAIN by retrying to asynchronously receive the
+    /// data. These errors are often returned on some operating systems, even
+    /// though one would expect that asynchronous operation would not
+    /// return such errors. Because these errors are handled by the
+    /// wrapper callback, the user supplied callback never receives
+    /// these errors.
+    ///
+    /// @param remote_handler User supplied callback.
+    /// @param buffer Buffer into which the data are received.
+    /// @param ec Error code returned as a result of asynchronous receive.
+    /// @param length Size of the received data.
+    void receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+                        const boost::asio::mutable_buffers_1& buffer,
+                        const boost::system::error_code& ec,
+                        size_t length);
+
+    /// @brief Disables read and write operations on the socket.
+    void shutdown();
+
+    /// @brief Cancels asynchronous operations on the socket.
+    void cancel();
+
+    /// @brief Closes the socket.
+    void close();
+
+    /// @brief Instance of the boost asio unix domain socket.
+    stream_protocol::socket socket_;
+};
+
+void
+UnixDomainSocketImpl::asyncConnect(const stream_protocol::endpoint& endpoint,
+                                   const UnixDomainSocket::ConnectHandler& handler) {
+    auto local_handler = std::bind(&UnixDomainSocketImpl::connectHandler,
+                                   shared_from_this(),
+                                   handler, ph::_1);
+    socket_.async_connect(endpoint, local_handler);
+}
+
+void
+UnixDomainSocketImpl::connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+                                     const boost::system::error_code& ec) {
+    // It was observed on Debian and Fedora that asynchronous connect may result
+    // in EINPROGRESS error. This doesn't really indicate a problem with a
+    // connection. If we continue transmitting data over the socket it will
+    // succeed. So we suppress this error and return 'success' to the user's
+    // handler.
+    if (ec.value() == boost::asio::error::in_progress) {
+        remote_handler(boost::system::error_code());
+    } else {
+        remote_handler(ec);
+    }
+}
+
+void
+UnixDomainSocketImpl::asyncSend(const void* data, const size_t length,
+                                const UnixDomainSocket::Handler& handler) {
+    doSend(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doSend(const boost::asio::const_buffers_1& buffer,
+                             const UnixDomainSocket::Handler& handler) {
+    auto local_handler = std::bind(&UnixDomainSocketImpl::sendHandler,
+                                   shared_from_this(),
+                                   handler, buffer, ph::_1, ph::_2);
+    socket_.async_send(buffer, local_handler);
+}
+
+void
+UnixDomainSocketImpl::sendHandler(const UnixDomainSocket::Handler& remote_handler,
+                                  const boost::asio::const_buffers_1& buffer,
+                                  const boost::system::error_code& ec,
+                                  size_t length) {
+    // The asynchronous send may return EWOULDBLOCK or EAGAIN on some
+    // operating systems. In this case, we simply retry hoping that it
+    // will succeed next time. The user's callback never sees these
+    // errors.
+    if ((ec.value() == boost::asio::error::would_block) ||
+        (ec.value() == boost::asio::error::try_again)) {
+        doSend(buffer, remote_handler);
+
+    } else {
+        remote_handler(ec, length);
+    }
+}
+
+void
+UnixDomainSocketImpl::asyncReceive(void* data, const size_t length,
+                                   const UnixDomainSocket::Handler& handler) {
+    doReceive(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doReceive(const boost::asio::mutable_buffers_1& buffer,
+                                const UnixDomainSocket::Handler& handler) {
+    auto local_handler = std::bind(&UnixDomainSocketImpl::receiveHandler,
+                                   shared_from_this(),
+                                   handler, buffer, ph::_1, ph::_2);
+    socket_.async_receive(buffer, 0, local_handler);
+}
+
+void
+UnixDomainSocketImpl::receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+                                     const boost::asio::mutable_buffers_1& buffer,
+                                     const boost::system::error_code& ec,
+                                     size_t length) {
+    // The asynchronous receive may return EWOULDBLOCK or EAGAIN on some
+    // operating systems. In this case, we simply retry hoping that it
+    // will succeed next time. The user's callback never sees these
+    // errors.
+    if ((ec.value() == boost::asio::error::would_block) ||
+        (ec.value() == boost::asio::error::try_again)) {
+        doReceive(buffer, remote_handler);
+
+    } else {
+        remote_handler(ec, length);
+    }
+}
+
+void
+UnixDomainSocketImpl::shutdown() {
+    boost::system::error_code ec;
+    static_cast<void>(socket_.shutdown(stream_protocol::socket::shutdown_both, ec));
+    if (ec) {
+        isc_throw(UnixDomainSocketError, ec.message());
+    }
+}
+
+void
+UnixDomainSocketImpl::cancel() {
+    boost::system::error_code ec;
+    static_cast<void>(socket_.cancel(ec));
+    if (ec) {
+        isc_throw(UnixDomainSocketError, ec.message());
+    }
+}
+
+void
+UnixDomainSocketImpl::close() {
+    boost::system::error_code ec;
+    static_cast<void>(socket_.close(ec));
+    if (ec) {
+        isc_throw(UnixDomainSocketError, ec.message());
+    }
+}
+
+UnixDomainSocket::UnixDomainSocket(IOService& io_service)
+    : impl_(new UnixDomainSocketImpl(io_service)) {
+}
+
+int
+UnixDomainSocket::getNative() const {
+#if BOOST_VERSION < 106600
+    return (impl_->socket_.native());
+#else
+    return (impl_->socket_.native_handle());
+#endif
+}
+
+int
+UnixDomainSocket::getProtocol() const {
+    return (0);
+}
+
+void
+UnixDomainSocket::connect(const std::string& path) {
+    boost::system::error_code ec;
+    impl_->socket_.connect(stream_protocol::endpoint(path.c_str()), ec);
+    if (ec) {
+        isc_throw(UnixDomainSocketError, ec.message());
+    }
+}
+
+void
+UnixDomainSocket::asyncConnect(const std::string& path, const ConnectHandler& handler) {
+    impl_->asyncConnect(stream_protocol::endpoint(path.c_str()), handler);
+}
+
+size_t
+UnixDomainSocket::write(const void* data, size_t length) {
+    boost::system::error_code ec;
+    size_t res = boost::asio::write(impl_->socket_,
+                                    boost::asio::buffer(data, length),
+                                    boost::asio::transfer_all(),
+                                    ec);
+    if (ec) {
+        isc_throw(UnixDomainSocketError, ec.message());
+    }
+    return (res);
+}
+
+void
+UnixDomainSocket::asyncSend(const void* data, const size_t length,
+                            const Handler& handler) {
+    impl_->asyncSend(data, length, handler);
+}
+
+size_t
+UnixDomainSocket::receive(void* data, size_t length) {
+    boost::system::error_code ec;
+    size_t res = impl_->socket_.receive(boost::asio::buffer(data, length), 0, ec);
+    if (ec) {
+        isc_throw(UnixDomainSocketError, ec.message());
+    }
+    return (res);
+}
+
+void
+UnixDomainSocket::asyncReceive(void* data, const size_t length,
+                               const Handler& handler) {
+    impl_->asyncReceive(data, length, handler);
+}
+
+void
+UnixDomainSocket::shutdown() {
+    impl_->shutdown();
+}
+
+void
+UnixDomainSocket::cancel() {
+    impl_->cancel();
+}
+
+void
+UnixDomainSocket::close() {
+    impl_->close();
+}
+
+boost::asio::local::stream_protocol::socket&
+UnixDomainSocket::getASIOSocket() const {
+    return (impl_->socket_);
+}
+
+} // end of namespace asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/asiolink/unix_domain_socket.h b/src/lib/asiolink/asiolink/unix_domain_socket.h
new file mode 100644 (file)
index 0000000..cd02f41
--- /dev/null
@@ -0,0 +1,137 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNIX_DOMAIN_SOCKET_H
+#define UNIX_DOMAIN_SOCKET_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Exception thrown upon socket error.
+class UnixDomainSocketError : public Exception {
+public:
+    UnixDomainSocketError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+class UnixDomainSocketImpl;
+
+/// @brief Represents unix domain socket implemented in terms
+/// of boost asio.
+class UnixDomainSocket : public IOSocket {
+public:
+
+    /// @brief Callback type used in call to @ref UnixDomainSocket::asyncConnect.
+    typedef std::function<void(const boost::system::error_code&)> ConnectHandler;
+
+    /// @brief Callback type used in calls to @ref UnixDomainSocket::asyncSend
+    /// and @ref UnixDomainSocket::asyncReceive.
+    typedef std::function<void(const boost::system::error_code&, size_t)> Handler;
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service Reference to IOService to be used by this
+    /// class.
+    explicit UnixDomainSocket(IOService& io_service);
+
+    /// @brief Returns native socket representation.
+    virtual int getNative() const;
+
+    /// @brief Always returns 0.
+    virtual int getProtocol() const;
+
+    /// @brief Connects the socket to the specified endpoint.
+    ///
+    /// @param path Path to the unix socket to which we should connect.
+    ///
+    /// @throw UnixDomainSocketError if error occurs.
+    void connect(const std::string& path);
+
+    /// @brief Asynchronously connects the socket to the specified endpoint.
+    ///
+    /// Always returns immediately.
+    ///
+    /// @param path Path to the unix socket to which we should connect.
+    /// @param handler Callback to be invoked when connection is established or
+    /// a connection error is signalled.
+    void asyncConnect(const std::string& path, const ConnectHandler& handler);
+
+    /// @brief Writes specified amount of data to a socket.
+    ///
+    /// @param data Pointer to data to be written.
+    /// @param length Number of bytes to be written.
+    ///
+    /// @return Number of bytes written.
+    /// @throw UnixDomainSocketError if error occurs.
+    size_t write(const void* data, size_t length);
+
+    /// @brief Asynchronously sends data over the socket.
+    ///
+    /// Always returns immediately.
+    ///
+    /// @param data Pointer to data to be sent.
+    /// @param length Number of bytes to be sent.
+    /// @param handler Callback to be invoked when data have been sent or
+    /// sending error is signalled.
+    void asyncSend(const void* data, const size_t length, const Handler& handler);
+
+    /// @brief Receives data from a socket.
+    ///
+    /// @param [out] data Pointer to a location into which the read data should
+    /// be stored.
+    /// @param length Length of the buffer.
+    ///
+    /// @return Number of bytes read.
+    /// @throw UnixDomainSocketError if error occurs.
+    size_t receive(void* data, size_t length);
+
+    /// @brief Asynchronously receives data over the socket.
+    ///
+    /// Always returns immediately.
+    /// @param [out] data Pointer to a location into which the read data should
+    /// be stored.
+    /// @param length Length of the buffer.
+    /// @param handler Callback to be invoked when data have been received or an
+    /// error is signalled.
+    void asyncReceive(void* data, const size_t length, const Handler& handler);
+
+    /// @brief Disables read and write operations on the socket.
+    ///
+    /// @throw UnixDomainSocketError if an error occurs during shutdown.
+    void shutdown();
+
+    /// @brief Cancels scheduled asynchronous operations on the socket.
+    ///
+    /// @throw UnixDomainSocketError if an error occurs during cancel operation.
+    void cancel();
+
+    /// @brief Closes the socket.
+    ///
+    /// @throw UnixDomainSocketError if an error occurs during closure.
+    void close();
+
+    /// @brief Returns reference to the underlying ASIO socket.
+    ///
+    /// @return Reference to underlying ASIO socket.
+    virtual boost::asio::local::stream_protocol::socket& getASIOSocket() const;
+
+private:
+
+    /// @brief Pointer to the implementation of this class.
+    boost::shared_ptr<UnixDomainSocketImpl> impl_;
+
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_H
diff --git a/src/lib/asiolink/asiolink/unix_domain_socket_acceptor.h b/src/lib/asiolink/asiolink/unix_domain_socket_acceptor.h
new file mode 100644 (file)
index 0000000..8aa11ca
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNIX_DOMAIN_SOCKET_ACCEPTOR_H
+#define UNIX_DOMAIN_SOCKET_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/unix_domain_socket.h>
+#include <functional>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implements acceptor service for @ref UnixDomainSocket.
+///
+/// This class is used to accept new incoming connections over unix domain
+/// sockets.
+class UnixDomainSocketAcceptor : public IOAcceptor<boost::asio::local::stream_protocol,
+                                 std::function<void(const boost::system::error_code&)> > {
+public:
+
+    /// @brief Callback type used in call to @ref UnixDomainSocketAcceptor::asyncAccept.
+    typedef std::function<void(const boost::system::error_code&)> AcceptHandler;
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service Reference to the IO service.
+    explicit UnixDomainSocketAcceptor(IOService& io_service)
+        : IOAcceptor<boost::asio::local::stream_protocol,
+                     std::function<void(const boost::system::error_code&)> >(io_service) {
+    }
+
+    /// @brief Returns the transport protocol of the socket.
+    ///
+    /// @return AF_LOCAL.
+    virtual int getProtocol() const final {
+        return (AF_LOCAL);
+    }
+
+    /// @brief Asynchronously accept new connection.
+    ///
+    /// This method accepts new connection into the specified socket. When the
+    /// new connection arrives or an error occurs the specified callback function
+    /// is invoked.
+    ///
+    /// @param socket Socket into which connection should be accepted.
+    /// @param callback Callback function to be invoked when the new connection
+    /// arrives.
+    /// @tparam SocketType
+    void asyncAccept(const UnixDomainSocket& socket, const AcceptHandler& callback) {
+        asyncAcceptInternal(socket, callback);
+    }
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_ACCEPTOR_H
diff --git a/src/lib/asiolink/asiolink/unix_domain_socket_endpoint.h b/src/lib/asiolink/asiolink/unix_domain_socket_endpoint.h
new file mode 100644 (file)
index 0000000..9378a83
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNIX_DOMAIN_SOCKET_ENDPOINT_H
+#define UNIX_DOMAIN_SOCKET_ENDPOINT_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <string>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Endpoint for @ref UnixDomainSocket.
+///
+/// This is a simple class encapsulating ASIO unix domain socket endpoint.
+/// It is used to represent endpoints taking part in communication via
+/// unix domain sockets.
+class UnixDomainSocketEndpoint {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param endpoint_path Path to the socket descriptor.
+    explicit UnixDomainSocketEndpoint(const std::string& endpoint_path)
+        : endpoint_(endpoint_path) {
+    }
+
+    /// @brief Returns underlying ASIO endpoint.
+    const boost::asio::local::stream_protocol::endpoint&
+    getASIOEndpoint() const {
+        return (endpoint_);
+    }
+
+private:
+
+    /// @brief Underlying ASIO endpoint.
+    boost::asio::local::stream_protocol::endpoint endpoint_;
+
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_ENDPOINT_H
index ef6f4f952c3a1f36ef1028d3f8162d365ca321d5..8b1dce1748cc170807096666c10cdbb13623a770 100644 (file)
@@ -59,8 +59,9 @@ if HAVE_OPENSSL
 # openssl_sample_server <port>
 # openssl_sample_server <address> <port>
 
-noinst_PROGRAMS = openssl_sample_client openssl_sample_server
+#noinst_PROGRAMS = openssl_sample_client openssl_sample_server
 
+<<<<<<< HEAD
 openssl_sample_client_SOURCES = openssl_sample_client.cc
 openssl_sample_client_CPPFLAGS = $(AM_CPPFLAGS)
 openssl_sample_client_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
@@ -70,5 +71,20 @@ openssl_sample_server_SOURCES = openssl_sample_server.cc
 openssl_sample_server_CPPFLAGS = $(AM_CPPFLAGS)
 openssl_sample_server_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
 openssl_sample_server_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+=======
+#openssl_sample_client_SOURCES = openssl_sample_client.cc
+#openssl_sample_client_CPPFLAGS = $(AM_CPPFLAGS)
+#openssl_sample_client_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+#openssl_sample_client_LDADD = $(CRYPTO_LIBS)
+
+#openssl_sample_server_SOURCES = openssl_sample_server.cc
+#openssl_sample_server_CPPFLAGS = $(AM_CPPFLAGS)
+#openssl_sample_server_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+#openssl_sample_server_LDADD = $(CRYPTO_LIBS)
+>>>>>>> [#1661] Fixed build on last Fedora
 endif
 endif
+
+
+
+