summaryrefslogtreecommitdiffstats
path: root/src/seastar/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/seastar/tests
parentInitial commit. (diff)
downloadceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz
ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/seastar/tests')
-rw-r--r--src/seastar/tests/CMakeLists.txt39
-rw-r--r--src/seastar/tests/perf/CMakeLists.txt74
-rw-r--r--src/seastar/tests/perf/fair_queue_perf.cc156
-rw-r--r--src/seastar/tests/perf/fstream_perf.cc83
-rw-r--r--src/seastar/tests/perf/future_util_perf.cc77
-rw-r--r--src/seastar/tests/perf/perf-tests.md106
-rw-r--r--src/seastar/tests/perf/perf_tests.cc365
-rw-r--r--src/seastar/tests/perf/rpc_perf.cc262
-rw-r--r--src/seastar/tests/unit/CMakeLists.txt560
-rw-r--r--src/seastar/tests/unit/abort_source_test.cc86
-rw-r--r--src/seastar/tests/unit/alien_test.cc116
-rw-r--r--src/seastar/tests/unit/alloc_test.cc162
-rw-r--r--src/seastar/tests/unit/allocator_test.cc220
-rw-r--r--src/seastar/tests/unit/cert.cfg.in23
-rw-r--r--src/seastar/tests/unit/checked_ptr_test.cc125
-rw-r--r--src/seastar/tests/unit/chunked_fifo_test.cc358
-rw-r--r--src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc173
-rw-r--r--src/seastar/tests/unit/circular_buffer_test.cc109
-rw-r--r--src/seastar/tests/unit/connect_test.cc74
-rw-r--r--src/seastar/tests/unit/coroutines_test.cc153
-rw-r--r--src/seastar/tests/unit/defer_test.cc76
-rw-r--r--src/seastar/tests/unit/deleter_test.cc79
-rw-r--r--src/seastar/tests/unit/directory_test.cc86
-rw-r--r--src/seastar/tests/unit/distributed_test.cc318
-rw-r--r--src/seastar/tests/unit/dns_test.cc131
-rw-r--r--src/seastar/tests/unit/execution_stage_test.cc334
-rw-r--r--src/seastar/tests/unit/expiring_fifo_test.cc190
-rw-r--r--src/seastar/tests/unit/fair_queue_test.cc413
-rw-r--r--src/seastar/tests/unit/file_io_test.cc756
-rw-r--r--src/seastar/tests/unit/file_utils_test.cc287
-rw-r--r--src/seastar/tests/unit/foreign_ptr_test.cc132
-rw-r--r--src/seastar/tests/unit/fsnotifier_test.cc227
-rw-r--r--src/seastar/tests/unit/fstream_test.cc538
-rw-r--r--src/seastar/tests/unit/futures_test.cc1617
-rw-r--r--src/seastar/tests/unit/httpd_test.cc889
-rw-r--r--src/seastar/tests/unit/ipv6_test.cc105
-rw-r--r--src/seastar/tests/unit/json_formatter_test.cc52
-rw-r--r--src/seastar/tests/unit/locking_test.cc223
-rw-r--r--src/seastar/tests/unit/log_buf_test.cc55
-rw-r--r--src/seastar/tests/unit/loopback_socket.hh278
-rw-r--r--src/seastar/tests/unit/lowres_clock_test.cc118
-rw-r--r--src/seastar/tests/unit/metrics_test.cc170
-rw-r--r--src/seastar/tests/unit/mkcert.gmk94
-rw-r--r--src/seastar/tests/unit/mock_file.hh113
-rw-r--r--src/seastar/tests/unit/net_config_test.cc127
-rw-r--r--src/seastar/tests/unit/network_interface_test.cc88
-rw-r--r--src/seastar/tests/unit/noncopyable_function_test.cc87
-rw-r--r--src/seastar/tests/unit/output_stream_test.cc159
-rw-r--r--src/seastar/tests/unit/packet_test.cc121
-rw-r--r--src/seastar/tests/unit/program_options_test.cc63
-rw-r--r--src/seastar/tests/unit/queue_test.cc70
-rw-r--r--src/seastar/tests/unit/request_parser_test.cc74
-rw-r--r--src/seastar/tests/unit/rpc_test.cc1199
-rw-r--r--src/seastar/tests/unit/scheduling_group_test.cc251
-rw-r--r--src/seastar/tests/unit/semaphore_test.cc310
-rw-r--r--src/seastar/tests/unit/sharded_test.cc110
-rw-r--r--src/seastar/tests/unit/shared_ptr_test.cc204
-rwxr-xr-xsrc/seastar/tests/unit/signal_test.cc48
-rw-r--r--src/seastar/tests/unit/simple_stream_test.cc99
-rw-r--r--src/seastar/tests/unit/slab_test.cc127
-rw-r--r--src/seastar/tests/unit/smp_test.cc81
-rw-r--r--src/seastar/tests/unit/socket_test.cc82
-rw-r--r--src/seastar/tests/unit/sstring_test.cc186
-rw-r--r--src/seastar/tests/unit/stall_detector_test.cc108
-rw-r--r--src/seastar/tests/unit/thread_context_switch_test.cc96
-rw-r--r--src/seastar/tests/unit/thread_test.cc264
-rw-r--r--src/seastar/tests/unit/timer_test.cc141
-rw-r--r--src/seastar/tests/unit/tls-ca-bundle.pem4195
-rw-r--r--src/seastar/tests/unit/tls_test.cc845
-rw-r--r--src/seastar/tests/unit/tmpdir.hh49
-rw-r--r--src/seastar/tests/unit/tuple_utils_test.cc99
-rw-r--r--src/seastar/tests/unit/uname_test.cc76
-rw-r--r--src/seastar/tests/unit/unix_domain_test.cc234
-rw-r--r--src/seastar/tests/unit/unwind_test.cc70
-rw-r--r--src/seastar/tests/unit/weak_ptr_test.cc136
75 files changed, 20401 insertions, 0 deletions
diff --git a/src/seastar/tests/CMakeLists.txt b/src/seastar/tests/CMakeLists.txt
new file mode 100644
index 000000000..16c9b2026
--- /dev/null
+++ b/src/seastar/tests/CMakeLists.txt
@@ -0,0 +1,39 @@
+#
+# This file is open source software, licensed to you under the terms
+# of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+# distributed with this work for additional information regarding copyright
+# ownership. You may not use this file except in compliance with the License.
+#
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# Copyright (C) 2018 Scylladb, Ltd.
+#
+
+function (seastar_jenkins_arguments test_name var)
+ string (TOLOWER ${CMAKE_BUILD_TYPE} mode)
+ set (output_file "${CMAKE_SOURCE_DIR}/${Seastar_JENKINS}.${mode}.${test_name}_test.boost.xml")
+
+ set (${var}
+ --output_format=XML
+ --log_level=all
+ --report_level=no
+ --log_sink=${output_file}
+ PARENT_SCOPE)
+endfunction ()
+
+# Performance tests.
+add_subdirectory (perf)
+
+# Unit tests.
+add_subdirectory (unit)
diff --git a/src/seastar/tests/perf/CMakeLists.txt b/src/seastar/tests/perf/CMakeLists.txt
new file mode 100644
index 000000000..07db0de5c
--- /dev/null
+++ b/src/seastar/tests/perf/CMakeLists.txt
@@ -0,0 +1,74 @@
+#
+# This file is open source software, licensed to you under the terms
+# of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+# distributed with this work for additional information regarding copyright
+# ownership. You may not use this file except in compliance with the License.
+#
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# Copyright (C) 2018 Scylladb, Ltd.
+#
+
+# Logical target for all perf tests.
+add_custom_target (perf_tests)
+
+macro (seastar_add_test name)
+ set (args ${ARGN})
+
+ cmake_parse_arguments (
+ parsed_args
+ "NO_SEASTAR_PERF_TESTING_LIBRARY"
+ ""
+ "SOURCES"
+ ${args})
+
+ set (target test_perf_${name})
+ add_executable (${target} ${parsed_args_SOURCES})
+
+ if (parsed_args_NO_SEASTAR_PERF_TESTING_LIBRARY)
+ set (libraries seastar_private)
+ else ()
+ set (libraries
+ seastar_private
+ seastar_perf_testing)
+ endif ()
+
+ target_link_libraries (${target}
+ PRIVATE ${libraries})
+
+ target_include_directories (${target}
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${Seastar_SOURCE_DIR}/src)
+
+ set_target_properties (${target}
+ PROPERTIES
+ OUTPUT_NAME ${name}_perf)
+
+ add_dependencies (perf_tests ${target})
+ set (${name}_test ${target})
+endmacro ()
+
+seastar_add_test (fstream
+ SOURCES fstream_perf.cc
+ NO_SEASTAR_PERF_TESTING_LIBRARY)
+
+seastar_add_test (fair_queue
+ SOURCES fair_queue_perf.cc)
+
+seastar_add_test (future_util
+ SOURCES future_util_perf.cc)
+
+seastar_add_test (rpc
+ SOURCES rpc_perf.cc)
diff --git a/src/seastar/tests/perf/fair_queue_perf.cc b/src/seastar/tests/perf/fair_queue_perf.cc
new file mode 100644
index 000000000..db692926c
--- /dev/null
+++ b/src/seastar/tests/perf/fair_queue_perf.cc
@@ -0,0 +1,156 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2020 ScyllaDB Ltd.
+ */
+
+
+#include <seastar/testing/perf_tests.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/fair_queue.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/when_all.hh>
+#include <boost/range/irange.hpp>
+
+struct local_fq_and_class {
+ seastar::fair_queue fq;
+ seastar::priority_class_ptr pclass;
+ unsigned executed = 0;
+
+ local_fq_and_class(seastar::fair_queue::config cfg)
+ : fq(std::move(cfg))
+ , pclass(fq.register_priority_class(1))
+ {}
+
+ ~local_fq_and_class() {
+ fq.unregister_priority_class(pclass);
+ }
+};
+
+struct perf_fair_queue {
+
+ static constexpr unsigned requests_to_dispatch = 1000;
+
+ seastar::fair_queue::config cfg;
+ seastar::sharded<local_fq_and_class> local_fq;
+
+ seastar::fair_queue shared_fq;
+ std::vector<priority_class_ptr> shared_pclass;
+
+ uint64_t shared_executed = 0;
+ uint64_t shared_acked = 0;
+
+ perf_fair_queue()
+ : cfg({ std::chrono::milliseconds(100), 1, 1 })
+ , shared_fq(cfg)
+ {
+ local_fq.start(cfg).get();
+ for (unsigned i = 0; i < smp::count; ++i) {
+ shared_pclass.push_back(shared_fq.register_priority_class(1));
+ }
+ }
+
+ ~perf_fair_queue() {
+ local_fq.stop().get();
+ for (auto& pc : shared_pclass) {
+ shared_fq.unregister_priority_class(pc);
+ }
+ }
+};
+
+PERF_TEST_F(perf_fair_queue, contended_local)
+{
+ auto invokers = local_fq.invoke_on_all([] (local_fq_and_class& local) {
+ return parallel_for_each(boost::irange(0u, requests_to_dispatch), [&local] (unsigned dummy) {
+ local.fq.queue(local.pclass, seastar::fair_queue_ticket{1, 1}, [&local] {
+ local.executed++;
+ local.fq.notify_requests_finished(seastar::fair_queue_ticket{1, 1});
+ });
+ return make_ready_future<>();
+ });
+ });
+
+ auto collectors = local_fq.invoke_on_all([] (local_fq_and_class& local) {
+ // Zeroing this counter must be here, otherwise should the collectors win the
+ // execution order in when_all_succeed(), the do_until()'s stopping callback
+ // would return true immediately and the queue would not be dispatched.
+ //
+ // At the same time, although this counter is incremented by the lambda from
+ // invokers, it's not called until the fq.dispatch_requests() is, so there's no
+ // opposite problem if zeroing it here.
+ local.executed = 0;
+
+ return do_until([&local] { return local.executed == requests_to_dispatch; }, [&local] {
+ local.fq.dispatch_requests();
+ return make_ready_future<>();
+ });
+ });
+
+ return when_all_succeed(std::move(invokers), std::move(collectors)).discard_result();
+}
+
+PERF_TEST_F(perf_fair_queue, contended_shared)
+{
+ shared_acked = 0;
+ shared_executed = 0;
+ auto invokers = local_fq.invoke_on_all([this, coordinator = this_shard_id()] (local_fq_and_class& dummy) {
+ return parallel_for_each(boost::irange(0u, requests_to_dispatch), [this, coordinator] (unsigned dummy) {
+ return smp::submit_to(coordinator, [this] {
+ shared_fq.queue(shared_pclass[this_shard_id()], seastar::fair_queue_ticket{1, 1}, [this] {
+ shared_executed++;
+ });
+ return make_ready_future<>();
+ });
+ });
+ });
+
+ auto collectors = do_until([this] { return shared_acked == requests_to_dispatch * smp::count; }, [this] {
+ shared_fq.dispatch_requests();
+ uint32_t pending_ack = shared_executed - shared_acked;
+ shared_acked = shared_executed;
+ shared_fq.notify_requests_finished(seastar::fair_queue_ticket{pending_ack, pending_ack}, pending_ack);
+ return make_ready_future<>();
+ });
+ return when_all_succeed(std::move(invokers), std::move(collectors)).discard_result();
+}
+PERF_TEST_F(perf_fair_queue, contended_shared_amortized)
+{
+ shared_acked = 0;
+ shared_executed = 0;
+ auto invokers = local_fq.invoke_on_all([this, coordinator = this_shard_id()] (local_fq_and_class& dummy) {
+ return smp::submit_to(coordinator, [this] {
+ return parallel_for_each(boost::irange(0u, requests_to_dispatch), [this] (unsigned dummy) {
+ shared_fq.queue(shared_pclass[this_shard_id()], seastar::fair_queue_ticket{1, 1}, [this] {
+ shared_executed++;
+ });
+ return make_ready_future<>();
+ });
+ });
+ });
+
+ auto collectors = do_until([this] { return shared_acked == requests_to_dispatch * smp::count; }, [this] {
+ shared_fq.dispatch_requests();
+ uint32_t pending_ack = shared_executed - shared_acked;
+ shared_acked = shared_executed;
+ shared_fq.notify_requests_finished(seastar::fair_queue_ticket{pending_ack, pending_ack}, pending_ack);
+ return make_ready_future<>();
+ });
+ return when_all_succeed(std::move(invokers), std::move(collectors)).discard_result();
+}
diff --git a/src/seastar/tests/perf/fstream_perf.cc b/src/seastar/tests/perf/fstream_perf.cc
new file mode 100644
index 000000000..e2e80baaf
--- /dev/null
+++ b/src/seastar/tests/perf/fstream_perf.cc
@@ -0,0 +1,83 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#include <seastar/core/fstream.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/core/file.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <fmt/printf.h>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+int main(int ac, char** av) {
+ app_template at;
+ namespace bpo = boost::program_options;
+ at.add_options()
+ ("concurrency", bpo::value<unsigned>()->default_value(1), "Write operations to issue in parallel")
+ ("buffer-size", bpo::value<size_t>()->default_value(4096), "Write buffer size")
+ ("total-ops", bpo::value<unsigned>()->default_value(100000), "Total write operations to issue")
+ ("sloppy-size", bpo::value<bool>()->default_value(false), "Enable the sloppy-size optimization")
+ ;
+ return at.run(ac, av, [&at] {
+ auto concurrency = at.configuration()["concurrency"].as<unsigned>();
+ auto buffer_size = at.configuration()["buffer-size"].as<size_t>();
+ auto total_ops = at.configuration()["total-ops"].as<unsigned>();
+ auto sloppy_size = at.configuration()["sloppy-size"].as<bool>();
+ file_open_options foo;
+ foo.sloppy_size = sloppy_size;
+ return open_file_dma(
+ "testfile.tmp", open_flags::wo | open_flags::create | open_flags::exclusive,
+ foo).then([=] (file f) {
+ file_output_stream_options foso;
+ foso.buffer_size = buffer_size;
+ foso.preallocation_size = 32 << 20;
+ foso.write_behind = concurrency;
+ return api_v3::and_newer::make_file_output_stream(f, foso).then([=] (output_stream<char>&& os) {
+ return do_with(std::move(os), std::move(f), unsigned(0), [=] (output_stream<char>& os, file& f, unsigned& completed) {
+ auto start = std::chrono::steady_clock::now();
+ return repeat([=, &os, &completed] {
+ if (completed == total_ops) {
+ return make_ready_future<stop_iteration>(stop_iteration::yes);
+ }
+ char buf[buffer_size];
+ memset(buf, 0, buffer_size);
+ return os.write(buf, buffer_size).then([&completed] {
+ ++completed;
+ return stop_iteration::no;
+ });
+ }).then([=, &os] {
+ auto end = std::chrono::steady_clock::now();
+ using fseconds = std::chrono::duration<float, std::ratio<1, 1>>;
+ auto iops = total_ops / std::chrono::duration_cast<fseconds>(end - start).count();
+ fmt::print("{:10} {:10} {:10} {:12}\n", "bufsize", "ops", "iodepth", "IOPS");
+ fmt::print("{:10d} {:10d} {:10d} {:12.0f}\n", buffer_size, total_ops, concurrency, iops);
+ return os.flush();
+ }).then([&os] {
+ return os.close();
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/src/seastar/tests/perf/future_util_perf.cc b/src/seastar/tests/perf/future_util_perf.cc
new file mode 100644
index 000000000..a439628ad
--- /dev/null
+++ b/src/seastar/tests/perf/future_util_perf.cc
@@ -0,0 +1,77 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 ScyllaDB Ltd.
+ */
+
+#include <boost/range.hpp>
+#include <boost/range/irange.hpp>
+
+#include <seastar/testing/perf_tests.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/later.hh>
+
+struct parallel_for_each {
+ std::vector<int> empty_range;
+ std::vector<int> range;
+ int value;
+
+ parallel_for_each()
+ : empty_range()
+ , range(boost::copy_range<std::vector<int>>(boost::irange(1, 100)))
+ { }
+};
+
+PERF_TEST_F(parallel_for_each, empty)
+{
+ return seastar::parallel_for_each(empty_range, [] (int) -> future<> {
+ abort();
+ });
+}
+
+[[gnu::noinline]]
+future<> immediate(int v, int& vs)
+{
+ vs += v;
+ return make_ready_future<>();
+}
+
+PERF_TEST_F(parallel_for_each, immediate)
+{
+ return seastar::parallel_for_each(range, [this] (int v) {
+ return immediate(v, value);
+ }).then([this] {
+ perf_tests::do_not_optimize(value);
+ });
+}
+
+[[gnu::noinline]]
+future<> suspend(int v, int& vs)
+{
+ vs += v;
+ return later();
+}
+
+PERF_TEST_F(parallel_for_each, suspend)
+{
+ return seastar::parallel_for_each(range, [this] (int v) {
+ return suspend(v, value);
+ }).then([this] {
+ perf_tests::do_not_optimize(value);
+ });
+}
diff --git a/src/seastar/tests/perf/perf-tests.md b/src/seastar/tests/perf/perf-tests.md
new file mode 100644
index 000000000..c42e7be4c
--- /dev/null
+++ b/src/seastar/tests/perf/perf-tests.md
@@ -0,0 +1,106 @@
+# perf-tests
+
+`perf-tests` is a simple microbenchmarking framework. Its main purpose is to allow monitoring the impact that code changes have on performance.
+
+## Theory of operation
+
+The framework performs each test in several runs. During a run the microbenchmark code is executed in a loop and the average time of an iteration is computed. The shown results are median, median absolute deviation, maximum and minimum value of all the runs.
+
+```
+single run iterations: 0
+single run duration: 1.000s
+number of runs: 5
+
+test iterations median mad min max
+combined.one_row 745336 691.218ns 0.175ns 689.073ns 696.476ns
+combined.single_active 7871 85.271us 76.185ns 85.145us 108.316us
+```
+
+`perf-tests` allows limiting the number of iterations or the duration of each run. In the latter case there is an additional dry run used to estimate how many iterations can be run in the specified time. The measured runs are limited by that number of iterations. This means that there is no overhead caused by timers and that each run consists of the same number of iterations.
+
+### Flags
+
+* `-i <n>` or `--iterations <n>` – limits the number of iterations in each run to no more than `n` (0 for unlimited)
+* `-d <t>` or `--duration <t>` – limits the duration of each run to no more than `t` seconds (0 for unlimited)
+* `-r <n>` or `--runs <n>` – the number of runs of each test to execute
+* `-t <regexs>` or `--tests <regexs>` – executes only tests which names match any regular expression in a comma-separated list `regexs`
+* `--list` – lists all available tests
+
+## Example usage
+
+### Simple test
+
+Performance tests are defined in a similar manner to unit tests. Macro `PERF_TEST(test_group, test_case)` allows specifying the name of the test and the group it belongs to. Microbenchmark can either return nothing or a future.
+
+Compiler may attempt to optimise too much of the test logic. A way of preventing this is passing the final result of all computations to a function `perf_tests::do_not_optimize()`. That function should introduce little to none overhead, but forces the compiler to actually compute the value.
+
+```c++
+PERF_TEST(example, simple1)
+{
+ auto v = compute_value();
+ perf_tests::do_not_optimize(v);
+}
+
+PERF_TEST(example, simple2)
+{
+ return compute_different_value().then([] (auto v) {
+ perf_tests::do_not_optimize(v);
+ });
+}
+```
+
+### Fixtures
+
+As it is in case of unit tests, performance tests may benefit from using a fixture that would set up a proper environment. Such tests should use macro `PERF_TEST_F(test_group, test_case)`. The test itself will be a member function of a class derivative of `test_group`.
+
+The constructor and destructor of a fixture are executed in a context of Seastar thread, but the actual test logic is not. The same instance of a fixture will be used for multiple iterations of the test.
+
+```c++
+class example {
+protected:
+ data_set _ds1;
+ data_set _ds2;
+private:
+ static data_set perpare_data_set();
+public:
+ example()
+ : _ds1(prepare_data_set())
+ , _ds2(prepare_data_set())
+ { }
+};
+
+PERF_TEST_F(example, fixture1)
+{
+ auto r = do_something_with(_ds1);
+ perf_tests::do_not_optimize(r);
+}
+
+PERF_TEST_F(example, fixture2)
+{
+ auto r = do_something_with(_ds1, _ds2);
+ perf_tests::do_not_optimize(r);
+}
+```
+
+### Custom time measurement
+
+Even with fixtures it may be necessary to do some costly initialisation during each iteration. Its impact can be reduced by specifying the exact part of the test that should be measured using functions `perf_tests::start_measuring_time()` and `perf_tests::stop_measuring_time()`.
+
+```c++
+PERF_TEST(example, custom_time_measurement2)
+{
+ auto data = prepare_data();
+ perf_tests::start_measuring_time();
+ do_something(std::move(data));
+ perf_tests::stop_measuring_time();
+}
+
+PERF_TEST(example, custom_time_measurement2)
+{
+ auto data = prepare_data();
+ perf_tests::start_measuring_time();
+ return do_something_else(std::move(data)).finally([] {
+ perf_tests::stop_measuring_time();
+ });
+}
+```
diff --git a/src/seastar/tests/perf/perf_tests.cc b/src/seastar/tests/perf/perf_tests.cc
new file mode 100644
index 000000000..45e367804
--- /dev/null
+++ b/src/seastar/tests/perf/perf_tests.cc
@@ -0,0 +1,365 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 ScyllaDB Ltd.
+ */
+
+#include <seastar/testing/perf_tests.hh>
+
+#include <fstream>
+#include <regex>
+
+#include <boost/range.hpp>
+#include <boost/range/adaptors.hpp>
+#include <boost/range/algorithm.hpp>
+
+#include <fmt/ostream.h>
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/json/formatter.hh>
+#include <seastar/util/later.hh>
+
+#include <signal.h>
+
+namespace perf_tests {
+namespace internal {
+
+namespace {
+
+// We need to use signal-based timer instead of seastar ones so that
+// tests that do not suspend can be interrupted.
+// This causes no overhead though since the timer is used only in a dry run.
+class signal_timer {
+ std::function<void()> _fn;
+ timer_t _timer;
+public:
+ explicit signal_timer(std::function<void()> fn) : _fn(fn) {
+ sigevent se{};
+ se.sigev_notify = SIGEV_SIGNAL;
+ se.sigev_signo = SIGALRM;
+ se.sigev_value.sival_ptr = this;
+ auto ret = timer_create(CLOCK_MONOTONIC, &se, &_timer);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+ }
+
+ ~signal_timer() {
+ timer_delete(_timer);
+ }
+
+ void arm(std::chrono::steady_clock::duration dt) {
+ time_t sec = std::chrono::duration_cast<std::chrono::seconds>(dt).count();
+ auto nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(dt).count();
+ nsec -= std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(sec)).count();
+
+ itimerspec ts{};
+ ts.it_value.tv_sec = sec;
+ ts.it_value.tv_nsec = nsec;
+ auto ret = timer_settime(_timer, 0, &ts, nullptr);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+ }
+
+ void cancel() {
+ itimerspec ts{};
+ auto ret = timer_settime(_timer, 0, &ts, nullptr);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+ }
+public:
+ static void init() {
+ struct sigaction sa{};
+ sa.sa_sigaction = &signal_timer::signal_handler;
+ sa.sa_flags = SA_SIGINFO;
+ auto ret = sigaction(SIGALRM, &sa, nullptr);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+ }
+private:
+ static void signal_handler(int, siginfo_t* si, void*) {
+ auto t = static_cast<signal_timer*>(si->si_value.sival_ptr);
+ t->_fn();
+ }
+};
+
+}
+
+time_measurement measure_time;
+
+struct config;
+struct result;
+
+struct result_printer {
+ virtual ~result_printer() = default;
+
+ virtual void print_configuration(const config&) = 0;
+ virtual void print_result(const result&) = 0;
+};
+
+struct config {
+ uint64_t single_run_iterations;
+ std::chrono::nanoseconds single_run_duration;
+ unsigned number_of_runs;
+ std::vector<std::unique_ptr<result_printer>> printers;
+};
+
+struct result {
+ sstring test_name;
+
+ uint64_t total_iterations;
+ unsigned runs;
+
+ double median;
+ double mad;
+ double min;
+ double max;
+};
+
+namespace {
+
+struct duration {
+ double value;
+};
+
+static inline std::ostream& operator<<(std::ostream& os, duration d)
+{
+ auto value = d.value;
+ if (value < 1'000) {
+ os << fmt::format("{:.3f}ns", value);
+ } else if (value < 1'000'000) {
+ // fmt hasn't discovered unicode yet so we are stuck with uicroseconds
+ // See: https://github.com/fmtlib/fmt/issues/628
+ os << fmt::format("{:.3f}us", value / 1'000);
+ } else if (value < 1'000'000'000) {
+ os << fmt::format("{:.3f}ms", value / 1'000'000);
+ } else {
+ os << fmt::format("{:.3f}s", value / 1'000'000'000);
+ }
+ return os;
+}
+
+}
+
+static constexpr auto format_string = "{:<40} {:>11} {:>11} {:>11} {:>11} {:>11}\n";
+
+struct stdout_printer final : result_printer {
+ virtual void print_configuration(const config& c) override {
+ fmt::print("{:<25} {}\n{:<25} {}\n{:<25} {}\n\n",
+ "single run iterations:", c.single_run_iterations,
+ "single run duration:", duration { double(c.single_run_duration.count()) },
+ "number of runs:", c.number_of_runs);
+ fmt::print(format_string, "test", "iterations", "median", "mad", "min", "max");
+ }
+
+ virtual void print_result(const result& r) override {
+ fmt::print(format_string, r.test_name, r.total_iterations / r.runs, duration { r.median },
+ duration { r.mad }, duration { r.min }, duration { r.max });
+ }
+};
+
+class json_printer final : public result_printer {
+ std::string _output_file;
+ std::unordered_map<std::string,
+ std::unordered_map<std::string,
+ std::unordered_map<std::string, double>>> _root;
+public:
+ explicit json_printer(const std::string& file) : _output_file(file) { }
+
+ ~json_printer() {
+ std::ofstream out(_output_file);
+ out << json::formatter::to_json(_root);
+ }
+
+ virtual void print_configuration(const config&) override { }
+
+ virtual void print_result(const result& r) override {
+ auto& result = _root["results"][r.test_name];
+ result["runs"] = r.runs;
+ result["total_iterations"] = r.total_iterations;
+ result["median"] = r.median;
+ result["mad"] = r.mad;
+ result["min"] = r.min;
+ result["max"] = r.max;
+ }
+};
+
+void performance_test::do_run(const config& conf)
+{
+ _max_single_run_iterations = conf.single_run_iterations;
+ if (!_max_single_run_iterations) {
+ _max_single_run_iterations = std::numeric_limits<uint64_t>::max();
+ }
+
+ signal_timer tmr([this] {
+ _max_single_run_iterations.store(0, std::memory_order_relaxed);
+ });
+
+ // dry run, estimate the number of iterations
+ if (conf.single_run_duration.count()) {
+ // switch out of seastar thread
+ later().then([&] {
+ tmr.arm(conf.single_run_duration);
+ return do_single_run().finally([&] {
+ tmr.cancel();
+ _max_single_run_iterations = _single_run_iterations;
+ });
+ }).get();
+ }
+
+ auto results = std::vector<double>(conf.number_of_runs);
+ uint64_t total_iterations = 0;
+ for (auto i = 0u; i < conf.number_of_runs; i++) {
+ // switch out of seastar thread
+ later().then([&] {
+ _single_run_iterations = 0;
+ return do_single_run().then([&] (clock_type::duration dt) {
+ double ns = std::chrono::duration_cast<std::chrono::nanoseconds>(dt).count();
+ results[i] = ns / _single_run_iterations;
+
+ total_iterations += _single_run_iterations;
+ });
+ }).get();
+ }
+
+ result r{};
+ r.test_name = name();
+ r.total_iterations = total_iterations;
+ r.runs = conf.number_of_runs;
+
+ auto mid = conf.number_of_runs / 2;
+
+ boost::range::sort(results);
+ r.median = results[mid];
+
+ auto diffs = boost::copy_range<std::vector<double>>(
+ results | boost::adaptors::transformed([&] (double x) { return fabs(x - r.median); })
+ );
+ boost::range::sort(diffs);
+ r.mad = diffs[mid];
+
+ r.min = results[0];
+ r.max = results[results.size() - 1];
+
+ for (auto& rp : conf.printers) {
+ rp->print_result(r);
+ }
+}
+
+void performance_test::run(const config& conf)
+{
+ set_up();
+ try {
+ do_run(conf);
+ } catch (...) {
+ tear_down();
+ throw;
+ }
+ tear_down();
+}
+
+std::vector<std::unique_ptr<performance_test>>& all_tests()
+{
+ static std::vector<std::unique_ptr<performance_test>> tests;
+ return tests;
+}
+
+void performance_test::register_test(std::unique_ptr<performance_test> test)
+{
+ all_tests().emplace_back(std::move(test));
+}
+
+void run_all(const std::vector<std::string>& tests, const config& conf)
+{
+ auto can_run = [tests = boost::copy_range<std::vector<std::regex>>(tests)] (auto&& test) {
+ auto it = boost::range::find_if(tests, [&test] (const std::regex& regex) {
+ return std::regex_match(test->name(), regex);
+ });
+ return tests.empty() || it != tests.end();
+ };
+
+ for (auto& rp : conf.printers) {
+ rp->print_configuration(conf);
+ }
+ for (auto&& test : all_tests() | boost::adaptors::filtered(std::move(can_run))) {
+ test->run(conf);
+ }
+}
+
+}
+}
+
+int main(int ac, char** av)
+{
+ using namespace perf_tests::internal;
+ namespace bpo = boost::program_options;
+
+ app_template app;
+ app.add_options()
+ ("iterations,i", bpo::value<size_t>()->default_value(0),
+ "number of iterations in a single run")
+ ("duration,d", bpo::value<double>()->default_value(1),
+ "duration of a single run in seconds")
+ ("runs,r", bpo::value<size_t>()->default_value(5), "number of runs")
+ ("test,t", bpo::value<std::vector<std::string>>(), "tests to execute")
+ ("no-stdout", "do not print to stdout")
+ ("json-output", bpo::value<std::string>(), "output json file")
+ ("list", "list available tests")
+ ;
+
+ return app.run(ac, av, [&] {
+ return async([&] {
+ signal_timer::init();
+
+ config conf;
+ conf.single_run_iterations = app.configuration()["iterations"].as<size_t>();
+ auto dur = std::chrono::duration<double>(app.configuration()["duration"].as<double>());
+ conf.single_run_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(dur);
+ conf.number_of_runs = app.configuration()["runs"].as<size_t>();
+
+ std::vector<std::string> tests_to_run;
+ if (app.configuration().count("test")) {
+ tests_to_run = app.configuration()["test"].as<std::vector<std::string>>();
+ }
+
+ if (app.configuration().count("list")) {
+ fmt::print("available tests:\n");
+ for (auto&& t : all_tests()) {
+ fmt::print("\t{}\n", t->name());
+ }
+ return;
+ }
+
+ if (!app.configuration().count("no-stdout")) {
+ conf.printers.emplace_back(std::make_unique<stdout_printer>());
+ }
+
+ if (app.configuration().count("json-output")) {
+ conf.printers.emplace_back(std::make_unique<json_printer>(
+ app.configuration()["json-output"].as<std::string>()
+ ));
+ }
+
+ run_all(tests_to_run, conf);
+ });
+ });
+}
diff --git a/src/seastar/tests/perf/rpc_perf.cc b/src/seastar/tests/perf/rpc_perf.cc
new file mode 100644
index 000000000..1f646548b
--- /dev/null
+++ b/src/seastar/tests/perf/rpc_perf.cc
@@ -0,0 +1,262 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Scylladb, Ltd.
+ */
+
+#include <random>
+
+#include <seastar/rpc/lz4_compressor.hh>
+#include <seastar/rpc/lz4_fragmented_compressor.hh>
+
+#include <seastar/testing/perf_tests.hh>
+
+template<typename Compressor>
+struct compression {
+ static constexpr size_t small_buffer_size = 128;
+ static constexpr size_t large_buffer_size = 16 * 1024 * 1024;
+
+private:
+ Compressor _compressor;
+
+ seastar::temporary_buffer<char> _small_buffer_random;
+ seastar::temporary_buffer<char> _small_buffer_zeroes;
+
+ std::vector<seastar::temporary_buffer<char>> _large_buffer_random;
+ std::vector<seastar::temporary_buffer<char>> _large_buffer_zeroes;
+
+ std::vector<seastar::temporary_buffer<char>> _small_compressed_buffer_random;
+ std::vector<seastar::temporary_buffer<char>> _small_compressed_buffer_zeroes;
+
+ std::vector<seastar::temporary_buffer<char>> _large_compressed_buffer_random;
+ std::vector<seastar::temporary_buffer<char>> _large_compressed_buffer_zeroes;
+
+private:
+ static seastar::rpc::rcv_buf get_rcv_buf(std::vector<temporary_buffer<char>>& input) {
+ if (input.size() == 1) {
+ return seastar::rpc::rcv_buf(input.front().share());
+ }
+ auto bufs = std::vector<temporary_buffer<char>>{};
+ auto total_size = std::accumulate(input.begin(), input.end(), size_t(0),
+ [&] (size_t n, temporary_buffer<char>& buf) {
+ bufs.emplace_back(buf.share());
+ return n + buf.size();
+ });
+ return seastar::rpc::rcv_buf(std::move(bufs), total_size);
+ }
+
+ static seastar::rpc::snd_buf get_snd_buf(std::vector<temporary_buffer<char>>& input) {
+ auto bufs = std::vector<temporary_buffer<char>>{};
+ auto total_size = std::accumulate(input.begin(), input.end(), size_t(0),
+ [&] (size_t n, temporary_buffer<char>& buf) {
+ bufs.emplace_back(buf.share());
+ return n + buf.size();
+ });
+ return seastar::rpc::snd_buf(std::move(bufs), total_size);
+ }
+ static seastar::rpc::snd_buf get_snd_buf(temporary_buffer<char>& input) {
+ return seastar::rpc::snd_buf(input.share());
+ }
+
+public:
+ compression()
+ : _small_buffer_random(seastar::temporary_buffer<char>(small_buffer_size))
+ , _small_buffer_zeroes(seastar::temporary_buffer<char>(small_buffer_size))
+ {
+ auto eng = std::default_random_engine{std::random_device{}()};
+ auto dist = std::uniform_int_distribution<char>();
+
+ std::generate_n(_small_buffer_random.get_write(), small_buffer_size, [&] { return dist(eng); });
+ for (auto i = 0u; i < large_buffer_size / seastar::rpc::snd_buf::chunk_size; i++) {
+ _large_buffer_random.emplace_back(seastar::rpc::snd_buf::chunk_size);
+ std::generate_n(_large_buffer_random.back().get_write(), seastar::rpc::snd_buf::chunk_size, [&] { return dist(eng); });
+ _large_buffer_zeroes.emplace_back(seastar::rpc::snd_buf::chunk_size);
+ std::fill_n(_large_buffer_zeroes.back().get_write(), seastar::rpc::snd_buf::chunk_size, 0);
+ }
+
+ auto rcv = _compressor.compress(0, seastar::rpc::snd_buf(_small_buffer_random.share()));
+ if (auto buffer = std::get_if<seastar::temporary_buffer<char>>(&rcv.bufs)) {
+ _small_compressed_buffer_random.emplace_back(std::move(*buffer));
+ } else {
+ _small_compressed_buffer_random
+ = std::move(std::get<std::vector<seastar::temporary_buffer<char>>>(rcv.bufs));
+ }
+
+ rcv = _compressor.compress(0, seastar::rpc::snd_buf(_small_buffer_zeroes.share()));
+ if (auto buffer = std::get_if<seastar::temporary_buffer<char>>(&rcv.bufs)) {
+ _small_compressed_buffer_zeroes.emplace_back(std::move(*buffer));
+ } else {
+ _small_compressed_buffer_zeroes
+ = std::move(std::get<std::vector<seastar::temporary_buffer<char>>>(rcv.bufs));
+ }
+
+ auto bufs = std::vector<temporary_buffer<char>>{};
+ for (auto&& b : _large_buffer_random) {
+ bufs.emplace_back(b.clone());
+ }
+ rcv = _compressor.compress(0, seastar::rpc::snd_buf(std::move(bufs), large_buffer_size));
+ if (auto buffer = std::get_if<seastar::temporary_buffer<char>>(&rcv.bufs)) {
+ _large_compressed_buffer_random.emplace_back(std::move(*buffer));
+ } else {
+ _large_compressed_buffer_random
+ = std::move(std::get<std::vector<seastar::temporary_buffer<char>>>(rcv.bufs));
+ }
+
+ bufs = std::vector<temporary_buffer<char>>{};
+ for (auto&& b : _large_buffer_zeroes) {
+ bufs.emplace_back(b.clone());
+ }
+ rcv = _compressor.compress(0, seastar::rpc::snd_buf(std::move(bufs), large_buffer_size));
+ if (auto buffer = std::get_if<seastar::temporary_buffer<char>>(&rcv.bufs)) {
+ _large_compressed_buffer_zeroes.emplace_back(std::move(*buffer));
+ } else {
+ _large_compressed_buffer_zeroes
+ = std::move(std::get<std::vector<seastar::temporary_buffer<char>>>(rcv.bufs));
+ }
+ }
+
+ Compressor& compressor() { return _compressor; }
+
+ seastar::rpc::snd_buf small_buffer_random() {
+ return get_snd_buf(_small_buffer_random);
+ }
+ seastar::rpc::snd_buf small_buffer_zeroes() {
+ return get_snd_buf(_small_buffer_zeroes);
+ }
+
+ seastar::rpc::snd_buf large_buffer_random() {
+ return get_snd_buf(_large_buffer_random);
+ }
+ seastar::rpc::snd_buf large_buffer_zeroes() {
+ return get_snd_buf(_large_buffer_zeroes);
+ }
+
+ seastar::rpc::rcv_buf small_compressed_buffer_random() {
+ return get_rcv_buf(_small_compressed_buffer_random);
+ }
+ seastar::rpc::rcv_buf small_compressed_buffer_zeroes() {
+ return get_rcv_buf(_small_compressed_buffer_zeroes);
+ }
+
+ seastar::rpc::rcv_buf large_compressed_buffer_random() {
+ return get_rcv_buf(_large_compressed_buffer_random);
+ }
+ seastar::rpc::rcv_buf large_compressed_buffer_zeroes() {
+ return get_rcv_buf(_large_compressed_buffer_zeroes);
+ }
+};
+
+using lz4 = compression<seastar::rpc::lz4_compressor>;
+
+PERF_TEST_F(lz4, small_random_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, small_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4, small_zeroed_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, small_buffer_zeroes())
+ );
+}
+
+PERF_TEST_F(lz4, large_random_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, large_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4, large_zeroed_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, large_buffer_zeroes())
+ );
+}
+
+PERF_TEST_F(lz4, small_random_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(small_compressed_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4, small_zeroed_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(small_compressed_buffer_zeroes())
+ );
+}
+
+PERF_TEST_F(lz4, large_random_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(large_compressed_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4, large_zeroed_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(large_compressed_buffer_zeroes())
+ );
+}
+
+using lz4_fragmented = compression<seastar::rpc::lz4_fragmented_compressor>;
+
+PERF_TEST_F(lz4_fragmented, small_random_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, small_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4_fragmented, small_zeroed_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, small_buffer_zeroes())
+ );
+}
+
+PERF_TEST_F(lz4_fragmented, large_random_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, large_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4_fragmented, large_zeroed_buffer_compress) {
+ perf_tests::do_not_optimize(
+ compressor().compress(0, large_buffer_zeroes())
+ );
+}
+
+PERF_TEST_F(lz4_fragmented, small_random_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(small_compressed_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4_fragmented, small_zeroed_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(small_compressed_buffer_zeroes())
+ );
+}
+
+PERF_TEST_F(lz4_fragmented, large_random_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(large_compressed_buffer_random())
+ );
+}
+
+PERF_TEST_F(lz4_fragmented, large_zeroed_buffer_decompress) {
+ perf_tests::do_not_optimize(
+ compressor().decompress(large_compressed_buffer_zeroes())
+ );
+}
diff --git a/src/seastar/tests/unit/CMakeLists.txt b/src/seastar/tests/unit/CMakeLists.txt
new file mode 100644
index 000000000..fd2f90368
--- /dev/null
+++ b/src/seastar/tests/unit/CMakeLists.txt
@@ -0,0 +1,560 @@
+#
+# This file is open source software, licensed to you under the terms
+# of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+# distributed with this work for additional information regarding copyright
+# ownership. You may not use this file except in compliance with the License.
+#
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# Copyright (C) 2018 Scylladb, Ltd.
+#
+
+# Logical target for all unit tests.
+add_custom_target (unit_tests)
+
+set (Seastar_UNIT_TEST_SMP
+ 2
+ CACHE
+ STRING
+ "Run unit tests with this many cores.")
+
+#
+# Define a new unit test with the given name.
+#
+# seastar_add_test (name
+# [KIND {SEASTAR,BOOST,CUSTOM}]
+# [SOURCES source1 source2 ... sourcen]
+# [WORKING_DIRECTORY dir]
+# [LIBRARIES library1 library2 ... libraryn]
+# [RUN_ARGS arg1 arg2 ... argn])
+#
+# There are three kinds of test we support (the KIND parameter):
+#
+# - SEASTAR: Unit tests which use macros like `SEASTAR_TEST_CASE`
+# - BOOST: Unit tests which use macros like `BOOST_AUTO_TEST_CASE`
+# - CUSTOM: Custom tests which need to be specified
+#
+# SEASTAR and BOOST tests will have their output saved for interpretation by the Jenkins continuous integration service
+# if this is configured for the build.
+#
+# KIND can be omitted, in which case it is assumed to be SEASTAR.
+#
+# If SOURCES is provided, then the test files are first compiled into an executable which has the same name as the test
+# but with a suffix ("_test").
+#
+# WORKING_DIRECTORY can be optionally provided to choose where the test is executed.
+#
+# If LIBRARIES is provided along with SOURCES, then the executable is additionally linked with these libraries.
+#
+# RUN_ARGS are optional additional arguments to pass to the executable. For SEASTAR tests, these come after `--`. For
+# CUSTOM tests with no SOURCES, this parameter can be used to specify the executable name as well as its arguments since
+# no executable is compiled.
+#
+function (seastar_add_test name)
+ set (test_kinds
+ SEASTAR
+ BOOST
+ CUSTOM)
+
+ cmake_parse_arguments (parsed_args
+ ""
+ "WORKING_DIRECTORY;KIND"
+ "RUN_ARGS;SOURCES;LIBRARIES;DEPENDS"
+ ${ARGN})
+
+ if (NOT parsed_args_KIND)
+ set (parsed_args_KIND SEASTAR)
+ elseif (NOT (parsed_args_KIND IN_LIST test_kinds))
+ message (FATAL_ERROR "Invalid test kind. KIND must be one of ${test_kinds}")
+ endif ()
+
+ if (parsed_args_SOURCES)
+ # These may be unused.
+ seastar_jenkins_arguments (${name} jenkins_args)
+
+ #
+ # Each kind of test must populate the `args` and `libraries` lists.
+ #
+
+ set (libraries "${parsed_args_LIBRARIES}")
+
+ set (args "")
+ if (parsed_args_KIND STREQUAL "SEASTAR")
+ list (APPEND libraries
+ seastar_testing
+ seastar_private)
+
+ if (NOT (Seastar_JENKINS STREQUAL ""))
+ list (APPEND args ${jenkins_args})
+ endif ()
+
+ list (APPEND args -- -c ${Seastar_UNIT_TEST_SMP})
+ elseif (parsed_args_KIND STREQUAL "BOOST")
+ list (APPEND libraries
+ Boost::unit_test_framework
+ seastar_private)
+
+ if (NOT (Seastar_JENKINS STREQUAL ""))
+ list (APPEND args ${jenkins_args})
+ endif ()
+ endif ()
+
+ list (APPEND args ${parsed_args_RUN_ARGS})
+
+ set (executable_target test_unit_${name})
+ add_executable (${executable_target} ${parsed_args_SOURCES})
+
+ target_link_libraries (${executable_target}
+ PRIVATE ${libraries})
+
+ target_compile_definitions (${executable_target}
+ PRIVATE SEASTAR_TESTING_MAIN)
+
+ if ((Seastar_STACK_GUARDS STREQUAL "ON") OR
+ ((Seastar_STACK_GUARDS STREQUAL "DEFAULT") AND
+ (CMAKE_BUILD_TYPE IN_LIST Seastar_STACK_GUARD_MODES)))
+ target_compile_definitions (${executable_target}
+ PRIVATE
+ SEASTAR_THREAD_STACK_GUARDS)
+ endif ()
+
+ target_include_directories (${executable_target}
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${Seastar_SOURCE_DIR}/src)
+
+ set_target_properties (${executable_target}
+ PROPERTIES
+ OUTPUT_NAME ${name}_test)
+
+ add_dependencies (unit_tests ${executable_target})
+ set (forwarded_args COMMAND ${executable_target} ${args})
+ else ()
+ if (NOT (parsed_args_KIND STREQUAL "CUSTOM"))
+ message (FATAL_ERROR "SOURCES are required for ${parsed_args_KIND} tests")
+ endif ()
+
+ set (forwarded_args COMMAND ${parsed_args_RUN_ARGS})
+ endif ()
+
+ #
+ # We expect `forwarded_args` to be populated correctly at this point.
+ #
+
+ set (target test_unit_${name}_run)
+
+ if (parsed_args_WORKING_DIRECTORY)
+ list (APPEND forwarded_args WORKING_DIRECTORY ${parsed_args_WORKING_DIRECTORY})
+ endif ()
+
+ if (parsed_args_DEPENDS)
+ add_dependencies(${executable_target} ${parsed_args_DEPENDS})
+ endif()
+
+ add_custom_target (${target}
+ ${forwarded_args}
+ USES_TERMINAL)
+
+ add_test (
+ NAME Seastar.unit.${name}
+ COMMAND ${CMAKE_COMMAND} --build ${Seastar_BINARY_DIR} --target ${target})
+
+ set_tests_properties (Seastar.unit.${name}
+ PROPERTIES
+ TIMEOUT ${Seastar_TEST_TIMEOUT}
+ ENVIRONMENT "${Seastar_TEST_ENVIRONMENT}")
+endfunction ()
+
+#
+# Define a new custom unit test whose entry point is a Seastar application.
+#
+# seastar_add_app_test (name
+# [SOURCES source1 source2 ... sourcen]
+# [LIBRARIES library1 library2 ... libraryn]
+# [RUN_ARGS arg1 arg2 ... argn])
+#
+# These kinds of tests are structured like Seastar applications.
+#
+# These tests always link against `seastar_private` and are always invoked with
+# `-c ${Seastar_UNIT_TEST_SMP}`.
+#
+function (seastar_add_app_test name)
+ cmake_parse_arguments (parsed_args
+ ""
+ ""
+ "RUN_ARGS;SOURCES;LIBRARIES"
+ ${ARGN})
+
+ seastar_add_test (${name}
+ KIND CUSTOM
+ SOURCES ${parsed_args_SOURCES}
+ LIBRARIES
+ seastar_private
+ ${parsed_args_LIBRARIES}
+ RUN_ARGS
+ -c ${Seastar_UNIT_TEST_SMP}
+ ${parsed_args_RUN_ARGS})
+endfunction ()
+
+function (prepend_each var prefix)
+ set (result "")
+
+ foreach (x ${ARGN})
+ list (APPEND result ${prefix}/${x})
+ endforeach ()
+
+ set (${var} ${result} PARENT_SCOPE)
+endfunction ()
+
+add_custom_target (test_unit
+ COMMAND ctest --verbose -R Seastar.unit
+ USES_TERMINAL)
+
+seastar_add_test (abort_source
+ SOURCES abort_source_test.cc)
+
+seastar_add_test (alloc
+ SOURCES alloc_test.cc)
+
+if (NOT Seastar_EXECUTE_ONLY_FAST_TESTS)
+ set (allocator_test_args "")
+else ()
+ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set (allocator_test_args --iterations 5)
+ else ()
+ set (allocator_test_args --time 0.1)
+ endif ()
+endif ()
+
+seastar_add_test (allocator
+ SOURCES allocator_test.cc
+ RUN_ARGS ${allocator_test_args})
+
+seastar_add_app_test (alien
+ SOURCES alien_test.cc)
+
+seastar_add_test (checked_ptr
+ SOURCES checked_ptr_test.cc)
+
+seastar_add_test (chunked_fifo
+ SOURCES chunked_fifo_test.cc)
+
+seastar_add_test (circular_buffer
+ SOURCES circular_buffer_test.cc)
+
+seastar_add_test (circular_buffer_fixed_capacity
+ SOURCES circular_buffer_fixed_capacity_test.cc)
+
+seastar_add_test (connect
+ SOURCES connect_test.cc)
+
+seastar_add_test (coroutines
+ SOURCES coroutines_test.cc)
+
+seastar_add_test (defer
+ SOURCES defer_test.cc)
+
+seastar_add_test (deleter
+ SOURCES deleter_test.cc)
+
+seastar_add_app_test (directory
+ SOURCES directory_test.cc)
+
+seastar_add_test (distributed
+ SOURCES distributed_test.cc)
+
+seastar_add_test (dns
+ SOURCES dns_test.cc)
+
+seastar_add_test (execution_stage
+ SOURCES execution_stage_test.cc)
+
+seastar_add_test (expiring_fifo
+ SOURCES expiring_fifo_test.cc)
+
+seastar_add_test (fair_queue
+ SOURCES fair_queue_test.cc)
+
+seastar_add_test (file_io
+ SOURCES file_io_test.cc)
+
+seastar_add_test (file_utils
+ SOURCES file_utils_test.cc)
+
+seastar_add_test (foreign_ptr
+ SOURCES foreign_ptr_test.cc)
+
+seastar_add_test (fsnotifier
+ SOURCES fsnotifier_test.cc)
+
+seastar_add_test (fstream
+ SOURCES
+ fstream_test.cc
+ mock_file.hh)
+
+seastar_add_test (futures
+ SOURCES futures_test.cc)
+
+seastar_add_test (sharded
+ SOURCES sharded_test.cc)
+
+seastar_add_test (httpd
+ SOURCES
+ httpd_test.cc
+ loopback_socket.hh)
+
+seastar_add_test (ipv6
+ SOURCES ipv6_test.cc)
+
+seastar_add_test (network_interface
+ SOURCES network_interface_test.cc)
+
+seastar_add_test (json_formatter
+ SOURCES json_formatter_test.cc)
+
+seastar_add_test (locking
+ SOURCES locking_test.cc)
+
+seastar_add_test (lowres_clock
+ SOURCES lowres_clock_test.cc)
+
+seastar_add_test (metrics
+ SOURCES metrics_test.cc)
+
+seastar_add_test (net_config
+ KIND BOOST
+ SOURCES net_config_test.cc)
+
+seastar_add_test (noncopyable_function
+ KIND BOOST
+ SOURCES noncopyable_function_test.cc)
+
+seastar_add_test (output_stream
+ SOURCES output_stream_test.cc)
+
+seastar_add_test (packet
+ KIND BOOST
+ SOURCES packet_test.cc)
+
+seastar_add_test (program_options
+ KIND BOOST
+ SOURCES program_options_test.cc)
+
+seastar_add_test (queue
+ SOURCES queue_test.cc)
+
+seastar_add_test (request_parser
+ SOURCES request_parser_test.cc)
+
+seastar_add_test (rpc
+ SOURCES
+ loopback_socket.hh
+ rpc_test.cc)
+
+seastar_add_test (semaphore
+ SOURCES semaphore_test.cc)
+
+seastar_add_test (shared_ptr
+ KIND BOOST
+ SOURCES shared_ptr_test.cc)
+
+seastar_add_test (signal
+ SOURCES signal_test.cc)
+
+seastar_add_test (simple_stream
+ KIND BOOST
+ SOURCES simple_stream_test.cc)
+
+# TODO: Disabled for now. See GH-520.
+# seastar_add_test (slab
+# SOURCES slab_test.cc
+# NO_SEASTAR_TESTING_LIBRARY)
+
+seastar_add_app_test (smp
+ SOURCES smp_test.cc)
+
+seastar_add_app_test (socket
+ SOURCES socket_test.cc)
+
+seastar_add_test (sstring
+ KIND BOOST
+ SOURCES sstring_test.cc)
+
+seastar_add_test (stall_detector
+ SOURCES stall_detector_test.cc)
+
+seastar_add_test (thread
+ SOURCES thread_test.cc)
+
+seastar_add_test (scheduling_group
+ SOURCES scheduling_group_test.cc)
+
+seastar_add_app_test (thread_context_switch
+ SOURCES thread_context_switch_test.cc)
+
+seastar_add_app_test (timer
+ SOURCES timer_test.cc)
+
+seastar_add_test (uname
+ KIND BOOST
+ SOURCES uname_test.cc)
+
+function(seastar_add_certgen name)
+ cmake_parse_arguments(CERT
+ ""
+ "SUBJECT;SERVER;NAME;DOMAIN;COMMON;LOCALITY;ORG;WIDTH;STATE;COUNTRY;UNIT;EMAIL;DAYS;ALG"
+ "ALG_OPTS"
+ ${ARGN}
+ )
+
+ if (NOT CERT_SERVER)
+ execute_process(COMMAND hostname
+ RESULT_VARIABLE CERT_SERVER
+ )
+ endif()
+ if (NOT CERT_DOMAIN)
+ execute_process(COMMAND dnsdomainname
+ RESULT_VARIABLE CERT_DOMAIN
+ )
+ endif()
+ if (NOT CERT_NAME)
+ set(CERT_NAME ${CERT_SERVER})
+ endif()
+ if (NOT CERT_COUNTRY)
+ set(CERT_COUNTRY SE)
+ endif()
+ if (NOT CERT_STATE)
+ set(CERT_STATE Stockholm)
+ endif()
+ if (NOT CERT_LOCALITY)
+ set(CERT_LOCALITY ${CERT_STATE})
+ endif()
+ if (NOT CERT_ORG)
+ set(CERT_ORG ${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_UNIT)
+ set(CERT_UNIT ${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_COMMON)
+ set(CERT_COMMON ${CERT_SERVER}.${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_EMAIL)
+ set(CERT_EMAIL postmaster@${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_WIDTH)
+ set(CERT_WIDTH 4096)
+ endif()
+ if (NOT CERT_DAYS)
+ set(CERT_DAYS 3650)
+ endif()
+ if ((NOT CERT_ALG) AND (NOT CERT_ALG_OPTS))
+ set(CERT_ALG_OPTS -pkeyopt rsa_keygen_bits:${CERT_WIDTH})
+ endif()
+ if (NOT CERT_ALG)
+ set(CERT_ALG RSA)
+ endif()
+
+ set(CERT_PRIVKEY ${CERT_NAME}.key)
+ set(CERT_REQ ${CERT_NAME}.csr)
+ set(CERT_CERT ${CERT_NAME}.crt)
+
+ set(CERT_CAPRIVKEY ca${CERT_NAME}.key)
+ set(CERT_CAROOT ca${CERT_NAME}.pem)
+
+ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cert.cfg.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/${CERT_NAME}.cfg"
+ )
+
+ find_program(OPENSSL openssl)
+
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_PRIVKEY}"
+ COMMAND ${OPENSSL} genpkey -out ${CERT_PRIVKEY} -algorithm ${CERT_ALG} ${CERT_ALG_OPTS}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_REQ}"
+ COMMAND ${OPENSSL} req -new -key ${CERT_PRIVKEY} -out ${CERT_REQ} -config ${CERT_NAME}.cfg
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_PRIVKEY}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAPRIVKEY}"
+ COMMAND ${OPENSSL} genpkey -out ${CERT_CAPRIVKEY} -algorithm ${CERT_ALG} ${CERT_ALG_OPTS}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAROOT}"
+ COMMAND ${OPENSSL} req -x509 -new -nodes -key ${CERT_CAPRIVKEY} -days ${CERT_DAYS} -config ${CERT_NAME}.cfg -out ${CERT_CAROOT}
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAPRIVKEY}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT}"
+ COMMAND ${OPENSSL} x509 -req -in ${CERT_REQ} -CA ${CERT_CAROOT} -CAkey ${CERT_CAPRIVKEY} -CAcreateserial -out ${CERT_CERT} -days ${CERT_DAYS}
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_REQ}" "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAROOT}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+ add_custom_target(${name}
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT}"
+ )
+endfunction()
+
+seastar_add_certgen(testcrt DOMAIN scylladb.org SERVER test)
+seastar_add_certgen(othercrt DOMAIN apa.org SERVER other)
+
+set (tls_certificate_files
+ tls-ca-bundle.pem
+)
+
+prepend_each (
+ in_tls_certificate_files
+ ${CMAKE_CURRENT_SOURCE_DIR}/
+ ${tls_certificate_files})
+
+prepend_each (
+ out_tls_certificate_files
+ ${CMAKE_CURRENT_BINARY_DIR}/
+ ${tls_certificate_files})
+
+add_custom_command (
+ DEPENDS ${in_tls_certificate_files}
+ OUTPUT ${out_tls_certificate_files}
+ COMMAND ${CMAKE_COMMAND} -E copy ${in_tls_certificate_files} ${CMAKE_CURRENT_BINARY_DIR})
+
+add_custom_target(tls_files
+ DEPENDS ${out_tls_certificate_files}
+)
+
+seastar_add_test (tls
+ DEPENDS tls_files testcrt othercrt
+ SOURCES tls_test.cc
+ LIBRARIES boost_filesystem
+ WORKING_DIRECTORY ${Seastar_BINARY_DIR})
+
+seastar_add_test (tuple_utils
+ KIND BOOST
+ SOURCES tuple_utils_test.cc)
+
+seastar_add_test (unix_domain
+ SOURCES unix_domain_test.cc)
+
+seastar_add_test (unwind
+ KIND BOOST
+ SOURCES unwind_test.cc)
+
+seastar_add_test (weak_ptr
+ KIND BOOST
+ SOURCES weak_ptr_test.cc)
+
+seastar_add_test (log_buf
+ SOURCES log_buf_test.cc)
diff --git a/src/seastar/tests/unit/abort_source_test.cc b/src/seastar/tests/unit/abort_source_test.cc
new file mode 100644
index 000000000..066500637
--- /dev/null
+++ b/src/seastar/tests/unit/abort_source_test.cc
@@ -0,0 +1,86 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/gate.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/do_with.hh>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_abort_source_notifies_subscriber) {
+ bool signalled = false;
+ auto as = abort_source();
+ auto st_opt = as.subscribe([&signalled] () noexcept {
+ signalled = true;
+ });
+ BOOST_REQUIRE_EQUAL(true, bool(st_opt));
+ as.request_abort();
+ BOOST_REQUIRE_EQUAL(true, signalled);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_abort_source_subscription_unregister) {
+ bool signalled = false;
+ auto as = abort_source();
+ auto st_opt = as.subscribe([&signalled] () noexcept {
+ signalled = true;
+ });
+ BOOST_REQUIRE_EQUAL(true, bool(st_opt));
+ st_opt = { };
+ as.request_abort();
+ BOOST_REQUIRE_EQUAL(false, signalled);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_abort_source_rejects_subscription) {
+ auto as = abort_source();
+ as.request_abort();
+ auto st_opt = as.subscribe([] () noexcept { });
+ BOOST_REQUIRE_EQUAL(false, bool(st_opt));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_sleep_abortable) {
+ auto as = std::make_unique<abort_source>();
+ auto f = sleep_abortable(100s, *as).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have failed");
+ } catch (const sleep_aborted& e) {
+ // expected
+ } catch (...) {
+ BOOST_FAIL("unexpected exception");
+ }
+ });
+ as->request_abort();
+ return f.finally([as = std::move(as)] { });
+}
+
+// Verify that negative sleep does not sleep forever. It should not sleep
+// at all.
+SEASTAR_TEST_CASE(test_negative_sleep_abortable) {
+ return do_with(abort_source(), [] (abort_source& as) {
+ return sleep_abortable(-10s, as);
+ });
+}
diff --git a/src/seastar/tests/unit/alien_test.cc b/src/seastar/tests/unit/alien_test.cc
new file mode 100644
index 000000000..3aa527a4a
--- /dev/null
+++ b/src/seastar/tests/unit/alien_test.cc
@@ -0,0 +1,116 @@
+// -*- mode:C++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*-
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 Red Hat
+ */
+
+#include <future>
+#include <numeric>
+#include <iostream>
+#include <seastar/core/alien.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/posix.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/util/later.hh>
+
+using namespace seastar;
+
+enum {
+ ENGINE_READY = 24,
+ ALIEN_DONE = 42,
+};
+
+int main(int argc, char** argv)
+{
+ // we need a protocol that both seastar and alien understand.
+ // and on which, a seastar future can wait.
+ int engine_ready_fd = eventfd(0, 0);
+ auto alien_done = file_desc::eventfd(0, 0);
+
+ // use the raw fd, because seastar engine want to take over the fds, if it
+ // polls on them.
+ auto zim = std::async([engine_ready_fd,
+ alien_done=alien_done.get()] {
+ eventfd_t result = 0;
+ // wait until the seastar engine is ready
+ int r = ::eventfd_read(engine_ready_fd, &result);
+ if (r < 0) {
+ return -EINVAL;
+ }
+ if (result != ENGINE_READY) {
+ return -EINVAL;
+ }
+ std::vector<std::future<int>> counts;
+ for (auto i : boost::irange(0u, smp::count)) {
+ // send messages from alien.
+ counts.push_back(alien::submit_to(i, [i] {
+ return seastar::make_ready_future<int>(i);
+ }));
+ }
+ // std::future<void>
+ alien::submit_to(0, [] {
+ return seastar::make_ready_future<>();
+ }).wait();
+ int total = 0;
+ for (auto& count : counts) {
+ total += count.get();
+ }
+ // i am done. dismiss the engine
+ ::eventfd_write(alien_done, ALIEN_DONE);
+ return total;
+ });
+
+ seastar::app_template app;
+ eventfd_t result = 0;
+ app.run(argc, argv, [&] {
+ return seastar::now().then([engine_ready_fd] {
+ // engine ready!
+ ::eventfd_write(engine_ready_fd, ENGINE_READY);
+ return seastar::now();
+ }).then([alien_done = std::move(alien_done), &result]() mutable {
+ return do_with(seastar::pollable_fd(std::move(alien_done)), [&result] (pollable_fd& alien_done_fds) {
+ // check if alien has dismissed me.
+ return alien_done_fds.readable().then([&result, &alien_done_fds] {
+ auto ret = alien_done_fds.get_file_desc().read(&result, sizeof(result));
+ return make_ready_future<size_t>(*ret);
+ });
+ });
+ }).then([&result](size_t n) {
+ if (n != sizeof(result)) {
+ throw std::runtime_error("read from eventfd failed");
+ }
+ if (result != ALIEN_DONE) {
+ throw std::logic_error("alien failed to dismiss me");
+ }
+ return seastar::now();
+ }).handle_exception([](auto ep) {
+ std::cerr << "Error: " << ep << std::endl;
+ }).finally([] {
+ seastar::engine().exit(0);
+ });
+ });
+ int total = zim.get();
+ const auto shards = boost::irange(0u, smp::count);
+ auto expected = std::accumulate(std::begin(shards), std::end(shards), 0);
+ if (total != expected) {
+ std::cerr << "Bad total: " << total << " != " << expected << std::endl;
+ return 1;
+ }
+}
diff --git a/src/seastar/tests/unit/alloc_test.cc b/src/seastar/tests/unit/alloc_test.cc
new file mode 100644
index 000000000..7569d49d9
--- /dev/null
+++ b/src/seastar/tests/unit/alloc_test.cc
@@ -0,0 +1,162 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/memory.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/temporary_buffer.hh>
+#include <vector>
+#include <future>
+#include <iostream>
+
+#include <malloc.h>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(alloc_almost_all_and_realloc_it_with_a_smaller_size) {
+#ifndef SEASTAR_DEFAULT_ALLOCATOR
+ auto all = memory::stats().total_memory();
+ auto reserve = size_t(0.02 * all);
+ auto to_alloc = all - (reserve + (10 << 20));
+ auto orig_to_alloc = to_alloc;
+ auto obj = malloc(to_alloc);
+ while (!obj) {
+ to_alloc *= 0.9;
+ obj = malloc(to_alloc);
+ }
+ BOOST_REQUIRE(to_alloc > orig_to_alloc / 4);
+ BOOST_REQUIRE(obj != nullptr);
+ auto obj2 = realloc(obj, to_alloc - (1 << 20));
+ BOOST_REQUIRE(obj == obj2);
+ free(obj2);
+#endif
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(malloc_0_and_free_it) {
+#ifndef SEASTAR_DEFAULT_ALLOCATOR
+ auto obj = malloc(0);
+ BOOST_REQUIRE(obj != nullptr);
+ free(obj);
+#endif
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_live_objects_counter_with_cross_cpu_free) {
+ return smp::submit_to(1, [] {
+ auto ret = std::vector<std::unique_ptr<bool>>(1000000);
+ for (auto& o : ret) {
+ o = std::make_unique<bool>(false);
+ }
+ return ret;
+ }).then([] (auto&& vec) {
+ vec.clear(); // cause cross-cpu free
+ BOOST_REQUIRE(memory::stats().live_objects() < std::numeric_limits<size_t>::max() / 2);
+ });
+}
+
+SEASTAR_TEST_CASE(test_aligned_alloc) {
+ for (size_t align = sizeof(void*); align <= 65536; align <<= 1) {
+ for (size_t size = align; size <= align * 2; size <<= 1) {
+ void *p = aligned_alloc(align, size);
+ BOOST_REQUIRE(p != nullptr);
+ BOOST_REQUIRE((reinterpret_cast<uintptr_t>(p) % align) == 0);
+ ::memset(p, 0, size);
+ free(p);
+ }
+ }
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_temporary_buffer_aligned) {
+ for (size_t align = sizeof(void*); align <= 65536; align <<= 1) {
+ for (size_t size = align; size <= align * 2; size <<= 1) {
+ auto buf = temporary_buffer<char>::aligned(align, size);
+ void *p = buf.get_write();
+ BOOST_REQUIRE(p != nullptr);
+ BOOST_REQUIRE((reinterpret_cast<uintptr_t>(p) % align) == 0);
+ ::memset(p, 0, size);
+ }
+ }
+ return make_ready_future<>();
+}
+
+#ifndef SEASTAR_DEFAULT_ALLOCATOR
+
+struct thread_alloc_info {
+ memory::statistics before;
+ memory::statistics after;
+ void *ptr;
+};
+
+template <typename Func>
+thread_alloc_info run_with_stats(Func&& f) {
+ return std::async([&f](){
+ auto before = seastar::memory::stats();
+ void* ptr = f();
+ auto after = seastar::memory::stats();
+ return thread_alloc_info{before, after, ptr};
+ }).get();
+}
+
+template <typename Func>
+void test_allocation_function(Func f) {
+ // alien alloc and free
+ auto alloc_info = run_with_stats(f);
+ auto free_info = std::async([p = alloc_info.ptr]() {
+ auto before = seastar::memory::stats();
+ free(p);
+ auto after = seastar::memory::stats();
+ return thread_alloc_info{before, after, nullptr};
+ }).get();
+
+ // there were mallocs
+ BOOST_REQUIRE(alloc_info.after.foreign_mallocs() - alloc_info.before.foreign_mallocs() > 0);
+ // mallocs balanced with frees
+ BOOST_REQUIRE(alloc_info.after.foreign_mallocs() - alloc_info.before.foreign_mallocs() == free_info.after.foreign_frees() - free_info.before.foreign_frees());
+
+ // alien alloc reactor free
+ auto info = run_with_stats(f);
+ auto before_cross_frees = memory::stats().foreign_cross_frees();
+ free(info.ptr);
+ BOOST_REQUIRE(memory::stats().foreign_cross_frees() - before_cross_frees == 1);
+
+ // reactor alloc, alien free
+ void *p = f();
+ auto alien_cross_frees = std::async([p]() {
+ auto frees_before = memory::stats().cross_cpu_frees();
+ free(p);
+ return memory::stats().cross_cpu_frees()-frees_before;
+ }).get();
+ BOOST_REQUIRE(alien_cross_frees == 1);
+}
+
+SEASTAR_TEST_CASE(test_foreign_function_use_glibc_malloc) {
+ test_allocation_function([]() ->void * { return malloc(1); });
+ test_allocation_function([]() { return realloc(NULL, 10); });
+ test_allocation_function([]() {
+ auto p = malloc(1);
+ return realloc(p, 1000);
+ });
+ test_allocation_function([]() { return aligned_alloc(4, 1024); });
+ return make_ready_future<>();
+}
+#endif
diff --git a/src/seastar/tests/unit/allocator_test.cc b/src/seastar/tests/unit/allocator_test.cc
new file mode 100644
index 000000000..cc0e6c80d
--- /dev/null
+++ b/src/seastar/tests/unit/allocator_test.cc
@@ -0,0 +1,220 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2014 Cloudius Systems
+ */
+
+#include <seastar/core/memory.hh>
+#include <seastar/core/timer.hh>
+#include <seastar/testing/test_runner.hh>
+#include <cmath>
+#include <iostream>
+#include <iomanip>
+#include <algorithm>
+#include <cassert>
+#include <memory>
+#include <chrono>
+#include <boost/program_options.hpp>
+
+using namespace seastar;
+
+struct allocation {
+ size_t n;
+ std::unique_ptr<char[]> data;
+ char poison;
+ allocation(size_t n, char poison) : n(n), data(new char[n]), poison(poison) {
+ std::fill_n(data.get(), n, poison);
+ }
+ ~allocation() {
+ verify();
+ }
+ allocation(allocation&& x) noexcept = default;
+ void verify() {
+ if (data) {
+ assert(std::find_if(data.get(), data.get() + n, [this] (char c) {
+ return c != poison;
+ }) == data.get() + n);
+ }
+ }
+ allocation& operator=(allocation&& x) {
+ verify();
+ if (this != &x) {
+ data = std::move(x.data);
+ n = x.n;
+ poison = x.poison;
+ }
+ return *this;
+ }
+};
+
+#ifdef __cpp_aligned_new
+
+template <size_t N>
+struct alignas(N) cpp17_allocation final {
+ char v;
+};
+
+struct test17 {
+ struct handle {
+ const test17* d;
+ void* p;
+ handle(const test17* d, void* p) : d(d), p(p) {}
+ handle(const handle&) = delete;
+ handle(handle&& x) noexcept : d(std::exchange(x.d, nullptr)), p(std::exchange(x.p, nullptr)) {}
+ handle& operator=(const handle&) = delete;
+ handle& operator=(handle&& x) noexcept {
+ std::swap(d, x.d);
+ std::swap(p, x.p);
+ return *this;
+ }
+ ~handle() {
+ if (d) {
+ d->free(p);
+ }
+ }
+ };
+ virtual ~test17() {}
+ virtual handle alloc() const = 0;
+ virtual void free(void* ptr) const = 0;
+};
+
+template <size_t N>
+struct test17_concrete : test17 {
+ using value_type = cpp17_allocation<N>;
+ static_assert(sizeof(value_type) == N, "language does not guarantee size >= align");
+ virtual handle alloc() const override {
+ auto ptr = new value_type();
+ assert((reinterpret_cast<uintptr_t>(ptr) & (N - 1)) == 0);
+ return handle{this, ptr};
+ }
+ virtual void free(void* ptr) const override {
+ delete static_cast<value_type*>(ptr);
+ }
+};
+
+void test_cpp17_aligned_allocator() {
+ std::vector<std::unique_ptr<test17>> tv;
+ tv.push_back(std::make_unique<test17_concrete<1>>());
+ tv.push_back(std::make_unique<test17_concrete<2>>());
+ tv.push_back(std::make_unique<test17_concrete<4>>());
+ tv.push_back(std::make_unique<test17_concrete<8>>());
+ tv.push_back(std::make_unique<test17_concrete<16>>());
+ tv.push_back(std::make_unique<test17_concrete<64>>());
+ tv.push_back(std::make_unique<test17_concrete<128>>());
+ tv.push_back(std::make_unique<test17_concrete<2048>>());
+ tv.push_back(std::make_unique<test17_concrete<4096>>());
+ tv.push_back(std::make_unique<test17_concrete<4096*16>>());
+ tv.push_back(std::make_unique<test17_concrete<4096*256>>());
+
+ std::default_random_engine random_engine(testing::local_random_engine());
+ std::uniform_int_distribution<> type_dist(0, 1);
+ std::uniform_int_distribution<size_t> size_dist(0, tv.size() - 1);
+ std::uniform_real_distribution<> which_dist(0, 1);
+
+ std::vector<test17::handle> allocs;
+ for (unsigned i = 0; i < 10000; ++i) {
+ auto type = type_dist(random_engine);
+ switch (type) {
+ case 0: {
+ size_t sz_idx = size_dist(random_engine);
+ allocs.push_back(tv[sz_idx]->alloc());
+ break;
+ }
+ case 1:
+ if (!allocs.empty()) {
+ size_t idx = which_dist(random_engine) * allocs.size();
+ std::swap(allocs[idx], allocs.back());
+ allocs.pop_back();
+ }
+ break;
+ }
+ }
+}
+
+#else
+
+void test_cpp17_aligned_allocator() {
+}
+
+#endif
+
+int main(int ac, char** av) {
+ namespace bpo = boost::program_options;
+ bpo::options_description opts("Allowed options");
+ opts.add_options()
+ ("help", "produce this help message")
+ ("iterations", bpo::value<unsigned>(), "run s specified number of iterations")
+ ("time", bpo::value<float>()->default_value(5.0), "run for a specified amount of time, in seconds")
+ ("random-seed", boost::program_options::value<unsigned>(), "Random number generator seed");
+ ;
+ bpo::variables_map vm;
+ bpo::store(bpo::parse_command_line(ac, av, opts), vm);
+ bpo::notify(vm);
+ test_cpp17_aligned_allocator();
+ auto seed = vm.count("random-seed") ? vm["random-seed"].as<unsigned>() : std::random_device{}();
+ std::default_random_engine random_engine(seed);
+ std::exponential_distribution<> distr(0.2);
+ std::uniform_int_distribution<> type(0, 1);
+ std::uniform_int_distribution<char> poison(-128, 127);
+ std::uniform_real_distribution<> which(0, 1);
+ std::vector<allocation> allocations;
+ auto iteration = [&] {
+ auto typ = type(random_engine);
+ switch (typ) {
+ case 0: {
+ size_t n = std::min<double>(std::exp(distr(random_engine)), 1 << 25);
+ try {
+ allocations.emplace_back(n, poison(random_engine));
+ } catch (std::bad_alloc&) {
+
+ }
+ break;
+ }
+ case 1: {
+ if (allocations.empty()) {
+ break;
+ }
+ size_t i = which(random_engine) * allocations.size();
+ allocations[i] = std::move(allocations.back());
+ allocations.pop_back();
+ break;
+ }
+ }
+ };
+ if (vm.count("help")) {
+ std::cout << opts << "\n";
+ return 1;
+ }
+ std::cout << "random-seed=" << seed << "\n";
+ if (vm.count("iterations")) {
+ auto iterations = vm["iterations"].as<unsigned>();
+ for (unsigned i = 0; i < iterations; ++i) {
+ iteration();
+ }
+ } else {
+ auto time = vm["time"].as<float>();
+ using clock = steady_clock_type;
+ auto end = clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1) * time);
+ while (clock::now() < end) {
+ for (unsigned i = 0; i < 1000; ++i) {
+ iteration();
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/seastar/tests/unit/cert.cfg.in b/src/seastar/tests/unit/cert.cfg.in
new file mode 100644
index 000000000..1591a1d39
--- /dev/null
+++ b/src/seastar/tests/unit/cert.cfg.in
@@ -0,0 +1,23 @@
+[ req ]
+default_bits = @CERT_WIDTH@
+default_keyfile = @CERT_PRIVKEY@
+default_md = sha256
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+prompt = no
+[ req_distinguished_name ]
+C = @CERT_COUNTRY@
+ST = @CERT_STATE@
+L = @CERT_LOCALITY@
+O = @CERT_ORG@
+OU = @CERT_UNIT@
+CN= @CERT_COMMON@
+emailAddress = @CERT_EMAIL@
+[v3_ca]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer:always
+basicConstraints = CA:true
+[v3_req]
+# Extensions to add to a certificate request
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
diff --git a/src/seastar/tests/unit/checked_ptr_test.cc b/src/seastar/tests/unit/checked_ptr_test.cc
new file mode 100644
index 000000000..56133f417
--- /dev/null
+++ b/src/seastar/tests/unit/checked_ptr_test.cc
@@ -0,0 +1,125 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/checked_ptr.hh>
+#include <seastar/core/weak_ptr.hh>
+
+using namespace seastar;
+
+static_assert(std::is_nothrow_default_constructible_v<checked_ptr<int*>>);
+static_assert(std::is_nothrow_move_constructible_v<checked_ptr<int*>>);
+static_assert(std::is_nothrow_copy_constructible_v<checked_ptr<int*>>);
+
+static_assert(std::is_nothrow_default_constructible_v<checked_ptr<weak_ptr<int>>>);
+static_assert(std::is_nothrow_move_constructible_v<checked_ptr<weak_ptr<int>>>);
+
+template <typename T>
+class may_throw_on_null_ptr : public seastar::weak_ptr<T> {
+public:
+ may_throw_on_null_ptr(std::nullptr_t) {}
+};
+
+static_assert(!std::is_nothrow_default_constructible_v<may_throw_on_null_ptr<int>>);
+static_assert(!std::is_nothrow_default_constructible_v<checked_ptr<may_throw_on_null_ptr<int>>>);
+static_assert(std::is_nothrow_move_constructible_v<checked_ptr<may_throw_on_null_ptr<int>>>);
+static_assert(!std::is_nothrow_copy_constructible_v<checked_ptr<may_throw_on_null_ptr<int>>>);
+
+struct my_st : public weakly_referencable<my_st> {
+ my_st(int a_) : a(a_) {}
+ int a;
+};
+
+void const_ref_check_naked(const seastar::checked_ptr<my_st*>& cp) {
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+}
+
+void const_ref_check_smart(const seastar::checked_ptr<::weak_ptr<my_st>>& cp) {
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_empty_when_default_initialized) {
+ seastar::checked_ptr<int*> cp;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_empty_when_nullptr_initialized_nakes_ptr) {
+ seastar::checked_ptr<int*> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_empty_when_nullptr_initialized_smart_ptr) {
+ seastar::checked_ptr<::weak_ptr<my_st>> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_initialized_after_assignment_naked_ptr) {
+ seastar::checked_ptr<my_st*> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+ my_st i(3);
+ my_st k(3);
+ cp = &i;
+ seastar::checked_ptr<my_st*> cp1(&i);
+ seastar::checked_ptr<my_st*> cp2(&k);
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE(cp == cp1);
+ BOOST_REQUIRE(cp != cp2);
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+
+ const_ref_check_naked(cp);
+
+ cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_initialized_after_assignment_smart_ptr) {
+ seastar::checked_ptr<::weak_ptr<my_st>> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+ std::unique_ptr<my_st> i = std::make_unique<my_st>(3);
+ cp = i->weak_from_this();
+ seastar::checked_ptr<::weak_ptr<my_st>> cp1(i->weak_from_this());
+ seastar::checked_ptr<::weak_ptr<my_st>> cp2;
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE(cp == cp1);
+ BOOST_REQUIRE(cp != cp2);
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+
+ const_ref_check_smart(cp);
+
+ i = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+ BOOST_REQUIRE(!bool(cp1));
+ BOOST_REQUIRE(!bool(cp2));
+}
+
diff --git a/src/seastar/tests/unit/chunked_fifo_test.cc b/src/seastar/tests/unit/chunked_fifo_test.cc
new file mode 100644
index 000000000..3d57576ea
--- /dev/null
+++ b/src/seastar/tests/unit/chunked_fifo_test.cc
@@ -0,0 +1,358 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2016 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/chunked_fifo.hh>
+#include <stdlib.h>
+#include <chrono>
+#include <deque>
+#include <seastar/core/circular_buffer.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_small) {
+ // Check all the methods of chunked_fifo but with a trivial type (int) and
+ // only a few elements - and in particular a single chunk is enough.
+ chunked_fifo<int> fifo;
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ fifo.push_back(3);
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.push_back(17);
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 17);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ // The previously allocated chunk should have been freed, and now
+ // a new one will need to be allocated:
+ fifo.push_back(57);
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 57);
+ // check miscelleneous methods (at least they shouldn't crash)
+ fifo.clear();
+ fifo.shrink_to_fit();
+ fifo.reserve(1);
+ fifo.reserve(100);
+ fifo.reserve(1280);
+ fifo.shrink_to_fit();
+ fifo.reserve(1280);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_fullchunk) {
+ // Grow a chunked_fifo to exactly fill a chunk, and see what happens when
+ // we cross that chunk.
+ constexpr size_t N = 128;
+ chunked_fifo<int, N> fifo;
+ for (int i = 0; i < static_cast<int>(N); i++) {
+ fifo.push_back(i);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), N);
+ fifo.push_back(N);
+ BOOST_REQUIRE_EQUAL(fifo.size(), N+1);
+ for (int i = 0 ; i < static_cast<int>(N+1); i++) {
+ BOOST_REQUIRE_EQUAL(fifo.front(), i);
+ BOOST_REQUIRE_EQUAL(fifo.size(), N+1-i);
+ fifo.pop_front();
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_big) {
+ // Grow a chunked_fifo to many elements, and see things are working as
+ // expected
+ chunked_fifo<int> fifo;
+ constexpr size_t N = 100'000;
+ for (int i=0; i < static_cast<int>(N); i++) {
+ fifo.push_back(i);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), N);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ for (int i = 0 ; i < static_cast<int>(N); i++) {
+ BOOST_REQUIRE_EQUAL(fifo.front(), i);
+ BOOST_REQUIRE_EQUAL(fifo.size(), N-i);
+ fifo.pop_front();
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_constructor) {
+ // Check that chunked_fifo appropriately calls the type's constructor
+ // and destructor, and doesn't need anything else.
+ struct typ {
+ int val;
+ unsigned* constructed;
+ unsigned* destructed;
+ typ(int val, unsigned* constructed, unsigned* destructed)
+ : val(val), constructed(constructed), destructed(destructed) {
+ ++*constructed;
+ }
+ ~typ() { ++*destructed; }
+ };
+ chunked_fifo<typ> fifo;
+ unsigned constructed = 0, destructed = 0;
+ constexpr unsigned N = 1000;
+ for (unsigned i = 0; i < N; i++) {
+ fifo.emplace_back(i, &constructed, &destructed);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), N);
+ BOOST_REQUIRE_EQUAL(constructed, N);
+ BOOST_REQUIRE_EQUAL(destructed, 0u);
+ for (unsigned i = 0 ; i < N; i++) {
+ BOOST_REQUIRE_EQUAL(fifo.front().val, static_cast<int>(i));
+ BOOST_REQUIRE_EQUAL(fifo.size(), N-i);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(destructed, i+1);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ // Check that destructing a fifo also destructs the objects it still
+ // contains
+ constructed = destructed = 0;
+ {
+ chunked_fifo<typ> fifo;
+ for (unsigned i = 0; i < N; i++) {
+ fifo.emplace_back(i, &constructed, &destructed);
+ BOOST_REQUIRE_EQUAL(fifo.front().val, 0);
+ BOOST_REQUIRE_EQUAL(fifo.size(), i+1);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(constructed, i+1);
+ BOOST_REQUIRE_EQUAL(destructed, 0u);
+ }
+ }
+ BOOST_REQUIRE_EQUAL(constructed, N);
+ BOOST_REQUIRE_EQUAL(destructed, N);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_construct_fail) {
+ // Check that if we fail to construct the item pushed, the queue remains
+ // empty.
+ class my_exception {};
+ struct typ {
+ typ() {
+ throw my_exception();
+ }
+ };
+ chunked_fifo<typ> fifo;
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ try {
+ fifo.emplace_back();
+ } catch(my_exception) {
+ // expected, ignore
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_construct_fail2) {
+ // A slightly more elaborate test, with a chunk size of 2
+ // items, and the third addition failing, so the question is
+ // not whether empty() is wrong immediately, but whether after
+ // we pop the two items, it will become true or we'll be left
+ // with an empty chunk.
+ class my_exception {};
+ struct typ {
+ typ(bool fail) {
+ if (fail) {
+ throw my_exception();
+ }
+ }
+ };
+ chunked_fifo<typ, 2> fifo;
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ fifo.emplace_back(false);
+ fifo.emplace_back(false);
+ try {
+ fifo.emplace_back(true);
+ } catch(my_exception) {
+ // expected, ignore
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+// Enable the following to run some benchmarks on different queue options
+#if 0
+// Unfortunately, C++ lacks the trivial feature of converting a type's name,
+// in compile time, to a string (akin to the C preprocessor's "#" feature).
+// Here is a neat trick to replace it - use typeinfo<T>::name() or
+// type_name<T>() to get a constant string name of the type.
+#include <cxxabi.h>
+template <typename T>
+class typeinfo {
+private:
+ static const char *_name;
+public:
+ static const char *name() {
+ int status;
+ if (!_name)
+ _name = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
+ return _name;
+ }
+};
+template<typename T> const char *typeinfo<T>::_name = nullptr;
+template<typename T> const char *type_name() {
+ return typeinfo<T>::name();
+}
+
+
+template<typename FIFO_TYPE> void
+benchmark_random_push_pop() {
+ // A test involving a random sequence of pushes and pops. Because the
+ // random walk is bounded the 0 end (the queue cannot be popped after
+ // being empty), the queue's expected length at the end of the test is
+ // not zero.
+ // The test uses the same random sequence each time so can be used for
+ // benchmarking different queue implementations on the same sequence.
+ constexpr int N = 1'000'000'000;
+ FIFO_TYPE fifo;
+ unsigned int seed = 0;
+ int entropy = 0;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N; i++) {
+ if (!entropy) {
+ entropy = rand_r(&seed);
+ }
+ if (entropy & 1) {
+ fifo.push_back(i);
+ } else {
+ if (!fifo.empty()) {
+ fifo.pop_front();
+ }
+ }
+ entropy >>= 1;
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " random push-and-pop " << fifo.size() << " " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_push_pop() {
+ // A benchmark involving repeated push and then pop to a queue, which
+ // will have 0 or 1 items at all times.
+ constexpr int N = 1'000'000'000;
+ FIFO_TYPE fifo;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N; i++) {
+ fifo.push_back(1);
+ fifo.pop_front();
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " push-and-pop " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_push_pop_k() {
+ // A benchmark involving repeated pushes of a few items and then popping
+ // to a queue, which will have just one chunk (or 0) at all times.
+ constexpr int N = 1'000'000'000;
+ constexpr int K = 100;
+ FIFO_TYPE fifo;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N/K; i++) {
+ for(int j = 0; j < K; j++) {
+ fifo.push_back(j);
+ }
+ for(int j = 0; j < K; j++) {
+ fifo.pop_front();
+ }
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " push-and-pop-" << K << " " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_pushes_pops() {
+ // A benchmark of pushing a lot of items, and then popping all of them
+ constexpr int N = 100'000'000;
+ FIFO_TYPE fifo;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N; i++) {
+ fifo.push_back(1);
+ }
+ for (int i = 0; i < N; i++) {
+ fifo.pop_front();
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " push-all-then-pop-all " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_all() {
+ std::cerr << "\n --- " << type_name<FIFO_TYPE>() << ": \n";
+ benchmark_random_push_pop<FIFO_TYPE>();
+ benchmark_push_pop<FIFO_TYPE>();
+ benchmark_push_pop_k<FIFO_TYPE>();
+ benchmark_pushes_pops<FIFO_TYPE>();
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_benchmark) {
+ benchmark_all<chunked_fifo<int>>();
+ benchmark_all<circular_buffer<int>>();
+ benchmark_all<std::deque<int>>();
+ benchmark_all<std::list<int>>();
+}
+#endif
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_iterator) {
+ constexpr auto items_per_chunk = 8;
+ auto fifo = chunked_fifo<int, items_per_chunk>{};
+ auto reference = std::deque<int>{};
+
+ BOOST_REQUIRE(fifo.begin() == fifo.end());
+
+ for (int i = 0; i < items_per_chunk * 4; ++i) {
+ fifo.push_back(i);
+ reference.push_back(i);
+ BOOST_REQUIRE(std::equal(fifo.begin(), fifo.end(), reference.begin(), reference.end()));
+ }
+
+ for (int i = 0; i < items_per_chunk * 2; ++i) {
+ fifo.pop_front();
+ reference.pop_front();
+ BOOST_REQUIRE(std::equal(fifo.begin(), fifo.end(), reference.begin(), reference.end()));
+ }
+}
diff --git a/src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc b/src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc
new file mode 100644
index 000000000..d5fe6b814
--- /dev/null
+++ b/src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc
@@ -0,0 +1,173 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <deque>
+#include <random>
+#include <seastar/core/circular_buffer_fixed_capacity.hh>
+
+#include <boost/range/algorithm/sort.hpp>
+#include <boost/range/algorithm/equal.hpp>
+#include <boost/range/algorithm/reverse.hpp>
+
+using namespace seastar;
+
+using cb16_t = circular_buffer_fixed_capacity<int, 16>;
+
+struct int_with_stats {
+ int val;
+ unsigned *num_deleted;
+ unsigned *num_moved;
+ operator int() const { return val; }
+ int_with_stats(int val, unsigned* num_deleted, unsigned* num_moved)
+ : val(val), num_deleted(num_deleted), num_moved(num_moved) {}
+
+ ~int_with_stats() { ++(*num_deleted); }
+ int_with_stats(const int_with_stats&) = delete;
+ int_with_stats(int_with_stats&& o) noexcept : val(o.val), num_deleted(o.num_deleted), num_moved(o.num_moved) {
+ ++(*num_moved);
+ }
+ int_with_stats& operator=(int_with_stats&& o) noexcept {
+ this->~int_with_stats();
+ new (this) int_with_stats(std::move(o));
+ return *this;
+ }
+};
+
+BOOST_AUTO_TEST_CASE(test_edge_cases) {
+ unsigned num_deleted = 0;
+ unsigned num_moved = 0;
+ auto get_val = [&num_deleted, &num_moved] (int val) {
+ return int_with_stats{val, &num_deleted, &num_moved};
+ };
+
+ {
+ circular_buffer_fixed_capacity<int_with_stats, 16> cb;
+ BOOST_REQUIRE(cb.begin() == cb.end());
+ cb.push_front(get_val(3)); // underflows indexes
+ BOOST_REQUIRE_EQUAL(cb[0], 3);
+ BOOST_REQUIRE(cb.begin() < cb.end());
+ cb.push_back(get_val(4));
+ BOOST_REQUIRE_EQUAL(cb.size(), 2u);
+ BOOST_REQUIRE_EQUAL(cb[0], 3);
+ BOOST_REQUIRE_EQUAL(cb[1], 4);
+ cb.pop_back();
+ BOOST_REQUIRE_EQUAL(cb.back(), 3);
+ cb.push_front(get_val(1));
+ cb.pop_back();
+ BOOST_REQUIRE_EQUAL(cb.back(), 1);
+
+ BOOST_REQUIRE_EQUAL(num_deleted, 5);
+ BOOST_REQUIRE_EQUAL(num_moved, 3);
+
+ cb.push_front(get_val(0));
+ cb.push_back(get_val(2));
+ BOOST_REQUIRE_EQUAL(cb.size(), 3);
+ BOOST_REQUIRE_EQUAL(cb[0], 0);
+ BOOST_REQUIRE_EQUAL(cb[1], 1);
+ BOOST_REQUIRE_EQUAL(cb[2], 2);
+ BOOST_REQUIRE_EQUAL(num_deleted, 7);
+ BOOST_REQUIRE_EQUAL(num_moved, 5);
+
+ circular_buffer_fixed_capacity<int_with_stats, 16> cb2 = std::move(cb);
+ BOOST_REQUIRE_EQUAL(cb2.size(), 3);
+ BOOST_REQUIRE_EQUAL(cb2[0], 0);
+ BOOST_REQUIRE_EQUAL(cb2[1], 1);
+ BOOST_REQUIRE_EQUAL(cb2[2], 2);
+ BOOST_REQUIRE_EQUAL(num_deleted, 7);
+ BOOST_REQUIRE_EQUAL(num_moved, 8);
+ }
+ BOOST_REQUIRE_EQUAL(num_deleted, 13);
+ BOOST_REQUIRE_EQUAL(num_moved, 8);
+}
+
+using deque = std::deque<int>;
+
+BOOST_AUTO_TEST_CASE(test_random_walk) {
+ auto rand = std::default_random_engine();
+ auto op_gen = std::uniform_int_distribution<unsigned>(0, 6);
+ deque d;
+ cb16_t c;
+ for (auto i = 0; i != 1000000; ++i) {
+ auto op = op_gen(rand);
+ switch (op) {
+ case 0:
+ if (d.size() < 16) {
+ auto n = rand();
+ c.push_back(n);
+ d.push_back(n);
+ }
+ break;
+ case 1:
+ if (d.size() < 16) {
+ auto n = rand();
+ c.push_front(n);
+ d.push_front(n);
+ }
+ break;
+ case 2:
+ if (!d.empty()) {
+ auto n = d.back();
+ auto m = c.back();
+ BOOST_REQUIRE_EQUAL(n, m);
+ c.pop_back();
+ d.pop_back();
+ }
+ break;
+ case 3:
+ if (!d.empty()) {
+ auto n = d.front();
+ auto m = c.front();
+ BOOST_REQUIRE_EQUAL(n, m);
+ c.pop_front();
+ d.pop_front();
+ }
+ break;
+ case 4:
+ boost::sort(c);
+ boost::sort(d);
+ break;
+ case 5:
+ if (!d.empty()) {
+ auto u = std::uniform_int_distribution<size_t>(0, d.size() - 1);
+ auto idx = u(rand);
+ auto m = c[idx];
+ auto n = c[idx];
+ BOOST_REQUIRE_EQUAL(m, n);
+ }
+ break;
+ case 6:
+ c.clear();
+ d.clear();
+ break;
+ case 7:
+ boost::reverse(c);
+ boost::reverse(d);
+ default:
+ abort();
+ }
+ BOOST_REQUIRE_EQUAL(c.size(), d.size());
+ BOOST_REQUIRE(boost::equal(c, d));
+ }
+}
diff --git a/src/seastar/tests/unit/circular_buffer_test.cc b/src/seastar/tests/unit/circular_buffer_test.cc
new file mode 100644
index 000000000..9de0af368
--- /dev/null
+++ b/src/seastar/tests/unit/circular_buffer_test.cc
@@ -0,0 +1,109 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <stdlib.h>
+#include <chrono>
+#include <deque>
+#include <seastar/core/circular_buffer.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(test_erasing) {
+ circular_buffer<int> buf;
+
+ buf.push_back(3);
+ buf.erase(buf.begin(), buf.end());
+
+ BOOST_REQUIRE(buf.size() == 0);
+ BOOST_REQUIRE(buf.empty());
+
+ buf.push_back(1);
+ buf.push_back(2);
+ buf.push_back(3);
+ buf.push_back(4);
+ buf.push_back(5);
+
+ buf.erase(std::remove_if(buf.begin(), buf.end(), [] (int v) { return (v & 1) == 0; }), buf.end());
+
+ BOOST_REQUIRE(buf.size() == 3);
+ BOOST_REQUIRE(!buf.empty());
+ {
+ auto i = buf.begin();
+ BOOST_REQUIRE_EQUAL(*i++, 1);
+ BOOST_REQUIRE_EQUAL(*i++, 3);
+ BOOST_REQUIRE_EQUAL(*i++, 5);
+ BOOST_REQUIRE(i == buf.end());
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_erasing_at_beginning_or_end_does_not_invalidate_iterators) {
+ // This guarantee comes from std::deque, which circular_buffer is supposed to mimic.
+
+ circular_buffer<int> buf;
+
+ buf.push_back(1);
+ buf.push_back(2);
+ buf.push_back(3);
+ buf.push_back(4);
+ buf.push_back(5);
+
+ int* ptr_to_3 = &buf[2];
+ auto iterator_to_3 = buf.begin() + 2;
+ assert(*ptr_to_3 == 3);
+ assert(*iterator_to_3 == 3);
+
+ buf.erase(buf.begin(), buf.begin() + 2);
+
+ BOOST_REQUIRE(*ptr_to_3 == 3);
+ BOOST_REQUIRE(*iterator_to_3 == 3);
+
+ buf.erase(buf.begin() + 1, buf.end());
+
+ BOOST_REQUIRE(*ptr_to_3 == 3);
+ BOOST_REQUIRE(*iterator_to_3 == 3);
+
+ BOOST_REQUIRE(buf.size() == 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_erasing_in_the_middle) {
+ circular_buffer<int> buf;
+
+ for (int i = 0; i < 10; ++i) {
+ buf.push_back(i);
+ }
+
+ auto i = buf.erase(buf.begin() + 3, buf.begin() + 6);
+ BOOST_REQUIRE_EQUAL(*i, 6);
+
+ i = buf.begin();
+ BOOST_REQUIRE_EQUAL(*i++, 0);
+ BOOST_REQUIRE_EQUAL(*i++, 1);
+ BOOST_REQUIRE_EQUAL(*i++, 2);
+ BOOST_REQUIRE_EQUAL(*i++, 6);
+ BOOST_REQUIRE_EQUAL(*i++, 7);
+ BOOST_REQUIRE_EQUAL(*i++, 8);
+ BOOST_REQUIRE_EQUAL(*i++, 9);
+ BOOST_REQUIRE(i == buf.end());
+}
diff --git a/src/seastar/tests/unit/connect_test.cc b/src/seastar/tests/unit/connect_test.cc
new file mode 100644
index 000000000..e4b3b4728
--- /dev/null
+++ b/src/seastar/tests/unit/connect_test.cc
@@ -0,0 +1,74 @@
+#include <seastar/core/reactor.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/net/ip.hh>
+
+using namespace seastar;
+using namespace net;
+
+SEASTAR_TEST_CASE(test_connection_attempt_is_shutdown) {
+ ipv4_addr server_addr("172.16.0.1");
+ auto unconn = make_socket();
+ auto f = unconn
+ .connect(make_ipv4_address(server_addr))
+ .then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+ unconn.shutdown();
+ return f.finally([unconn = std::move(unconn)] {});
+}
+
+SEASTAR_TEST_CASE(test_unconnected_socket_shutsdown_established_connection) {
+ // Use a random port to reduce chance of conflict.
+ // TODO: retry a few times on failure.
+ std::default_random_engine& rnd = testing::local_random_engine;
+ auto distr = std::uniform_int_distribution<uint16_t>(12000, 65000);
+ auto sa = make_ipv4_address({"127.0.0.1", distr(rnd)});
+ return do_with(engine().net().listen(sa, listen_options()), [sa] (auto& listener) {
+ auto f = listener.accept();
+ auto unconn = make_socket();
+ auto connf = unconn.connect(sa);
+ return connf.then([unconn = std::move(unconn)] (auto&& conn) mutable {
+ unconn.shutdown();
+ return do_with(std::move(conn), [] (auto& conn) {
+ return do_with(conn.output(1), [] (auto& out) {
+ return out.write("ping").then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+ });
+ });
+ }).finally([f = std::move(f)] () mutable {
+ return std::move(f);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_accept_after_abort) {
+ std::default_random_engine& rnd = testing::local_random_engine;
+ auto distr = std::uniform_int_distribution<uint16_t>(12000, 65000);
+ auto sa = make_ipv4_address({"127.0.0.1", distr(rnd)});
+ return do_with(seastar::server_socket(engine().net().listen(sa, listen_options())), [] (auto& listener) {
+ using ftype = future<accept_result>;
+ promise<ftype> p;
+ future<ftype> done = p.get_future();
+ auto f = listener.accept().then_wrapped([&listener, p = std::move(p)] (auto f) mutable {
+ f.ignore_ready_future();
+ p.set_value(listener.accept());
+ });
+ listener.abort_accept();
+ return done.then([] (ftype f) {
+ return f.then_wrapped([] (ftype f) {
+ BOOST_REQUIRE(f.failed());
+ if (f.available()) {
+ f.ignore_ready_future();
+ }
+ });
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/coroutines_test.cc b/src/seastar/tests/unit/coroutines_test.cc
new file mode 100644
index 000000000..b4b3bb2c2
--- /dev/null
+++ b/src/seastar/tests/unit/coroutines_test.cc
@@ -0,0 +1,153 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 ScyllaDB Ltd.
+ */
+
+#include <seastar/core/future-util.hh>
+#include <seastar/testing/test_case.hh>
+
+using namespace seastar;
+
+#ifndef SEASTAR_COROUTINES_ENABLED
+
+SEASTAR_TEST_CASE(test_coroutines_not_compiled_in) {
+ return make_ready_future<>();
+}
+
+#else
+
+#include <seastar/core/coroutine.hh>
+
+namespace {
+
+future<int> old_fashioned_continuations() {
+ return later().then([] {
+ return 42;
+ });
+}
+
+future<int> simple_coroutine() {
+ co_await later();
+ co_return 53;
+}
+
+future<int> ready_coroutine() {
+ co_return 64;
+}
+
+future<std::tuple<int, double>> tuple_coroutine() {
+ co_return std::tuple(1, 2.);
+}
+
+future<int> failing_coroutine() {
+ co_await later();
+ throw 42;
+}
+
+}
+
+SEASTAR_TEST_CASE(test_simple_coroutines) {
+ BOOST_REQUIRE_EQUAL(co_await old_fashioned_continuations(), 42);
+ BOOST_REQUIRE_EQUAL(co_await simple_coroutine(), 53);
+ BOOST_REQUIRE_EQUAL(ready_coroutine().get0(), 64);
+ BOOST_REQUIRE(co_await tuple_coroutine() == std::tuple(1, 2.));
+ BOOST_REQUIRE_EXCEPTION((void)co_await failing_coroutine(), int, [] (auto v) { return v == 42; });
+}
+
+
+future<> forwarding_return_coroutine_1(bool& x) {
+ co_return
+// Clang complains if both return_value and return_void are defined
+#if defined(__clang__)
+ co_await
+#endif
+ later().then([&x] {
+ x = true;
+ });
+}
+
+future<int> forwarding_return_coroutine_2() {
+ co_return later().then([] {
+ return 3;
+ });
+}
+
+SEASTAR_TEST_CASE(test_forwarding_return) {
+ bool x = false;
+ co_await forwarding_return_coroutine_1(x);
+ BOOST_REQUIRE(x);
+ auto y = co_await forwarding_return_coroutine_2();
+ BOOST_REQUIRE_EQUAL(y, 3);
+}
+
+SEASTAR_TEST_CASE(test_abandond_coroutine) {
+ std::optional<future<int>> f;
+ {
+ auto p1 = promise<>();
+ auto p2 = promise<>();
+ auto p3 = promise<>();
+ f = p1.get_future().then([&] () -> future<int> {
+ p2.set_value();
+ BOOST_CHECK_THROW(co_await p3.get_future(), broken_promise);
+ co_return 1;
+ });
+ p1.set_value();
+ co_await p2.get_future();
+ }
+ BOOST_CHECK_EQUAL(co_await std::move(*f), 1);
+}
+
+SEASTAR_TEST_CASE(test_scheduling_group) {
+ auto other_sg = co_await create_scheduling_group("the other group", 10.f);
+
+ auto p1 = promise<>();
+ auto p2 = promise<>();
+
+ auto p1b = promise<>();
+ auto p2b = promise<>();
+ auto f1 = p1b.get_future();
+ auto f2 = p2b.get_future();
+
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+ auto f_ret = with_scheduling_group(other_sg,
+ [other_sg_cap = other_sg] (future<> f1, future<> f2, promise<> p1, promise<> p2) -> future<int> {
+ // Make a copy in the coroutine before the lambda is destroyed.
+ auto other_sg = other_sg_cap;
+ BOOST_REQUIRE(current_scheduling_group() == other_sg);
+ BOOST_REQUIRE(other_sg == other_sg);
+ p1.set_value();
+ co_await std::move(f1);
+ BOOST_REQUIRE(current_scheduling_group() == other_sg);
+ p2.set_value();
+ co_await std::move(f2);
+ BOOST_REQUIRE(current_scheduling_group() == other_sg);
+ co_return 42;
+ }, p1.get_future(), p2.get_future(), std::move(p1b), std::move(p2b));
+
+ co_await std::move(f1);
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+ p1.set_value();
+ co_await std::move(f2);
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+ p2.set_value();
+ BOOST_REQUIRE_EQUAL(co_await std::move(f_ret), 42);
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+}
+
+#endif
diff --git a/src/seastar/tests/unit/defer_test.cc b/src/seastar/tests/unit/defer_test.cc
new file mode 100644
index 000000000..b79245694
--- /dev/null
+++ b/src/seastar/tests/unit/defer_test.cc
@@ -0,0 +1,76 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/util/defer.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(test_defer_does_not_run_when_canceled) {
+ bool ran = false;
+ {
+ auto d = defer([&] {
+ ran = true;
+ });
+ d.cancel();
+ }
+ BOOST_REQUIRE(!ran);
+}
+
+BOOST_AUTO_TEST_CASE(test_defer_runs) {
+ bool ran = false;
+ {
+ auto d = defer([&] {
+ ran = true;
+ });
+ }
+ BOOST_REQUIRE(ran);
+}
+
+BOOST_AUTO_TEST_CASE(test_defer_runs_once_when_moved) {
+ int ran = 0;
+ {
+ auto d = defer([&] {
+ ++ran;
+ });
+ {
+ auto d2 = std::move(d);
+ }
+ BOOST_REQUIRE_EQUAL(1, ran);
+ }
+ BOOST_REQUIRE_EQUAL(1, ran);
+}
+
+BOOST_AUTO_TEST_CASE(test_defer_does_not_run_when_moved_after_cancelled) {
+ int ran = 0;
+ {
+ auto d = defer([&] {
+ ++ran;
+ });
+ d.cancel();
+ {
+ auto d2 = std::move(d);
+ }
+ }
+ BOOST_REQUIRE_EQUAL(0, ran);
+}
diff --git a/src/seastar/tests/unit/deleter_test.cc b/src/seastar/tests/unit/deleter_test.cc
new file mode 100644
index 000000000..a9a0c5795
--- /dev/null
+++ b/src/seastar/tests/unit/deleter_test.cc
@@ -0,0 +1,79 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Lightbits Labs Ltd. - All Rights Reserved
+*/
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/deleter.hh>
+
+using namespace seastar;
+
+struct TestObject {
+ TestObject() : has_ref(true){}
+ TestObject(TestObject&& other) {
+ has_ref = true;
+ other.has_ref = false;
+ }
+ ~TestObject() {
+ if (has_ref) {
+ ++deletions_called;
+ }
+ }
+ static int deletions_called;
+ int has_ref;
+};
+int TestObject::deletions_called = 0;
+
+BOOST_AUTO_TEST_CASE(test_deleter_append_does_not_free_shared_object) {
+ {
+ deleter tested;
+ {
+ auto obj1 = TestObject();
+ deleter del1 = make_object_deleter(std::move(obj1));
+ auto obj2 = TestObject();
+ deleter del2 = make_object_deleter(std::move(obj2));
+ del1.append(std::move(del2));
+ tested = del1.share();
+ auto obj3 = TestObject();
+ deleter del3 = make_object_deleter(std::move(obj3));
+ del1.append(std::move(del3));
+ }
+ // since deleter tested still holds references to first two objects, last objec should be deleted
+ BOOST_REQUIRE(TestObject::deletions_called == 1);
+ }
+ BOOST_REQUIRE(TestObject::deletions_called == 3);
+}
+
+BOOST_AUTO_TEST_CASE(test_deleter_append_same_shared_object_twice) {
+ TestObject::deletions_called = 0;
+ {
+ deleter tested;
+ {
+ deleter del1 = make_object_deleter(TestObject());
+ auto del2 = del1.share();
+
+ tested.append(std::move(del1));
+ tested.append(std::move(del2));
+ }
+ BOOST_REQUIRE(TestObject::deletions_called == 0);
+ }
+ BOOST_REQUIRE(TestObject::deletions_called == 1);
+}
diff --git a/src/seastar/tests/unit/directory_test.cc b/src/seastar/tests/unit/directory_test.cc
new file mode 100644
index 000000000..3f7f540d2
--- /dev/null
+++ b/src/seastar/tests/unit/directory_test.cc
@@ -0,0 +1,86 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/shared_ptr.hh>
+
+using namespace seastar;
+
+const char* de_type_desc(directory_entry_type t)
+{
+ switch (t) {
+ case directory_entry_type::unknown:
+ return "unknown";
+ case directory_entry_type::block_device:
+ return "block_device";
+ case directory_entry_type::char_device:
+ return "char_device";
+ case directory_entry_type::directory:
+ return "directory";
+ case directory_entry_type::fifo:
+ return "fifo";
+ case directory_entry_type::link:
+ return "link";
+ case directory_entry_type::regular:
+ return "regular";
+ case directory_entry_type::socket:
+ return "socket";
+ }
+ assert(0 && "should not get here");
+ return nullptr;
+}
+
+int main(int ac, char** av) {
+ class lister {
+ file _f;
+ subscription<directory_entry> _listing;
+ public:
+ lister(file f)
+ : _f(std::move(f))
+ , _listing(_f.list_directory([this] (directory_entry de) { return report(de); })) {
+ }
+ future<> done() { return _listing.done(); }
+ private:
+ future<> report(directory_entry de) {
+ return file_stat(de.name, follow_symlink::no).then([de = std::move(de)] (stat_data sd) {
+ if (de.type) {
+ assert(*de.type == sd.type);
+ } else {
+ assert(sd.type == directory_entry_type::unknown);
+ }
+ fmt::print("{} (type={})\n", de.name, de_type_desc(sd.type));
+ return make_ready_future<>();
+ });
+ }
+ };
+ return app_template().run(ac, av, [] {
+ return engine().open_directory(".").then([] (file f) {
+ return do_with(lister(std::move(f)), [] (lister& l) {
+ return l.done().then([] {
+ return 0;
+ });
+ });
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/distributed_test.cc b/src/seastar/tests/unit/distributed_test.cc
new file mode 100644
index 000000000..497bf3c7d
--- /dev/null
+++ b/src/seastar/tests/unit/distributed_test.cc
@@ -0,0 +1,318 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/distributed.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/print.hh>
+#include <seastar/util/defer.hh>
+#include <mutex>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+struct async_service : public seastar::async_sharded_service<async_service> {
+ thread_local static bool deleted;
+ ~async_service() {
+ deleted = true;
+ }
+ void run() {
+ auto ref = shared_from_this();
+ // Wait a while and check.
+ (void)sleep(std::chrono::milliseconds(100 + 100 * this_shard_id())).then([this, ref] {
+ check();
+ });
+ }
+ virtual void check() {
+ assert(!deleted);
+ }
+ future<> stop() { return make_ready_future<>(); }
+};
+
+thread_local bool async_service::deleted = false;
+
+struct X {
+ sstring echo(sstring arg) {
+ return arg;
+ }
+ int cpu_id_squared() const {
+ auto id = this_shard_id();
+ return id * id;
+ }
+ future<> stop() { return make_ready_future<>(); }
+};
+
+template <typename T, typename Func>
+future<> do_with_distributed(Func&& func) {
+ auto x = make_shared<distributed<T>>();
+ return func(*x).finally([x] {
+ return x->stop();
+ }).finally([x]{});
+}
+
+SEASTAR_TEST_CASE(test_that_each_core_gets_the_arguments) {
+ return do_with_distributed<X>([] (auto& x) {
+ return x.start().then([&x] {
+ return x.map_reduce([] (sstring msg){
+ if (msg != "hello") {
+ throw std::runtime_error("wrong message");
+ }
+ }, &X::echo, sstring("hello"));
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_functor_version) {
+ return do_with_distributed<X>([] (auto& x) {
+ return x.start().then([&x] {
+ return x.map_reduce([] (sstring msg){
+ if (msg != "hello") {
+ throw std::runtime_error("wrong message");
+ }
+ }, [] (X& x) { return x.echo("hello"); });
+ });
+ });
+}
+
+struct Y {
+ sstring s;
+ Y(sstring s) : s(std::move(s)) {}
+ future<> stop() { return make_ready_future<>(); }
+};
+
+SEASTAR_TEST_CASE(test_constructor_argument_is_passed_to_each_core) {
+ return do_with_distributed<Y>([] (auto& y) {
+ return y.start(sstring("hello")).then([&y] {
+ return y.invoke_on_all([] (Y& y) {
+ if (y.s != "hello") {
+ throw std::runtime_error(format("expected message mismatch, is \"%s\"", y.s));
+ }
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce) {
+ return do_with_distributed<X>([] (distributed<X>& x) {
+ return x.start().then([&x] {
+ return x.map_reduce0(std::mem_fn(&X::cpu_id_squared),
+ 0,
+ std::plus<int>()).then([] (int result) {
+ int n = smp::count - 1;
+ if (result != (n * (n + 1) * (2*n + 1)) / 6) {
+ throw std::runtime_error("map_reduce failed");
+ }
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_async) {
+ return do_with_distributed<async_service>([] (distributed<async_service>& x) {
+ return x.start().then([&x] {
+ return x.invoke_on_all(&async_service::run);
+ });
+ }).then([] {
+ return sleep(std::chrono::milliseconds(100 * (smp::count + 1)));
+ });
+}
+
+SEASTAR_TEST_CASE(test_invoke_on_others) {
+ return seastar::async([] {
+ struct my_service {
+ int counter = 0;
+ void up() { ++counter; }
+ future<> stop() { return make_ready_future<>(); }
+ };
+ for (unsigned c = 0; c < smp::count; ++c) {
+ smp::submit_to(c, [c] {
+ return seastar::async([c] {
+ sharded<my_service> s;
+ s.start().get();
+ s.invoke_on_others([](auto& s) { s.up(); }).get();
+ if (s.local().counter != 0) {
+ throw std::runtime_error("local modified");
+ }
+ s.invoke_on_all([c](auto& remote) {
+ if (this_shard_id() != c) {
+ if (remote.counter != 1) {
+ throw std::runtime_error("remote not modified");
+ }
+ }
+ }).get();
+ s.stop().get();
+ });
+ }).get();
+ }
+ });
+}
+
+
+struct remote_worker {
+ unsigned current = 0;
+ unsigned max_concurrent_observed = 0;
+ unsigned expected_max;
+ semaphore sem{0};
+ remote_worker(unsigned expected_max) : expected_max(expected_max) {
+ }
+ future<> do_work() {
+ ++current;
+ max_concurrent_observed = std::max(current, max_concurrent_observed);
+ if (max_concurrent_observed >= expected_max && sem.current() == 0) {
+ sem.signal(semaphore::max_counter());
+ }
+ return sem.wait().then([this] {
+ // Sleep a bit to check if the concurrency goes over the max
+ return sleep(100ms).then([this] {
+ max_concurrent_observed = std::max(current, max_concurrent_observed);
+ --current;
+ });
+ });
+ }
+ future<> do_remote_work(shard_id t, smp_service_group ssg) {
+ return smp::submit_to(t, ssg, [this] {
+ return do_work();
+ });
+ }
+};
+
+SEASTAR_TEST_CASE(test_smp_service_groups) {
+ return async([] {
+ smp_service_group_config ssgc1;
+ ssgc1.max_nonlocal_requests = 1;
+ auto ssg1 = create_smp_service_group(ssgc1).get0();
+ smp_service_group_config ssgc2;
+ ssgc2.max_nonlocal_requests = 1000;
+ auto ssg2 = create_smp_service_group(ssgc2).get0();
+ shard_id other_shard = smp::count - 1;
+ remote_worker rm1(1);
+ remote_worker rm2(1000);
+ auto bunch1 = parallel_for_each(boost::irange(0, 20), [&] (int ignore) { return rm1.do_remote_work(other_shard, ssg1); });
+ auto bunch2 = parallel_for_each(boost::irange(0, 2000), [&] (int ignore) { return rm2.do_remote_work(other_shard, ssg2); });
+ bunch1.get();
+ bunch2.get();
+ if (smp::count > 1) {
+ assert(rm1.max_concurrent_observed == 1);
+ assert(rm2.max_concurrent_observed == 1000);
+ }
+ destroy_smp_service_group(ssg1).get();
+ destroy_smp_service_group(ssg2).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_smp_service_groups_re_construction) {
+ // During development of the feature, we saw a bug where the vector
+ // holding the groups did not expand correctly. This test triggers the
+ // bug.
+ return async([] {
+ auto ssg1 = create_smp_service_group({}).get0();
+ auto ssg2 = create_smp_service_group({}).get0();
+ destroy_smp_service_group(ssg1).get();
+ auto ssg3 = create_smp_service_group({}).get0();
+ destroy_smp_service_group(ssg2).get();
+ destroy_smp_service_group(ssg3).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_smp_timeout) {
+ return async([] {
+ smp_service_group_config ssgc1;
+ ssgc1.max_nonlocal_requests = 1;
+ auto ssg1 = create_smp_service_group(ssgc1).get0();
+
+ auto _ = defer([ssg1] {
+ destroy_smp_service_group(ssg1).get();
+ });
+
+ const shard_id other_shard = smp::count - 1;
+
+ // Ugly but beats using sleeps.
+ std::mutex mut;
+ std::unique_lock<std::mutex> lk(mut);
+
+ // Submitted to the remote shard.
+ auto fut1 = smp::submit_to(other_shard, ssg1, [&mut] {
+ std::cout << "Running request no. 1" << std::endl;
+ std::unique_lock<std::mutex> lk(mut);
+ std::cout << "Request no. 1 done" << std::endl;
+ });
+ // Consume the only unit from the semaphore.
+ auto fut2 = smp::submit_to(other_shard, ssg1, [] {
+ std::cout << "Running request no. 2 - done" << std::endl;
+ });
+
+ auto fut_timedout = smp::submit_to(other_shard, smp_submit_to_options(ssg1, smp_timeout_clock::now() + 10ms), [] {
+ std::cout << "Running timed-out request - done" << std::endl;
+ });
+
+ {
+ auto notify = defer([lk = std::move(lk)] { });
+
+ try {
+ fut_timedout.get();
+ throw std::runtime_error("smp::submit_to() didn't timeout as expected");
+ } catch (semaphore_timed_out& e) {
+ std::cout << "Expected timeout received: " << e.what() << std::endl;
+ } catch (...) {
+ std::throw_with_nested(std::runtime_error("smp::submit_to() failed with unexpected exception"));
+ }
+ }
+
+ fut1.get();
+ fut2.get();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_sharded_parameter) {
+ struct dependency {
+ unsigned val = this_shard_id() * 7;
+ };
+ struct some_service {
+ bool ok = false;
+ some_service(unsigned non_shard_dependent, unsigned shard_dependent, dependency& dep, unsigned shard_dependent_2) {
+ ok =
+ non_shard_dependent == 43
+ && shard_dependent == this_shard_id() * 3
+ && dep.val == this_shard_id() * 7
+ && shard_dependent_2 == -dep.val;
+ }
+ };
+ sharded<dependency> s_dep;
+ s_dep.start().get();
+ auto undo1 = defer([&] { s_dep.stop().get(); });
+
+ sharded<some_service> s_service;
+ s_service.start(
+ 43, // should be copied verbatim
+ sharded_parameter([] { return this_shard_id() * 3; }),
+ std::ref(s_dep),
+ sharded_parameter([] (dependency& d) { return -d.val; }, std::ref(s_dep))
+ ).get();
+ auto undo2 = defer([&] { s_service.stop().get(); });
+
+ auto all_ok = s_service.map_reduce0(std::mem_fn(&some_service::ok), true, std::multiplies<>()).get0();
+ BOOST_REQUIRE(all_ok);
+}
diff --git a/src/seastar/tests/unit/dns_test.cc b/src/seastar/tests/unit/dns_test.cc
new file mode 100644
index 000000000..ff25c453c
--- /dev/null
+++ b/src/seastar/tests/unit/dns_test.cc
@@ -0,0 +1,131 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB.
+ */
+#include <vector>
+#include <algorithm>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/net/dns.hh>
+#include <seastar/net/inet_address.hh>
+
+using namespace seastar;
+using namespace seastar::net;
+
+static const sstring seastar_name = "seastar.io";
+
+static future<> test_resolve(dns_resolver::options opts) {
+ auto d = ::make_lw_shared<dns_resolver>(std::move(opts));
+ return d->get_host_by_name(seastar_name, inet_address::family::INET).then([d](hostent e) {
+ return d->get_host_by_addr(e.addr_list.front()).then([d, a = e.addr_list.front()](hostent e) {
+ return d->get_host_by_name(e.names.front(), inet_address::family::INET).then([a](hostent e) {
+ BOOST_REQUIRE(std::count(e.addr_list.begin(), e.addr_list.end(), a));
+ });
+ });
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+static future<> test_bad_name(dns_resolver::options opts) {
+ auto d = ::make_lw_shared<dns_resolver>(std::move(opts));
+ return d->get_host_by_name("apa.ninja.gnu", inet_address::family::INET).then_wrapped([d](future<hostent> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not succeed");
+ } catch (...) {
+ // ok.
+ }
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+SEASTAR_TEST_CASE(test_resolve_udp) {
+ return test_resolve(dns_resolver::options());
+}
+
+SEASTAR_TEST_CASE(test_bad_name_udp) {
+ return test_bad_name(dns_resolver::options());
+}
+
+SEASTAR_TEST_CASE(test_timeout_udp) {
+ dns_resolver::options opts;
+ opts.servers = std::vector<inet_address>({ inet_address("1.2.3.4") }); // not a server
+ opts.udp_port = 29953; // not a dns port
+ opts.timeout = std::chrono::milliseconds(500);
+
+ auto d = ::make_lw_shared<dns_resolver>(engine().net(), opts);
+ return d->get_host_by_name(seastar_name, inet_address::family::INET).then_wrapped([d](future<hostent> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not succeed");
+ } catch (...) {
+ // ok.
+ }
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+// Currently failing, disable until fixed (#521)
+#if 0
+SEASTAR_TEST_CASE(test_resolve_tcp) {
+ dns_resolver::options opts;
+ opts.use_tcp_query = true;
+ return test_resolve(opts);
+}
+#endif
+
+SEASTAR_TEST_CASE(test_bad_name_tcp) {
+ dns_resolver::options opts;
+ opts.use_tcp_query = true;
+ return test_bad_name(opts);
+}
+
+static const sstring imaps_service = "imaps";
+static const sstring gmail_domain = "gmail.com";
+
+static future<> test_srv() {
+ auto d = ::make_lw_shared<dns_resolver>();
+ return d->get_srv_records(dns_resolver::srv_proto::tcp,
+ imaps_service,
+ gmail_domain).then([d](dns_resolver::srv_records records) {
+ BOOST_REQUIRE(!records.empty());
+ for (auto& record : records) {
+ // record.target should end with "gmail.com"
+ BOOST_REQUIRE_GT(record.target.size(), gmail_domain.size());
+ BOOST_REQUIRE_EQUAL(record.target.compare(record.target.size() - gmail_domain.size(),
+ gmail_domain.size(),
+ gmail_domain),
+ 0);
+ }
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+SEASTAR_TEST_CASE(test_srv_tcp) {
+ return test_srv();
+}
diff --git a/src/seastar/tests/unit/execution_stage_test.cc b/src/seastar/tests/unit/execution_stage_test.cc
new file mode 100644
index 000000000..e74219c89
--- /dev/null
+++ b/src/seastar/tests/unit/execution_stage_test.cc
@@ -0,0 +1,334 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+#include <algorithm>
+#include <vector>
+#include <chrono>
+
+#include <seastar/core/thread.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/execution_stage.hh>
+#include <seastar/core/sleep.hh>
+
+using namespace std::chrono_literals;
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(test_create_stage_from_lvalue_function_object) {
+ return seastar::async([] {
+ auto dont_move = [obj = make_shared<int>(53)] { return *obj; };
+ auto stage = seastar::make_execution_stage("test", dont_move);
+ BOOST_REQUIRE_EQUAL(stage().get0(), 53);
+ BOOST_REQUIRE_EQUAL(dont_move(), 53);
+ });
+}
+
+SEASTAR_TEST_CASE(test_create_stage_from_rvalue_function_object) {
+ return seastar::async([] {
+ auto dont_copy = [obj = std::make_unique<int>(42)] { return *obj; };
+ auto stage = seastar::make_execution_stage("test", std::move(dont_copy));
+ BOOST_REQUIRE_EQUAL(stage().get0(), 42);
+ });
+}
+
+int func() {
+ return 64;
+}
+
+SEASTAR_TEST_CASE(test_create_stage_from_function) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", func);
+ BOOST_REQUIRE_EQUAL(stage().get0(), 64);
+ });
+}
+
+template<typename Function, typename Verify>
+void test_simple_execution_stage(Function&& func, Verify&& verify) {
+ auto stage = seastar::make_execution_stage("test", std::forward<Function>(func));
+
+ std::vector<int> vs;
+ std::default_random_engine& gen = testing::local_random_engine;
+ std::uniform_int_distribution<> dist(0, 100'000);
+ std::generate_n(std::back_inserter(vs), 1'000, [&] { return dist(gen); });
+
+ std::vector<future<int>> fs;
+ for (auto v : vs) {
+ fs.emplace_back(stage(v));
+ }
+
+ for (auto i = 0u; i < fs.size(); i++) {
+ verify(vs[i], std::move(fs[i]));
+ }
+}
+
+SEASTAR_TEST_CASE(test_simple_stage_returning_int) {
+ return seastar::async([] {
+ test_simple_execution_stage([] (int x) {
+ if (x % 2) {
+ return x * 2;
+ } else {
+ throw x;
+ }
+ }, [] (int original, future<int> result) {
+ if (original % 2) {
+ BOOST_REQUIRE_EQUAL(original * 2, result.get0());
+ } else {
+ BOOST_REQUIRE_EXCEPTION(result.get0(), int, [&] (int v) { return original == v; });
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_simple_stage_returning_future_int) {
+ return seastar::async([] {
+ test_simple_execution_stage([] (int x) {
+ if (x % 2) {
+ return make_ready_future<int>(x * 2);
+ } else {
+ return make_exception_future<int>(x);
+ }
+ }, [] (int original, future<int> result) {
+ if (original % 2) {
+ BOOST_REQUIRE_EQUAL(original * 2, result.get0());
+ } else {
+ BOOST_REQUIRE_EXCEPTION(result.get0(), int, [&] (int v) { return original == v; });
+ }
+ });
+ });
+}
+
+template<typename T>
+void test_execution_stage_avoids_copy() {
+ auto stage = seastar::make_execution_stage("test", [] (T obj) {
+ return std::move(obj);
+ });
+
+ auto f = stage(T());
+ T obj = f.get0();
+ (void)obj;
+}
+
+SEASTAR_TEST_CASE(test_stage_moves_when_cannot_copy) {
+ return seastar::async([] {
+ struct noncopyable_but_movable {
+ noncopyable_but_movable() = default;
+ noncopyable_but_movable(const noncopyable_but_movable&) = delete;
+ noncopyable_but_movable(noncopyable_but_movable&&) = default;
+ };
+
+ test_execution_stage_avoids_copy<noncopyable_but_movable>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_stage_prefers_move_to_copy) {
+ return seastar::async([] {
+ struct copyable_and_movable {
+ copyable_and_movable() = default;
+ copyable_and_movable(const copyable_and_movable&) {
+ BOOST_FAIL("should not copy");
+ }
+ copyable_and_movable(copyable_and_movable&&) = default;
+ };
+
+ test_execution_stage_avoids_copy<copyable_and_movable>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_rref_decays_to_value) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] (std::vector<int>&& vec) {
+ return vec.size();
+ });
+
+ std::vector<int> tmp;
+ std::vector<future<size_t>> fs;
+ for (auto i = 0; i < 100; i++) {
+ tmp.resize(i);
+ fs.emplace_back(stage(std::move(tmp)));
+ tmp = std::vector<int>();
+ }
+
+ for (size_t i = 0; i < 100; i++) {
+ BOOST_REQUIRE_EQUAL(fs[i].get0(), i);
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_lref_does_not_decay) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] (int& v) {
+ v++;
+ });
+
+ int value = 0;
+ std::vector<future<>> fs;
+ for (auto i = 0; i < 100; i++) {
+ //fs.emplace_back(stage(value)); // should fail to compile
+ fs.emplace_back(stage(seastar::ref(value)));
+ }
+
+ for (auto&& f : fs) {
+ f.get();
+ }
+ BOOST_REQUIRE_EQUAL(value, 100);
+ });
+}
+
+SEASTAR_TEST_CASE(test_explicit_reference_wrapper_is_not_unwrapped) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] (seastar::reference_wrapper<int> v) {
+ v.get()++;
+ });
+
+ int value = 0;
+ std::vector<future<>> fs;
+ for (auto i = 0; i < 100; i++) {
+ //fs.emplace_back(stage(value)); // should fail to compile
+ fs.emplace_back(stage(seastar::ref(value)));
+ }
+
+ for (auto&& f : fs) {
+ f.get();
+ }
+ BOOST_REQUIRE_EQUAL(value, 100);
+ });
+}
+
+SEASTAR_TEST_CASE(test_function_is_class_member) {
+ return seastar::async([] {
+ struct foo {
+ int value = -1;
+ int member(int x) {
+ return std::exchange(value, x);
+ }
+ };
+
+ auto stage = seastar::make_execution_stage("test", &foo::member);
+
+ foo object;
+ std::vector<future<int>> fs;
+ for (auto i = 0; i < 100; i++) {
+ fs.emplace_back(stage(&object, i));
+ }
+
+ for (auto i = 0; i < 100; i++) {
+ BOOST_REQUIRE_EQUAL(fs[i].get0(), i - 1);
+ }
+ BOOST_REQUIRE_EQUAL(object.value, 99);
+ });
+}
+
+SEASTAR_TEST_CASE(test_function_is_const_class_member) {
+ return seastar::async([] {
+ struct foo {
+ int value = 999;
+ int member() const {
+ return value;
+ }
+ };
+ auto stage = seastar::make_execution_stage("test", &foo::member);
+
+ const foo object;
+ BOOST_REQUIRE_EQUAL(stage(&object).get0(), 999);
+ });
+}
+
+SEASTAR_TEST_CASE(test_stage_stats) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] { });
+
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_enqueued, 0u);
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_executed, 0u);
+
+ auto fs = std::vector<future<>>();
+ static constexpr auto call_count = 53u;
+ for (auto i = 0u; i < call_count; i++) {
+ fs.emplace_back(stage());
+ }
+
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_enqueued, call_count);
+
+ for (auto i = 0u; i < call_count; i++) {
+ fs[i].get();
+ BOOST_REQUIRE_GE(stage.get_stats().tasks_scheduled, 1u);
+ BOOST_REQUIRE_GE(stage.get_stats().function_calls_executed, i);
+ }
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_executed, call_count);
+ });
+}
+
+SEASTAR_TEST_CASE(test_unique_stage_names_are_enforced) {
+ return seastar::async([] {
+ {
+ auto stage = seastar::make_execution_stage("test", [] {});
+ BOOST_REQUIRE_THROW(seastar::make_execution_stage("test", [] {}), std::invalid_argument);
+ stage().get();
+ }
+
+ auto stage = seastar::make_execution_stage("test", [] {});
+ stage().get();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_inheriting_concrete_execution_stage) {
+ auto sg1 = seastar::create_scheduling_group("sg1", 300).get0();
+ auto ksg1 = seastar::defer([&] { seastar::destroy_scheduling_group(sg1).get(); });
+ auto sg2 = seastar::create_scheduling_group("sg2", 100).get0();
+ auto ksg2 = seastar::defer([&] { seastar::destroy_scheduling_group(sg2).get(); });
+ auto check_sg = [] (seastar::scheduling_group sg) {
+ BOOST_REQUIRE(seastar::current_scheduling_group() == sg);
+ };
+ auto es = seastar::inheriting_concrete_execution_stage<void, seastar::scheduling_group>("stage", check_sg);
+ auto make_attr = [] (scheduling_group sg) {
+ seastar::thread_attributes a;
+ a.sched_group = sg;
+ return a;
+ };
+ bool done = false;
+ auto make_test_thread = [&] (scheduling_group sg) {
+ return seastar::thread(make_attr(sg), [&, sg] {
+ while (!done) {
+ es(sg).get(); // will check if executed with same sg
+ };
+ });
+ };
+ auto th1 = make_test_thread(sg1);
+ auto th2 = make_test_thread(sg2);
+ seastar::sleep(10ms).get();
+ done = true;
+ th1.join().get();
+ th2.join().get();
+}
+
+struct a_struct {};
+
+SEASTAR_THREAD_TEST_CASE(test_inheriting_concrete_execution_stage_reference_parameters) {
+ // mostly a compile test, but take the opportunity to test that passing
+ // by reference preserves the address
+ auto check_ref = [] (a_struct& ref, a_struct* ptr) {
+ BOOST_REQUIRE_EQUAL(&ref, ptr);
+ };
+ auto es = seastar::inheriting_concrete_execution_stage<void, a_struct&, a_struct*>("stage", check_ref);
+ a_struct obj;
+ es(seastar::ref(obj), &obj).get();
+}
diff --git a/src/seastar/tests/unit/expiring_fifo_test.cc b/src/seastar/tests/unit/expiring_fifo_test.cc
new file mode 100644
index 000000000..0d33df9e7
--- /dev/null
+++ b/src/seastar/tests/unit/expiring_fifo_test.cc
@@ -0,0 +1,190 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/manual_clock.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/expiring_fifo.hh>
+#include <seastar/util/later.hh>
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_no_expiry_operations) {
+ expiring_fifo<int> fifo;
+
+ BOOST_REQUIRE(fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE(!bool(fifo));
+
+ fifo.push_back(1);
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+
+ fifo.push_back(2);
+ fifo.push_back(3);
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 3u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+
+ fifo.pop_front();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 2);
+
+ fifo.pop_front();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+
+ fifo.pop_front();
+
+ BOOST_REQUIRE(fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE(!bool(fifo));
+
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_expiry_operations) {
+ return seastar::async([] {
+ std::vector<int> expired;
+ struct my_expiry {
+ std::vector<int>& e;
+ void operator()(int& v) { e.push_back(v); }
+ };
+
+ expiring_fifo<int, my_expiry, manual_clock> fifo(my_expiry{expired});
+
+ fifo.push_back(1, manual_clock::now() + 1s);
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE(!bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 1u);
+ BOOST_REQUIRE_EQUAL(expired[0], 1);
+
+ expired.clear();
+
+ fifo.push_back(1);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 1u);
+ BOOST_REQUIRE_EQUAL(expired[0], 2);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+
+ expired.clear();
+
+ fifo.push_back(1, manual_clock::now() + 1s);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3);
+ fifo.push_back(4, manual_clock::now() + 2s);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 2u);
+ std::sort(expired.begin(), expired.end());
+ BOOST_REQUIRE_EQUAL(expired[0], 1);
+ BOOST_REQUIRE_EQUAL(expired[1], 2);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 4);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+
+ expired.clear();
+
+ fifo.push_back(1);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3, manual_clock::now() + 1s);
+ fifo.push_back(4, manual_clock::now() + 1s);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 3u);
+ std::sort(expired.begin(), expired.end());
+ BOOST_REQUIRE_EQUAL(expired[0], 2);
+ BOOST_REQUIRE_EQUAL(expired[1], 3);
+ BOOST_REQUIRE_EQUAL(expired[2], 4);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+
+ expired.clear();
+
+ fifo.push_back(1);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3, manual_clock::now() + 1s);
+ fifo.push_back(4, manual_clock::now() + 1s);
+ fifo.push_back(5);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 5);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ });
+}
diff --git a/src/seastar/tests/unit/fair_queue_test.cc b/src/seastar/tests/unit/fair_queue_test.cc
new file mode 100644
index 000000000..02d63b161
--- /dev/null
+++ b/src/seastar/tests/unit/fair_queue_test.cc
@@ -0,0 +1,413 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/fair_queue.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/util/later.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/print.hh>
+#include <boost/range/irange.hpp>
+#include <chrono>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+struct request {
+ fair_queue_ticket fqdesc;
+ unsigned index;
+
+ request(unsigned weight, unsigned index)
+ : fqdesc({weight, 0})
+ , index(index)
+ {}
+};
+
+
+class test_env {
+ fair_queue _fq;
+ std::vector<int> _results;
+ std::vector<std::vector<std::exception_ptr>> _exceptions;
+ std::vector<priority_class_ptr> _classes;
+ std::vector<request> _inflight;
+
+ void drain() {
+ do {} while (tick() != 0);
+ }
+public:
+ test_env(unsigned capacity) : _fq(capacity)
+ {}
+
+ // As long as there is a request sitting in the queue, tick() will process
+ // at least one request. The only situation in which tick() will return nothing
+ // is if no requests were sent to the fair_queue (obviously).
+ //
+ // Because of this property, one useful use of tick() is to implement a drain()
+ // method (see above) in which all requests currently sent to the queue are drained
+ // before the queue is destroyed.
+ unsigned tick(unsigned n = 1) {
+ unsigned processed = 0;
+ _fq.dispatch_requests();
+
+ for (unsigned i = 0; i < n; ++i) {
+ std::vector<request> curr;
+ curr.swap(_inflight);
+
+ for (auto& req : curr) {
+ processed++;
+ _results[req.index]++;
+ _fq.notify_requests_finished(req.fqdesc);
+ }
+
+ _fq.dispatch_requests();
+ }
+ return processed;
+ }
+
+ ~test_env() {
+ drain();
+ for (auto& p: _classes) {
+ _fq.unregister_priority_class(p);
+ }
+ }
+
+ size_t register_priority_class(uint32_t shares) {
+ _results.push_back(0);
+ _exceptions.push_back(std::vector<std::exception_ptr>());
+ _classes.push_back(_fq.register_priority_class(shares));
+ return _classes.size() - 1;
+ }
+
+ void do_op(unsigned index, unsigned weight) {
+ auto cl = _classes[index];
+ auto req = request(weight, index);
+
+ _fq.queue(cl, req.fqdesc, [this, index, req] () mutable noexcept {
+ try {
+ _inflight.push_back(std::move(req));
+ } catch (...) {
+ auto eptr = std::current_exception();
+ _exceptions[index].push_back(eptr);
+ _fq.notify_requests_finished(req.fqdesc);
+ }
+ });
+ }
+
+ void update_shares(unsigned index, uint32_t shares) {
+ auto cl = _classes[index];
+ cl->update_shares(shares);
+ }
+
+ void reset_results(unsigned index) {
+ _results[index] = 0;
+ }
+
+ // Verify if the ratios are what we expect. Because we can't be sure about
+ // precise timing issues, we can always be off by some percentage. In simpler
+ // tests we really expect it to very low, but in more complex tests, with share
+ // changes, for instance, they can accumulate
+ //
+ // The ratios argument is the ratios towards the first class
+ void verify(sstring name, std::vector<unsigned> ratios, unsigned expected_error = 1) {
+ assert(ratios.size() == _results.size());
+ auto str = name + ":";
+ for (auto i = 0ul; i < _results.size(); ++i) {
+ str += format(" r[{:d}] = {:d}", i, _results[i]);
+ }
+ std::cout << str << std::endl;
+ for (auto i = 0ul; i < ratios.size(); ++i) {
+ int min_expected = ratios[i] * (_results[0] - expected_error);
+ int max_expected = ratios[i] * (_results[0] + expected_error);
+ BOOST_REQUIRE(_results[i] >= min_expected);
+ BOOST_REQUIRE(_results[i] <= max_expected);
+ BOOST_REQUIRE(_exceptions[i].size() == 0);
+ }
+ }
+};
+
+// Equal ratios. Expected equal results.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_equal_2classes) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ later().get();
+ // allow half the requests in
+ env.tick(100);
+ env.verify("equal_2classes", {1, 1});
+}
+
+// Equal results, spread among 4 classes.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_equal_4classes) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+ auto c = env.register_priority_class(10);
+ auto d = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ env.do_op(c, 1);
+ env.do_op(d, 1);
+ }
+ later().get();
+ // allow half the requests in
+ env.tick(200);
+ env.verify("equal_4classes", {1, 1, 1, 1});
+}
+
+// Class2 twice as powerful. Expected class2 to have 2 x more requests.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_different_shares) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(20);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+ // allow half the requests in
+ env.tick(100);
+ return env.verify("different_shares", {1, 2});
+}
+
+// Equal ratios, high capacity queue. Should still divide equally.
+//
+// Note that we sleep less because now more requests will be going through the
+// queue.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_equal_hi_capacity_2classes) {
+ test_env env(10);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // queue has capacity 10, 10 x 10 = 100, allow half the requests in
+ env.tick(10);
+ env.verify("hi_capacity_2classes", {1, 1});
+}
+
+// Class2 twice as powerful, queue is high capacity. Still expected class2 to
+// have 2 x more requests.
+//
+// Note that we sleep less because now more requests will be going through the
+// queue.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_different_shares_hi_capacity) {
+ test_env env(10);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(20);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+ // queue has capacity 10, 10 x 10 = 100, allow half the requests in
+ env.tick(10);
+ env.verify("different_shares_hi_capacity", {1, 2});
+}
+
+// Classes equally powerful. But Class1 issues twice as expensive requests. Expected Class2 to have 2 x more requests.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_different_weights) {
+ test_env env(2);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 2);
+ env.do_op(b, 1);
+ }
+ later().get();
+ // allow half the requests in
+ env.tick(100);
+ env.verify("different_weights", {1, 2});
+}
+
+// Class2 pushes many requests over. Right after, don't expect Class2 to be able to push anything else.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_dominant_queue) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // consume all requests
+ env.tick(100);
+ // zero statistics.
+ env.reset_results(b);
+ for (int i = 0; i < 20; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ // allow half the requests in
+ env.tick(20);
+ env.verify("dominant_queue", {1, 0});
+}
+
+// Class2 pushes many requests at first. After enough time, this shouldn't matter anymore.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_forgiving_queue) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // consume all requests
+ env.tick(100);
+ sleep(500ms).get();
+ env.reset_results(b);
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // allow half the requests in
+ env.tick(100);
+ env.verify("forgiving_queue", {1, 1});
+}
+
+// Classes push requests and then update swap their shares. In the end, should have executed
+// the same number of requests.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_update_shares) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(20);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 500; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ later().get();
+ // allow 25% of the requests in
+ env.tick(250);
+ env.update_shares(a, 10);
+ env.update_shares(b, 20);
+
+ later().get();
+ // allow 25% of the requests in
+ env.tick(250);
+ env.verify("update_shares", {1, 1}, 2);
+}
+
+// Classes run for a longer period of time. Balance must be kept over many timer
+// periods.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_longer_run) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 20000; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ // In total allow half the requests in, but do it over a
+ // long period of time, ticking slowly
+ for (int i = 0; i < 1000; ++i) {
+ sleep(1ms).get();
+ env.tick(2);
+ }
+ env.verify("longer_run", {1, 1}, 2);
+}
+
+// Classes run for a longer period of time. Proportional balance must be kept over many timer
+// periods, despite unequal shares..
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_longer_run_different_shares) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(20);
+
+ for (int i = 0; i < 20000; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ // In total allow half the requests in, but do it over a
+ // long period of time, ticking slowly
+ for (int i = 0; i < 1000; ++i) {
+ sleep(1ms).get();
+ env.tick(2);
+ }
+ env.verify("longer_run_different_shares", {1, 2}, 2);
+}
+
+// Classes run for a random period of time. Equal operations expected.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_random_run) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(1);
+ auto b = env.register_priority_class(1);
+
+ std::default_random_engine& generator = testing::local_random_engine;
+ // multiples of 100usec - which is the approximate length of the request. We will
+ // put a minimum of 10. Below that, it is hard to guarantee anything. The maximum is
+ // about 50 seconds.
+ std::uniform_int_distribution<uint32_t> distribution(10, 500 * 1000);
+ auto reqs = distribution(generator);
+
+ // Enough requests for the maximum run (half per queue, + leeway)
+ for (uint32_t i = 0; i < reqs; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ later().get();
+ // In total allow half the requests in
+ env.tick(reqs);
+
+ // Accept 5 % error.
+ auto expected_error = std::max(1, int(round(reqs * 0.05)));
+ env.verify(format("random_run ({:d} requests)", reqs), {1, 1}, expected_error);
+}
diff --git a/src/seastar/tests/unit/file_io_test.cc b/src/seastar/tests/unit/file_io_test.cc
new file mode 100644
index 000000000..abece7c6b
--- /dev/null
+++ b/src/seastar/tests/unit/file_io_test.cc
@@ -0,0 +1,756 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014-2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+
+#include <seastar/core/seastar.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/condition-variable.hh>
+#include <seastar/core/file.hh>
+#include <seastar/core/layered_file.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/stall_sampler.hh>
+#include <seastar/core/aligned_buffer.hh>
+#include <seastar/util/tmp_file.hh>
+#include <seastar/util/alloc_failure_injector.hh>
+
+#include <boost/range/adaptor/transformed.hpp>
+#include <iostream>
+#include <sys/statfs.h>
+
+#include "core/file-impl.hh"
+
+using namespace seastar;
+namespace fs = std::filesystem;
+
+SEASTAR_TEST_CASE(open_flags_test) {
+ open_flags flags = open_flags::rw | open_flags::create | open_flags::exclusive;
+ BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
+ (std::underlying_type_t<open_flags>(open_flags::rw) |
+ std::underlying_type_t<open_flags>(open_flags::create) |
+ std::underlying_type_t<open_flags>(open_flags::exclusive)));
+
+ open_flags mask = open_flags::create | open_flags::exclusive;
+ BOOST_REQUIRE((flags & mask) == mask);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(access_flags_test) {
+ access_flags flags = access_flags::read | access_flags::write | access_flags::execute;
+ BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
+ (std::underlying_type_t<open_flags>(access_flags::read) |
+ std::underlying_type_t<open_flags>(access_flags::write) |
+ std::underlying_type_t<open_flags>(access_flags::execute)));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(file_exists_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.close().get();
+ auto exists = file_exists(filename).get0();
+ BOOST_REQUIRE(exists);
+ remove_file(filename).get();
+ exists = file_exists(filename).get0();
+ BOOST_REQUIRE(!exists);
+ });
+}
+
+SEASTAR_TEST_CASE(handle_bad_alloc_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.close().get();
+ bool exists = false;
+ memory::with_allocation_failures([&] {
+ exists = file_exists(filename).get0();
+ });
+ BOOST_REQUIRE(exists);
+ });
+}
+
+SEASTAR_TEST_CASE(file_access_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.close().get();
+ auto is_accessible = file_accessible(filename, access_flags::read | access_flags::write).get0();
+ BOOST_REQUIRE(is_accessible);
+ });
+}
+
+struct file_test {
+ file_test(file&& f) : f(std::move(f)) {}
+ file f;
+ semaphore sem = { 0 };
+ semaphore par = { 1000 };
+};
+
+SEASTAR_TEST_CASE(test1) {
+ // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB).
+ return tmp_dir::do_with([] (tmp_dir& t) {
+ static constexpr auto max = 10000;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename, open_flags::rw | open_flags::create).then([filename] (file f) {
+ auto ft = new file_test{std::move(f)};
+ for (size_t i = 0; i < max; ++i) {
+ // Don't wait for future, use semaphore to signal when done instead.
+ (void)ft->par.wait().then([ft, i] {
+ auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(wbuf.get(), wbuf.get() + 4096, i);
+ auto wb = wbuf.get();
+ (void)ft->f.dma_write(i * 4096, wb, 4096).then(
+ [ft, i, wbuf = std::move(wbuf)] (size_t ret) mutable {
+ BOOST_REQUIRE(ret == 4096);
+ auto rbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ auto rb = rbuf.get();
+ (void)ft->f.dma_read(i * 4096, rb, 4096).then(
+ [ft, rbuf = std::move(rbuf), wbuf = std::move(wbuf)] (size_t ret) mutable {
+ BOOST_REQUIRE(ret == 4096);
+ BOOST_REQUIRE(std::equal(rbuf.get(), rbuf.get() + 4096, wbuf.get()));
+ ft->sem.signal(1);
+ ft->par.signal();
+ });
+ });
+ });
+ }
+ return ft->sem.wait(max).then([ft] () mutable {
+ return ft->f.flush();
+ }).then([ft] {
+ return ft->f.close();
+ }).then([ft] () mutable {
+ delete ft;
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(parallel_write_fsync) {
+ return internal::report_reactor_stalls([] {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ // Plan: open a file and write to it like crazy. In parallel fsync() it all the time.
+ auto fname = (t.get_path() / "testfile.tmp").native();
+ auto sz = uint64_t(32*1024*1024);
+ auto buffer_size = 32768;
+ auto write_concurrency = 16;
+ auto fsync_every = 1024*1024;
+ auto max_write_ahead_of_fsync = 4*1024*1024; // ensures writes don't complete too quickly
+ auto written = uint64_t(0);
+ auto fsynced_at = uint64_t(0);
+
+ file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ // Avoid filesystem problems with size-extending operations
+ f.truncate(sz).get();
+
+ auto fsync_semaphore = semaphore(0);
+ auto may_write_condvar = condition_variable();
+ auto fsync_thread = thread([&] {
+ auto fsynced = uint64_t(0);
+ while (fsynced < sz) {
+ fsync_semaphore.wait(fsync_every).get();
+ fsynced_at = written;
+ // Signal the condition variable now so that writes proceed
+ // in parallel with the fsync
+ may_write_condvar.broadcast();
+ f.flush().get();
+ fsynced += fsync_every;
+ }
+ });
+
+ auto write_semaphore = semaphore(write_concurrency);
+ while (written < sz) {
+ write_semaphore.wait().get();
+ may_write_condvar.wait([&] {
+ return written <= fsynced_at + max_write_ahead_of_fsync;
+ }).get();
+ auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), buffer_size);
+ memset(buf.get_write(), 0, buf.size());
+ // Write asynchronously, signal when done.
+ (void)f.dma_write(written, buf.get(), buf.size()).then([&fsync_semaphore, &write_semaphore, buf = std::move(buf)] (size_t w) {
+ fsync_semaphore.signal(buf.size());
+ write_semaphore.signal();
+ });
+ written += buffer_size;
+ }
+ write_semaphore.wait(write_concurrency).get();
+
+ fsync_thread.join().get();
+ f.close().get();
+ remove_file(fname).get();
+ });
+ }).then([] (internal::stall_report sr) {
+ std::cout << "parallel_write_fsync: " << sr << "\n";
+ });
+}
+
+SEASTAR_TEST_CASE(test_iov_max) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ static constexpr size_t buffer_size = 4096;
+ static constexpr size_t buffer_count = IOV_MAX * 2 + 1;
+
+ std::vector<temporary_buffer<char>> original_buffers;
+ std::vector<iovec> iovecs;
+ for (auto i = 0u; i < buffer_count; i++) {
+ original_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
+ std::fill_n(original_buffers.back().get_write(), buffer_size, char(i));
+ iovecs.emplace_back(iovec { original_buffers.back().get_write(), buffer_size });
+ }
+
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ size_t left = buffer_size * buffer_count;
+ size_t position = 0;
+ while (left) {
+ auto written = f.dma_write(position, iovecs).get0();
+ iovecs.erase(iovecs.begin(), iovecs.begin() + written / buffer_size);
+ assert(written % buffer_size == 0);
+ position += written;
+ left -= written;
+ }
+
+ BOOST_CHECK(iovecs.empty());
+
+ std::vector<temporary_buffer<char>> read_buffers;
+ for (auto i = 0u; i < buffer_count; i++) {
+ read_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
+ std::fill_n(read_buffers.back().get_write(), buffer_size, char(0));
+ iovecs.emplace_back(iovec { read_buffers.back().get_write(), buffer_size });
+ }
+
+ left = buffer_size * buffer_count;
+ position = 0;
+ while (left) {
+ auto read = f.dma_read(position, iovecs).get0();
+ iovecs.erase(iovecs.begin(), iovecs.begin() + read / buffer_size);
+ assert(read % buffer_size == 0);
+ position += read;
+ left -= read;
+ }
+
+ for (auto i = 0u; i < buffer_count; i++) {
+ BOOST_CHECK(std::equal(original_buffers[i].get(), original_buffers[i].get() + original_buffers[i].size(),
+ read_buffers[i].get(), read_buffers[i].get() + read_buffers[i].size()));
+ }
+
+ f.close().get();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs) {
+ auto buf = temporary_buffer<char>::aligned(4096, 4096);
+
+ auto iovec_equal = [] (const iovec& a, const iovec& b) {
+ return a.iov_base == b.iov_base && a.iov_len == b.iov_len;
+ };
+
+ { // Single fragment, sanitize is noop
+ auto original_iovecs = std::vector<iovec> { { buf.get_write(), buf.size() } };
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 4096);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), 1);
+ BOOST_CHECK(iovec_equal(original_iovecs.back(), actual_iovecs.back()));
+ }
+
+ { // one 1024 buffer and IOV_MAX+6 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
+ auto original_iovecs = std::vector<iovec>{};
+ for (auto i = 0u; i < IOV_MAX + 7; i++) {
+ original_iovecs.emplace_back(iovec { buf.get_write(), i == 0 ? 1024u : 512u });
+ }
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX - 1);
+
+ original_iovecs.resize(IOV_MAX - 1);
+ BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
+ actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
+ }
+
+ { // IOV_MAX-1 buffers of 512, one 1024 buffer, and 6 512 buffers; 4096 byte disk alignment, sanitize needs to drop and trim buffers
+ auto original_iovecs = std::vector<iovec>{};
+ for (auto i = 0u; i < IOV_MAX + 7; i++) {
+ original_iovecs.emplace_back(iovec { buf.get_write(), i == (IOV_MAX - 1) ? 1024u : 512u });
+ }
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);
+
+ original_iovecs.resize(IOV_MAX);
+ original_iovecs.back().iov_len = 512;
+ BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
+ actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
+ }
+
+ { // IOV_MAX+8 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
+ auto original_iovecs = std::vector<iovec>{};
+ for (auto i = 0u; i < IOV_MAX + 8; i++) {
+ original_iovecs.emplace_back(iovec { buf.get_write(), 512 });
+ }
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);
+
+ original_iovecs.resize(IOV_MAX);
+ BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
+ actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
+ }
+}
+
+SEASTAR_TEST_CASE(test_chmod) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ if (file_exists(filename).get0()) {
+ remove_file(filename).get();
+ }
+
+ auto orig_umask = umask(0);
+
+ // test default_file_permissions
+ auto f = open_file_dma(filename, oflags).get0();
+ f.close().get();
+ auto sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ // test chmod with new_permissions
+ auto new_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(new_permissions != file_permissions::default_file_permissions);
+ BOOST_REQUIRE(file_exists(filename).get0());
+ chmod(filename, new_permissions).get();
+ sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(new_permissions));
+ remove_file(filename).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_open_file_dma_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ if (file_exists(filename).get0()) {
+ remove_file(filename).get();
+ }
+
+ auto orig_umask = umask(0);
+
+ // test default_file_permissions
+ auto f = open_file_dma(filename, oflags).get0();
+ f.close().get();
+ auto sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+ remove_file(filename).get();
+
+ // test options.create_permissions
+ auto options = file_open_options();
+ options.create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(options.create_permissions != file_permissions::default_file_permissions);
+ f = open_file_dma(filename, oflags, options).get0();
+ f.close().get();
+ sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(options.create_permissions));
+ remove_file(filename).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_make_directory_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring dirname = (t.get_path() / "testdir.tmp").native();
+ auto orig_umask = umask(0);
+
+ // test default_dir_permissions with make_directory
+ make_directory(dirname).get();
+ auto sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ remove_file(dirname).get();
+
+ // test make_directory
+ auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
+ make_directory(dirname, create_permissions).get();
+ sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+ remove_file(dirname).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_touch_directory_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring dirname = (t.get_path() / "testdir.tmp").native();
+ auto orig_umask = umask(0);
+
+ // test default_dir_permissions with touch_directory
+ touch_directory(dirname).get();
+ auto sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ remove_file(dirname).get();
+
+ // test touch_directory, dir creation
+ auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
+ BOOST_REQUIRE(!file_exists(dirname).get0());
+ touch_directory(dirname, create_permissions).get();
+ sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+
+ // test touch_directory of existing dir, dir mode need not change
+ BOOST_REQUIRE(file_exists(dirname).get0());
+ touch_directory(dirname, file_permissions::default_dir_permissions).get();
+ sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+ remove_file(dirname).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_recursive_touch_directory_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring base_dirname = (t.get_path() / "testbasedir.tmp").native();
+ sstring dirpath = base_dirname + "/" + "testsubdir.tmp";
+ if (file_exists(dirpath).get0()) {
+ remove_file(dirpath).get();
+ }
+ if (file_exists(base_dirname).get0()) {
+ remove_file(base_dirname).get();
+ }
+
+ auto orig_umask = umask(0);
+
+ // test default_dir_permissions with recursive_touch_directory
+ recursive_touch_directory(dirpath).get();
+ auto sd = file_stat(base_dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ sd = file_stat(dirpath).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ remove_file(dirpath).get();
+
+ // test recursive_touch_directory, dir creation
+ auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
+ BOOST_REQUIRE(file_exists(base_dirname).get0());
+ BOOST_REQUIRE(!file_exists(dirpath).get0());
+ recursive_touch_directory(dirpath, create_permissions).get();
+ sd = file_stat(base_dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ sd = file_stat(dirpath).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+
+ // test recursive_touch_directory of existing dir, dir mode need not change
+ BOOST_REQUIRE(file_exists(dirpath).get0());
+ recursive_touch_directory(dirpath, file_permissions::default_dir_permissions).get();
+ sd = file_stat(base_dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ sd = file_stat(dirpath).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+ remove_file(dirpath).get();
+ remove_file(base_dirname).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_file_stat_method) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+
+ auto orig_umask = umask(0);
+
+ auto f = open_file_dma(filename, oflags).get0();
+ auto st = f.stat().get0();
+ f.close().get();
+ BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ umask(orig_umask);
+ });
+}
+
+
+class test_layered_file : public layered_file_impl {
+public:
+ explicit test_layered_file(file f) : layered_file_impl(std::move(f)) {}
+ virtual future<size_t> write_dma(uint64_t pos, const void* buffer, size_t len, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<size_t> read_dma(uint64_t pos, void* buffer, size_t len, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<> flush(void) override {
+ abort();
+ }
+ virtual future<struct stat> stat(void) override {
+ abort();
+ }
+ virtual future<> truncate(uint64_t length) override {
+ abort();
+ }
+ virtual future<> discard(uint64_t offset, uint64_t length) override {
+ abort();
+ }
+ virtual future<> allocate(uint64_t position, uint64_t length) override {
+ abort();
+ }
+ virtual future<uint64_t> size(void) override {
+ abort();
+ }
+ virtual future<> close() override {
+ abort();
+ }
+ virtual std::unique_ptr<file_handle_impl> dup() override {
+ abort();
+ }
+ virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) override {
+ abort();
+ }
+ virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class& pc) override {
+ abort();
+ }
+};
+
+SEASTAR_TEST_CASE(test_underlying_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, oflags).get0();
+ auto lf = file(make_shared<test_layered_file>(f));
+ BOOST_CHECK_EQUAL(f.memory_dma_alignment(), lf.memory_dma_alignment());
+ BOOST_CHECK_EQUAL(f.disk_read_dma_alignment(), lf.disk_read_dma_alignment());
+ BOOST_CHECK_EQUAL(f.disk_write_dma_alignment(), lf.disk_write_dma_alignment());
+ f.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_file_stat_method_with_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create | open_flags::truncate;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ file ref;
+
+ auto orig_umask = umask(0);
+
+ auto st = with_file(open_file_dma(filename, oflags), [&ref] (file& f) {
+ // make a copy of f to verify f is auto-closed when `with_file` returns.
+ ref = f;
+ return f.stat();
+ }).get0();
+ BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ // verify that the file was auto-closed
+ BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_open_error_with_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto open_file = [&t] (bool do_open) {
+ auto oflags = open_flags::ro;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ if (do_open) {
+ return open_file_dma(filename, oflags);
+ } else {
+ throw std::runtime_error("expected exception");
+ }
+ };
+ bool got_exception = false;
+
+ BOOST_REQUIRE_NO_THROW(with_file(open_file(true), [] (file& f) {
+ BOOST_REQUIRE(false);
+ }).handle_exception_type([&got_exception] (const std::system_error& e) {
+ got_exception = true;
+ BOOST_REQUIRE(e.code().value() == ENOENT);
+ }).get());
+ BOOST_REQUIRE(got_exception);
+
+ got_exception = false;
+ BOOST_REQUIRE_THROW(with_file(open_file(false), [] (file& f) {
+ BOOST_REQUIRE(false);
+ }).handle_exception_type([&got_exception] (const std::runtime_error& e) {
+ got_exception = true;
+ }).get(), std::runtime_error);
+ BOOST_REQUIRE(!got_exception);
+ });
+}
+
+SEASTAR_TEST_CASE(test_with_file_close_on_failure) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create | open_flags::truncate;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+
+ auto orig_umask = umask(0);
+
+ // error-free case
+ auto ref = with_file_close_on_failure(open_file_dma(filename, oflags), [] (file& f) {
+ return f;
+ }).get0();
+ auto st = ref.stat().get0();
+ ref.close().get();
+ BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ // close-on-error case
+ BOOST_REQUIRE_THROW(with_file_close_on_failure(open_file_dma(filename, oflags), [&ref] (file& f) {
+ ref = f;
+ throw std::runtime_error("expected exception");
+ }).get(), std::runtime_error);
+
+ // verify that file was auto-closed on error
+ BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
+
+ umask(orig_umask);
+ });
+}
+
+namespace seastar {
+ extern bool aio_nowait_supported;
+}
+
+SEASTAR_TEST_CASE(test_nowait_flag_correctness) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto is_tmpfs = [&] (sstring filename) {
+ struct ::statfs buf;
+ int fd = ::open(filename.c_str(), static_cast<int>(open_flags::ro));
+ assert(fd != -1);
+ auto r = ::fstatfs(fd, &buf);
+ if (r == -1) {
+ return false;
+ }
+ return buf.f_type == 0x01021994; // TMPFS_MAGIC
+ };
+
+ if (!seastar::aio_nowait_supported) {
+ BOOST_TEST_WARN(0, "Skipping this test because RWF_NOWAIT is not supported by the system");
+ return;
+ }
+
+ auto f = open_file_dma(filename, oflags).get0();
+ auto close_f = defer([&] { f.close().get(); });
+
+ if (is_tmpfs(filename)) {
+ BOOST_TEST_WARN(0, "Skipping this test because TMPFS was detected, and RWF_NOWAIT is only supported by disk-based FSes");
+ return;
+ }
+
+ for (auto i = 0; i < 10; i++) {
+ auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(wbuf.get(), wbuf.get() + 4096, i);
+ auto wb = wbuf.get();
+ f.dma_write(i * 4096, wb, 4096).get();
+ f.flush().get0();
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto oflags = open_flags::rw | open_flags::create;
+ auto f = open_file_dma(filename, oflags).get0();
+ });
+}
+
+SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_write) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(buf.get(), buf.get() + 4096, 0);
+
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.dma_write(0, buf.get(), 4096).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_read) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(buf.get(), buf.get() + 4096, 0);
+
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.dma_write(0, buf.get(), 4096).get();
+ f.flush().get0();
+ f.close().get();
+
+ f = open_file_dma(filename, open_flags::rw).get0();
+ f.dma_read(0, buf.get(), 4096).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_dma_iovec) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ static constexpr size_t alignment = 4096;
+ auto wbuf = allocate_aligned_buffer<char>(alignment, alignment);
+ size_t size = 1234;
+ std::fill_n(wbuf.get(), alignment, char(0));
+ std::fill_n(wbuf.get(), size, char(42));
+ std::vector<iovec> iovecs;
+
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ iovecs.push_back(iovec{ wbuf.get(), alignment });
+ auto count = f.dma_write(0, iovecs).get0();
+ BOOST_REQUIRE_EQUAL(count, alignment);
+ f.truncate(size).get();
+ f.close().get();
+
+ auto rbuf = allocate_aligned_buffer<char>(alignment, alignment);
+
+ // this tests the posix_file_impl
+ f = open_file_dma(filename, open_flags::ro).get0();
+ std::fill_n(rbuf.get(), alignment, char(0));
+ iovecs.clear();
+ iovecs.push_back(iovec{ rbuf.get(), alignment });
+ count = f.dma_read(0, iovecs).get0();
+ BOOST_REQUIRE_EQUAL(count, size);
+
+ BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
+
+ // this tests the append_challenged_posix_file_impl
+ f = open_file_dma(filename, open_flags::rw).get0();
+ std::fill_n(rbuf.get(), alignment, char(0));
+ iovecs.clear();
+ iovecs.push_back(iovec{ rbuf.get(), alignment });
+ count = f.dma_read(0, iovecs).get0();
+ BOOST_REQUIRE_EQUAL(count, size);
+
+ BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
+ });
+}
diff --git a/src/seastar/tests/unit/file_utils_test.cc b/src/seastar/tests/unit/file_utils_test.cc
new file mode 100644
index 000000000..3316a7751
--- /dev/null
+++ b/src/seastar/tests/unit/file_utils_test.cc
@@ -0,0 +1,287 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB
+ */
+
+#include <stdlib.h>
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+
+#include <seastar/core/file.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/tmp_file.hh>
+#include <seastar/util/file.hh>
+
+using namespace seastar;
+namespace fs = std::filesystem;
+
+class expected_exception : std::runtime_error {
+public:
+ expected_exception() : runtime_error("expected") {}
+};
+
+SEASTAR_TEST_CASE(test_make_tmp_file) {
+ return make_tmp_file().then([] (tmp_file tf) {
+ return async([tf = std::move(tf)] () mutable {
+ const sstring tmp_path = tf.get_path().native();
+ BOOST_REQUIRE(file_exists(tmp_path).get0());
+ tf.close().get();
+ tf.remove().get();
+ BOOST_REQUIRE(!file_exists(tmp_path).get0());
+ });
+ });
+}
+
+static temporary_buffer<char> get_init_buffer(file& f) {
+ auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), f.memory_dma_alignment());
+ memset(buf.get_write(), 0, buf.size());
+ return buf;
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_file) {
+ size_t expected = ~0;
+ size_t actual = 0;
+
+ tmp_file::do_with([&] (tmp_file& tf) mutable {
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ return do_with(std::move(buf), [&] (auto& buf) mutable {
+ expected = buf.size();
+ return f.dma_write(0, buf.get(), buf.size()).then([&] (size_t written) {
+ actual = written;
+ return make_ready_future<>();
+ });
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(expected , actual);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_non_existing_TMPDIR) {
+ auto old_tmpdir = getenv("TMPDIR");
+ setenv("TMPDIR", "/tmp/non-existing-TMPDIR", true);
+ BOOST_REQUIRE_EXCEPTION(tmp_file::do_with("/tmp/non-existing-TMPDIR", [] (tmp_file& tf) {}).get(),
+ std::system_error, testing::exception_predicate::message_contains("No such file or directory"));
+ if (old_tmpdir) {
+ setenv("TMPDIR", old_tmpdir, true);
+ } else {
+ unsetenv("TMPDIR");
+ }
+}
+
+static future<> touch_file(const sstring& filename, open_flags oflags = open_flags::rw | open_flags::create) noexcept {
+ return open_file_dma(filename, oflags).then([] (file f) {
+ return f.close().finally([f] {});
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_recursive_remove_directory) {
+ struct test_dir {
+ test_dir *parent;
+ sstring name;
+ std::list<sstring> sub_files = {};
+ std::list<test_dir> sub_dirs = {};
+
+ test_dir(test_dir* parent, sstring name)
+ : parent(parent)
+ , name(std::move(name))
+ { }
+
+ fs::path path() const {
+ if (!parent) {
+ return fs::path(name.c_str());
+ }
+ return parent->path() / name.c_str();
+ }
+
+ void fill_random_file(std::uniform_int_distribution<unsigned>& dist, std::default_random_engine& eng) {
+ sub_files.emplace_back(format("file-{}", dist(eng)));
+ }
+
+ test_dir& fill_random_dir(std::uniform_int_distribution<unsigned>& dist, std::default_random_engine& eng) {
+ sub_dirs.emplace_back(this, format("dir-{}", dist(eng)));
+ return sub_dirs.back();
+ }
+
+ void random_fill(int level, int levels, std::uniform_int_distribution<unsigned>& dist, std::default_random_engine& eng) {
+ int num_files = dist(eng) % 10;
+ int num_dirs = (level < levels - 1) ? (1 + dist(eng) % 3) : 0;
+
+ for (int i = 0; i < num_files; i++) {
+ fill_random_file(dist, eng);
+ }
+
+ if (num_dirs) {
+ level++;
+ for (int i = 0; i < num_dirs; i++) {
+ fill_random_dir(dist, eng).random_fill(level, levels, dist, eng);
+ }
+ }
+ }
+
+ future<> populate() {
+ return touch_directory(path().native()).then([this] {
+ return parallel_for_each(sub_files, [this] (auto& name) {
+ return touch_file((path() / name.c_str()).native());
+ }).then([this] {
+ return parallel_for_each(sub_dirs, [] (auto& sub_dir) {
+ return sub_dir.populate();
+ });
+ });
+ });
+ }
+ };
+
+ auto& eng = testing::local_random_engine;
+ auto dist = std::uniform_int_distribution<unsigned>();
+ int levels = 1 + dist(eng) % 3;
+ test_dir root = { nullptr, default_tmpdir().native() };
+ test_dir base = { &root, format("base-{}", dist(eng)) };
+ base.random_fill(0, levels, dist, eng);
+ base.populate().get();
+ recursive_remove_directory(base.path()).get();
+ BOOST_REQUIRE(!file_exists(base.path().native()).get0());
+}
+
+SEASTAR_TEST_CASE(test_make_tmp_dir) {
+ return make_tmp_dir().then([] (tmp_dir td) {
+ return async([td = std::move(td)] () mutable {
+ const sstring tmp_path = td.get_path().native();
+ BOOST_REQUIRE(file_exists(tmp_path).get0());
+ td.remove().get();
+ BOOST_REQUIRE(!file_exists(tmp_path).get0());
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_dir) {
+ size_t expected;
+ size_t actual;
+ tmp_dir::do_with([&] (tmp_dir& td) {
+ return tmp_file::do_with(td.get_path(), [&] (tmp_file& tf) {
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ return do_with(std::move(buf), [&] (auto& buf) mutable {
+ expected = buf.size();
+ return f.dma_write(0, buf.get(), buf.size()).then([&] (size_t written) {
+ actual = written;
+ return make_ready_future<>();
+ });
+ });
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(expected , actual);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_dir_with_path) {
+ size_t expected;
+ size_t actual;
+ tmp_dir::do_with(".", [&] (tmp_dir& td) {
+ return tmp_file::do_with(td.get_path(), [&] (tmp_file& tf) {
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ return do_with(std::move(buf), [&] (auto& buf) mutable {
+ expected = buf.size();
+ return tf.get_file().dma_write(0, buf.get(), buf.size()).then([&] (size_t written) {
+ actual = written;
+ return make_ready_future<>();
+ });
+ });
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(expected , actual);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_dir_with_non_existing_path) {
+ BOOST_REQUIRE_EXCEPTION(tmp_dir::do_with("/tmp/this_name_should_not_exist", [] (tmp_dir&) {}).get(),
+ std::system_error, testing::exception_predicate::message_contains("No such file or directory"));
+}
+
+SEASTAR_TEST_CASE(tmp_dir_with_thread_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& td) {
+ tmp_file tf = make_tmp_file(td.get_path()).get0();
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ auto expected = buf.size();
+ auto actual = f.dma_write(0, buf.get(), buf.size()).get0();
+ BOOST_REQUIRE_EQUAL(expected, actual);
+ tf.close().get();
+ tf.remove().get();
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_with_leftovers_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& td) {
+ fs::path path = td.get_path() / "testfile.tmp";
+ touch_file(path.native()).get();
+ BOOST_REQUIRE(file_exists(path.native()).get0());
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_fail_func_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ BOOST_REQUIRE_THROW(tmp_dir::do_with([] (tmp_dir& inner) mutable {
+ return make_exception_future<>(expected_exception());
+ }).get(), expected_exception);
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_fail_remove_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ auto saved_default_tmpdir = default_tmpdir();
+ sstring outer_path = outer.get_path().native();
+ sstring inner_path;
+ set_default_tmpdir(outer_path.c_str());
+ BOOST_REQUIRE_THROW(tmp_dir::do_with([outer_path, &inner_path] (tmp_dir& inner) mutable {
+ inner_path = inner.get_path().native();
+ return chmod(outer_path, file_permissions::user_read | file_permissions::user_execute);
+ }).get(), std::system_error);
+ BOOST_REQUIRE(file_exists(inner_path).get0());
+ chmod(outer_path, file_permissions::default_dir_permissions).get();
+ set_default_tmpdir(saved_default_tmpdir.c_str());
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_thread_fail_func_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ BOOST_REQUIRE_THROW(tmp_dir::do_with_thread([] (tmp_dir& inner) mutable {
+ throw expected_exception();
+ }).get(), expected_exception);
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_thread_fail_remove_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ auto saved_default_tmpdir = default_tmpdir();
+ sstring outer_path = outer.get_path().native();
+ sstring inner_path;
+ set_default_tmpdir(outer_path.c_str());
+ BOOST_REQUIRE_THROW(tmp_dir::do_with_thread([outer_path, &inner_path] (tmp_dir& inner) mutable {
+ inner_path = inner.get_path().native();
+ chmod(outer_path, file_permissions::user_read | file_permissions::user_execute).get();
+ }).get(), std::system_error);
+ BOOST_REQUIRE(file_exists(inner_path).get0());
+ chmod(outer_path, file_permissions::default_dir_permissions).get();
+ set_default_tmpdir(saved_default_tmpdir.c_str());
+ });
+}
diff --git a/src/seastar/tests/unit/foreign_ptr_test.cc b/src/seastar/tests/unit/foreign_ptr_test.cc
new file mode 100644
index 000000000..6eccb482e
--- /dev/null
+++ b/src/seastar/tests/unit/foreign_ptr_test.cc
@@ -0,0 +1,132 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/distributed.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+#include <iostream>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(make_foreign_ptr_from_lw_shared_ptr) {
+ auto p = make_foreign(make_lw_shared<sstring>("foo"));
+ BOOST_REQUIRE(p->size() == 3);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(make_foreign_ptr_from_shared_ptr) {
+ auto p = make_foreign(make_shared<sstring>("foo"));
+ BOOST_REQUIRE(p->size() == 3);
+ return make_ready_future<>();
+}
+
+
+SEASTAR_TEST_CASE(foreign_ptr_copy_test) {
+ return seastar::async([] {
+ auto ptr = make_foreign(make_shared<sstring>("foo"));
+ BOOST_REQUIRE(ptr->size() == 3);
+ auto ptr2 = ptr.copy().get0();
+ BOOST_REQUIRE(ptr2->size() == 3);
+ });
+}
+
+SEASTAR_TEST_CASE(foreign_ptr_get_test) {
+ auto p = make_foreign(std::make_unique<sstring>("foo"));
+ BOOST_REQUIRE_EQUAL(p.get(), &*p);
+ return make_ready_future<>();
+};
+
+SEASTAR_TEST_CASE(foreign_ptr_release_test) {
+ auto p = make_foreign(std::make_unique<sstring>("foo"));
+ auto raw_ptr = p.get();
+ BOOST_REQUIRE(bool(p));
+ BOOST_REQUIRE(p->size() == 3);
+ auto released_p = p.release();
+ BOOST_REQUIRE(!bool(p));
+ BOOST_REQUIRE(released_p->size() == 3);
+ BOOST_REQUIRE_EQUAL(raw_ptr, released_p.get());
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(foreign_ptr_reset_test) {
+ auto fp = make_foreign(std::make_unique<sstring>("foo"));
+ BOOST_REQUIRE(bool(fp));
+ BOOST_REQUIRE(fp->size() == 3);
+
+ fp.reset(std::make_unique<sstring>("foobar"));
+ BOOST_REQUIRE(bool(fp));
+ BOOST_REQUIRE(fp->size() == 6);
+
+ fp.reset();
+ BOOST_REQUIRE(!bool(fp));
+ return make_ready_future<>();
+}
+
+class dummy {
+ unsigned _cpu;
+public:
+ dummy() : _cpu(this_shard_id()) { }
+ ~dummy() { BOOST_REQUIRE_EQUAL(_cpu, this_shard_id()); }
+};
+
+SEASTAR_TEST_CASE(foreign_ptr_cpu_test) {
+ if (smp::count == 1) {
+ std::cerr << "Skipping multi-cpu foreign_ptr tests. Run with --smp=2 to test multi-cpu delete and reset.";
+ return make_ready_future<>();
+ }
+
+ using namespace std::chrono_literals;
+
+ return seastar::async([] {
+ auto p = smp::submit_to(1, [] {
+ return make_foreign(std::make_unique<dummy>());
+ }).get0();
+
+ p.reset(std::make_unique<dummy>());
+ }).then([] {
+ // Let ~foreign_ptr() take its course. RIP dummy.
+ return seastar::sleep(100ms);
+ });
+}
+
+SEASTAR_TEST_CASE(foreign_ptr_move_assignment_test) {
+ if (smp::count == 1) {
+ std::cerr << "Skipping multi-cpu foreign_ptr tests. Run with --smp=2 to test multi-cpu delete and reset.";
+ return make_ready_future<>();
+ }
+
+ using namespace std::chrono_literals;
+
+ return seastar::async([] {
+ auto p = smp::submit_to(1, [] {
+ return make_foreign(std::make_unique<dummy>());
+ }).get0();
+
+ p = foreign_ptr<std::unique_ptr<dummy>>();
+ }).then([] {
+ // Let ~foreign_ptr() take its course. RIP dummy.
+ return seastar::sleep(100ms);
+ });
+}
+
diff --git a/src/seastar/tests/unit/fsnotifier_test.cc b/src/seastar/tests/unit/fsnotifier_test.cc
new file mode 100644
index 000000000..b94953afc
--- /dev/null
+++ b/src/seastar/tests/unit/fsnotifier_test.cc
@@ -0,0 +1,227 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2020 ScyllaDB Ltd.
+ */
+
+#include <random>
+#include <algorithm>
+
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/fstream.hh>
+#include <seastar/core/file.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/util/std-compat.hh>
+
+#include "../../src/core/fsnotify.hh"
+#include "tmpdir.hh"
+
+namespace fs = std::filesystem;
+using namespace seastar;
+
+static bool find_event(const std::vector<fsnotifier::event>& events, const fsnotifier::watch& w, fsnotifier::flags mask, std::optional<sstring> path = {}) {
+ auto i = std::find_if(events.begin(), events.end(), [&](const fsnotifier::event& e) {
+ return (e.mask & mask) != fsnotifier::flags{}
+ && e.id == w
+ && (!path || *path == e.name)
+ ;
+ });
+ return i != events.end();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_modify_close_delete) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+ auto w = fsn.create_watch(p.native(), fsnotifier::flags::delete_self
+ | fsnotifier::flags::modify
+ | fsnotifier::flags::close
+ ).get0();
+
+ auto os = api_v3::and_newer::make_file_output_stream(f).get0();
+ os.write("kossa").get();
+ os.flush().get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::modify));
+ }
+
+ os.close().get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::close_write));
+ }
+
+ remove_file(p.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::delete_self));
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::ignored));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_overwrite) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+
+ auto write_file = [](fs::path& p, sstring content) {
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+ auto os = api_v3::and_newer::make_file_output_stream(f).get0();
+ os.write(content).get();
+ os.flush().get();
+ os.close().get();
+ };
+
+ write_file(p, "kossa");
+
+ auto w = fsn.create_watch(p.native(), fsnotifier::flags::delete_self
+ | fsnotifier::flags::modify
+ | fsnotifier::flags::close
+ ).get0();
+
+ write_file(p, "kossabello");
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::modify));
+ }
+
+ write_file(p, "kossaruffalobill");
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::modify));
+ }
+
+ auto p2 = tmp.path() / "tmp.apa";
+ write_file(p2, "le apa");
+
+ auto w2 = fsn.create_watch(tmp.path().native(), fsnotifier::flags::move_to).get0();
+
+ rename_file(p2.native(), p.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::delete_self));
+ BOOST_REQUIRE(find_event(events, w2, fsnotifier::flags::move_to, p.filename().native()));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_create_delete_child) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto w = fsn.create_watch(tmp.path().native(), fsnotifier::flags::create_child
+ | fsnotifier::flags::delete_child
+ ).get0();
+
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::create_child));
+ }
+
+ f.close().get();
+ remove_file(p.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::delete_child));
+ BOOST_REQUIRE(!find_event(events, w, fsnotifier::flags::ignored));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_open) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+ f.close().get();
+
+ auto w = fsn.create_watch(p.native(), fsnotifier::flags::open).get0();
+
+ auto f2 = open_file_dma(p.native(), open_flags::ro).get0();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::open));
+ }
+
+ f2.close().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_move) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+
+ f.close().get();
+
+ auto w = fsn.create_watch(tmp.path().native(), fsnotifier::flags::move).get0();
+ auto p2 = tmp.path() / "kossa.mu";
+
+ rename_file(p.native(), p2.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::move_from, p.filename().native()));
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::move_to, p2.filename().native()));
+ }
+
+ tmpdir tmp2;
+ auto p3 = tmp2.path() / "ninja.mission";
+ auto w2 = fsn.create_watch(tmp2.path().native(), fsnotifier::flags::move).get0();
+
+ rename_file(p2.native(), p3.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::move_from, p2.filename().native()));
+ BOOST_REQUIRE(find_event(events, w2, fsnotifier::flags::move_to, p3.filename().native()));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shutdown_notifier) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+
+ f.close().get();
+
+ auto w = fsn.create_watch(tmp.path().native(), fsnotifier::flags::delete_child).get0();
+ auto fut = fsn.wait();
+
+ fsn.shutdown();
+
+ auto events = fut.get0();
+ BOOST_REQUIRE(events.empty());
+}
diff --git a/src/seastar/tests/unit/fstream_test.cc b/src/seastar/tests/unit/fstream_test.cc
new file mode 100644
index 000000000..9f15b27d3
--- /dev/null
+++ b/src/seastar/tests/unit/fstream_test.cc
@@ -0,0 +1,538 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <numeric>
+#include <seastar/core/fstream.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/print.hh>
+#include <seastar/util/defer.hh>
+#include <seastar/util/tmp_file.hh>
+#include <boost/range/adaptor/transformed.hpp>
+#include <boost/algorithm/cxx11/any_of.hpp>
+#include "mock_file.hh"
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+namespace fs = std::filesystem;
+
+struct writer {
+ output_stream<char> out;
+ static future<shared_ptr<writer>> make(file f) {
+ return api_v3::and_newer::make_file_output_stream(std::move(f)).then([] (output_stream<char>&& os) {
+ return make_shared<writer>(writer{std::move(os)});
+ });
+ }
+};
+
+struct reader {
+ input_stream<char> in;
+ reader(file f) : in(make_file_input_stream(std::move(f))) {}
+ reader(file f, file_input_stream_options options) : in(make_file_input_stream(std::move(f), std::move(options))) {}
+};
+
+SEASTAR_TEST_CASE(test_fstream) {
+ return tmp_dir::do_with([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).then([filename] (file f) {
+ return writer::make(std::move(f)).then([filename] (shared_ptr<writer> w) {
+ auto buf = static_cast<char*>(::malloc(4096));
+ memset(buf, 0, 4096);
+ buf[0] = '[';
+ buf[1] = 'A';
+ buf[4095] = ']';
+ return w->out.write(buf, 4096).then([buf, w] {
+ ::free(buf);
+ return make_ready_future<>();
+ }).then([w] {
+ auto buf = static_cast<char*>(::malloc(8192));
+ memset(buf, 0, 8192);
+ buf[0] = '[';
+ buf[1] = 'B';
+ buf[8191] = ']';
+ return w->out.write(buf, 8192).then([buf, w] {
+ ::free(buf);
+ return w->out.close().then([w] {});
+ });
+ }).then([filename] {
+ return open_file_dma(filename, open_flags::ro);
+ }).then([] (file f) {
+ /* file content after running the above:
+ * 00000000 5b 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |[A..............|
+ * 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+ * *
+ * 00000ff0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5d |...............]|
+ * 00001000 5b 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |[B..............|
+ * 00001010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+ * *
+ * 00002ff0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5d |...............]|
+ * 00003000
+ */
+ auto r = make_shared<reader>(std::move(f));
+ return r->in.read_exactly(4096 + 8192).then([r] (temporary_buffer<char> buf) {
+ auto p = buf.get();
+ BOOST_REQUIRE(p[0] == '[' && p[1] == 'A' && p[4095] == ']');
+ BOOST_REQUIRE(p[4096] == '[' && p[4096 + 1] == 'B' && p[4096 + 8191] == ']');
+ return make_ready_future<>();
+ }).then([r] {
+ return r->in.close();
+ }).finally([r] {});
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_consume_skip_bytes) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ auto w = writer::make(std::move(f)).get0();
+ auto write_block = [w] (char c, size_t size) {
+ std::vector<char> vec(size, c);
+ w->out.write(&vec.front(), vec.size()).get();
+ };
+ write_block('a', 8192);
+ write_block('b', 8192);
+ w->out.close().get();
+ /* file content after running the above:
+ * 00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa|
+ * *
+ * 00002000 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 |bbbbbbbbbbbbbbbb|
+ * *
+ * 00004000
+ */
+ f = open_file_dma(filename, open_flags::ro).get0();
+ auto r = make_lw_shared<reader>(std::move(f), file_input_stream_options{512});
+ struct consumer {
+ uint64_t _count = 0;
+ using consumption_result_type = typename input_stream<char>::consumption_result_type;
+ using stop_consuming_type = typename consumption_result_type::stop_consuming_type;
+ using tmp_buf = stop_consuming_type::tmp_buf;
+
+ /*
+ * Consumer reads the file as follows:
+ * - first 8000 bytes are read in 512-byte chunks and checked
+ * - next 2000 bytes are skipped (jumping over both read buffer size and DMA block)
+ * - the remaining 6384 bytes are read and checked
+ */
+ future<consumption_result_type> operator()(tmp_buf buf) {
+ if (_count < 8000) {
+ auto delta = std::min(buf.size(), 8000 - _count);
+ for (auto c : buf.share(0, delta)) {
+ BOOST_REQUIRE_EQUAL(c, 'a');
+ }
+ buf.trim_front(delta);
+ _count += delta;
+
+ if (_count == 8000) {
+ return make_ready_future<consumption_result_type>(skip_bytes{2000 - buf.size()});
+ } else {
+ assert(buf.empty());
+ return make_ready_future<consumption_result_type>(continue_consuming{});
+ }
+ return make_ready_future<consumption_result_type>(continue_consuming{});
+ } else {
+ for (auto c : buf) {
+ BOOST_REQUIRE_EQUAL(c, 'b');
+ }
+ _count += buf.size();
+ if (_count < 14384) {
+ return make_ready_future<consumption_result_type>(continue_consuming{});
+ } else if (_count > 14384) {
+ BOOST_FAIL("Read more than expected");
+ }
+ return make_ready_future<consumption_result_type>(stop_consuming_type({}));
+ }
+ }
+ };
+ r->in.consume(consumer{}).get();
+ r->in.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_fstream_unaligned) {
+ return tmp_dir::do_with([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).then([filename] (file f) {
+ return writer::make(std::move(f)).then([filename] (shared_ptr<writer> w) {
+ auto buf = static_cast<char*>(::malloc(40));
+ memset(buf, 0, 40);
+ buf[0] = '[';
+ buf[1] = 'A';
+ buf[39] = ']';
+ return w->out.write(buf, 40).then([buf, w] {
+ ::free(buf);
+ return w->out.close().then([w] {});
+ }).then([filename] {
+ return open_file_dma(filename, open_flags::ro);
+ }).then([] (file f) {
+ return do_with(std::move(f), [] (file& f) {
+ return f.size().then([] (size_t size) {
+ // assert that file was indeed truncated to the amount of bytes written.
+ BOOST_REQUIRE(size == 40);
+ return make_ready_future<>();
+ });
+ });
+ }).then([filename] {
+ return open_file_dma(filename, open_flags::ro);
+ }).then([] (file f) {
+ auto r = make_shared<reader>(std::move(f));
+ return r->in.read_exactly(40).then([r] (temporary_buffer<char> buf) {
+ auto p = buf.get();
+ BOOST_REQUIRE(p[0] == '[' && p[1] == 'A' && p[39] == ']');
+ return make_ready_future<>();
+ }).then([r] {
+ return r->in.close();
+ }).finally([r] {});
+ });
+ });
+ });
+ });
+}
+
+future<> test_consume_until_end(uint64_t size) {
+ return tmp_dir::do_with([size] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).then([size] (file f) {
+ return api_v3::and_newer::make_file_output_stream(f).then([size] (output_stream<char>&& os) {
+ return do_with(std::move(os), [size] (output_stream<char>& out) {
+ std::vector<char> buf(size);
+ std::iota(buf.begin(), buf.end(), 0);
+ return out.write(buf.data(), buf.size()).then([&out] {
+ return out.flush();
+ });
+ });
+ }).then([f] {
+ return f.size();
+ }).then([size, f] (size_t real_size) {
+ BOOST_REQUIRE_EQUAL(size, real_size);
+ }).then([size, f] {
+ auto consumer = [offset = uint64_t(0), size] (temporary_buffer<char> buf) mutable -> future<input_stream<char>::unconsumed_remainder> {
+ if (!buf) {
+ return make_ready_future<input_stream<char>::unconsumed_remainder>(temporary_buffer<char>());
+ }
+ BOOST_REQUIRE(offset + buf.size() <= size);
+ std::vector<char> expected(buf.size());
+ std::iota(expected.begin(), expected.end(), offset);
+ offset += buf.size();
+ BOOST_REQUIRE(std::equal(buf.begin(), buf.end(), expected.begin()));
+ return make_ready_future<input_stream<char>::unconsumed_remainder>(std::nullopt);
+ };
+ return do_with(make_file_input_stream(f), std::move(consumer), [] (input_stream<char>& in, auto& consumer) {
+ return in.consume(consumer).then([&in] {
+ return in.close();
+ });
+ });
+ }).finally([f] () mutable {
+ return f.close();
+ });
+ });
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_consume_aligned_file) {
+ return test_consume_until_end(4096);
+}
+
+SEASTAR_TEST_CASE(test_consume_empty_file) {
+ return test_consume_until_end(0);
+}
+
+SEASTAR_TEST_CASE(test_consume_unaligned_file) {
+ return test_consume_until_end(1);
+}
+
+SEASTAR_TEST_CASE(test_consume_unaligned_file_large) {
+ return test_consume_until_end((1 << 20) + 1);
+}
+
+SEASTAR_TEST_CASE(test_input_stream_esp_around_eof) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto flen = uint64_t(5341);
+ auto rdist = std::uniform_int_distribution<char>();
+ auto reng = testing::local_random_engine;
+ auto data = boost::copy_range<std::vector<uint8_t>>(
+ boost::irange<uint64_t>(0, flen)
+ | boost::adaptors::transformed([&] (int x) { return rdist(reng); }));
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ auto out = api_v3::and_newer::make_file_output_stream(f).get0();
+ out.write(reinterpret_cast<const char*>(data.data()), data.size()).get();
+ out.flush().get();
+ //out.close().get(); // FIXME: closes underlying stream:?!
+ struct range { uint64_t start; uint64_t end; };
+ auto ranges = std::vector<range>{{
+ range{0, flen},
+ range{0, flen * 2},
+ range{0, flen + 1},
+ range{0, flen - 1},
+ range{0, 1},
+ range{1, 2},
+ range{flen - 1, flen},
+ range{flen - 1, flen + 1},
+ range{flen, flen + 1},
+ range{flen + 1, flen + 2},
+ range{1023, flen-1},
+ range{1023, flen},
+ range{1023, flen + 2},
+ range{8193, 8194},
+ range{1023, 1025},
+ range{1023, 1024},
+ range{1024, 1025},
+ range{1023, 4097},
+ }};
+ auto opt = file_input_stream_options();
+ opt.buffer_size = 512;
+ for (auto&& r : ranges) {
+ auto start = r.start;
+ auto end = r.end;
+ auto len = end - start;
+ auto in = make_file_input_stream(f, start, len, opt);
+ std::vector<uint8_t> readback;
+ auto more = true;
+ while (more) {
+ auto rdata = in.read().get0();
+ for (size_t i = 0; i < rdata.size(); ++i) {
+ readback.push_back(rdata.get()[i]);
+ }
+ more = !rdata.empty();
+ }
+ //in.close().get();
+ auto xlen = std::min(end, flen) - std::min(flen, start);
+ if (xlen != readback.size()) {
+ BOOST_FAIL(format("Expected {:d} bytes but got {:d}, start={:d}, end={:d}", xlen, readback.size(), start, end));
+ }
+ BOOST_REQUIRE(std::equal(readback.begin(), readback.end(), data.begin() + std::min(start, flen)));
+ }
+ f.close().get();
+ });
+}
+
+#if SEASTAR_API_LEVEL >= 3
+SEASTAR_TEST_CASE(without_api_prefix) {
+ return tmp_dir::do_with_thread([](tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ output_stream<char> out = make_file_output_stream(f).get0();
+ out.close().get();
+ });
+}
+#endif
+
+SEASTAR_TEST_CASE(file_handle_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::create | open_flags::truncate | open_flags::rw).get0();
+ auto buf = static_cast<char*>(aligned_alloc(4096, 4096));
+ auto del = defer([&] { ::free(buf); });
+ for (unsigned i = 0; i < 4096; ++i) {
+ buf[i] = i;
+ }
+ f.dma_write(0, buf, 4096).get();
+ auto bad = std::vector<unsigned>(smp::count); // std::vector<bool> is special and unsuitable because it uses bitfields
+ smp::invoke_on_all([fh = f.dup(), &bad] {
+ return seastar::async([fh, &bad] {
+ auto f = fh.to_file();
+ auto buf = static_cast<char*>(aligned_alloc(4096, 4096));
+ auto del = defer([&] { ::free(buf); });
+ f.dma_read(0, buf, 4096).get();
+ for (unsigned i = 0; i < 4096; ++i) {
+ bad[this_shard_id()] |= buf[i] != char(i);
+ }
+ });
+ }).get();
+ BOOST_REQUIRE(!boost::algorithm::any_of_equal(bad, 1u));
+ f.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_fstream_slow_start) {
+ return seastar::async([] {
+ static constexpr size_t file_size = 128 * 1024 * 1024;
+ static constexpr size_t buffer_size = 260 * 1024;
+ static constexpr size_t read_ahead = 1;
+
+ auto mock_file = make_shared<mock_read_only_file>(file_size);
+
+ auto history = make_lw_shared<file_input_stream_history>();
+
+ file_input_stream_options options{};
+ options.buffer_size = buffer_size;
+ options.read_ahead = read_ahead;
+ options.dynamic_adjustments = history;
+
+ static constexpr size_t requests_at_slow_start = 2; // 1 request + 1 read-ahead
+ static constexpr size_t requests_at_full_speed = read_ahead + 1; // 1 request + read_ahead
+
+ std::optional<size_t> initial_read_size;
+
+ auto read_whole_file_with_slow_start = [&] (auto fstr) {
+ uint64_t total_read = 0;
+ size_t previous_buffer_length = 0;
+
+ // We don't want to assume too much about fstream internals, but with
+ // no history we should start with a buffer sizes somewhere in
+ // (0, buffer_size) range.
+ mock_file->set_read_size_verifier([&] (size_t length) {
+ BOOST_CHECK_LE(length, initial_read_size.value_or(buffer_size - 1));
+ BOOST_CHECK_GE(length, initial_read_size.value_or(1));
+ previous_buffer_length = length;
+ if (!initial_read_size) {
+ initial_read_size = length;
+ }
+ });
+
+ // Slow start phase
+ while (true) {
+ // We should leave slow start before reading the whole file.
+ BOOST_CHECK_LT(total_read, file_size);
+
+ mock_file->set_allowed_read_requests(requests_at_slow_start);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_GT(buf.size(), 0u);
+
+ mock_file->set_read_size_verifier([&] (size_t length) {
+ // There is no reason to reduce buffer size.
+ BOOST_CHECK_LE(length, std::min(previous_buffer_length * 2, buffer_size));
+ BOOST_CHECK_GE(length, previous_buffer_length);
+ previous_buffer_length = length;
+ });
+
+ BOOST_TEST_MESSAGE(format("Size {:d}", buf.size()));
+ total_read += buf.size();
+ if (buf.size() == buffer_size) {
+ BOOST_TEST_MESSAGE("Leaving slow start phase.");
+ break;
+ }
+ }
+
+ // Reading at full speed now
+ mock_file->set_expected_read_size(buffer_size);
+ while (total_read != file_size) {
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ total_read += buf.size();
+ }
+
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_EQUAL(buf.size(), 0u);
+ assert(buf.size() == 0);
+ };
+
+ auto read_while_file_at_full_speed = [&] (auto fstr) {
+ uint64_t total_read = 0;
+
+ mock_file->set_expected_read_size(buffer_size);
+ while (total_read != file_size) {
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ total_read += buf.size();
+ }
+
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_EQUAL(buf.size(), 0u);
+ };
+
+ auto read_and_skip_a_lot = [&] (auto fstr) {
+ uint64_t total_read = 0;
+ size_t previous_buffer_size = buffer_size;
+
+ mock_file->set_allowed_read_requests(std::numeric_limits<size_t>::max());
+ mock_file->set_read_size_verifier([&] (size_t length) {
+ // There is no reason to reduce buffer size.
+ BOOST_CHECK_LE(length, previous_buffer_size);
+ BOOST_CHECK_GE(length, initial_read_size.value_or(1));
+ previous_buffer_size = length;
+ });
+ while (total_read != file_size) {
+ auto buf = fstr.read().get0();
+ total_read += buf.size();
+
+ buf = fstr.read().get0();
+ total_read += buf.size();
+
+ auto skip_by = std::min(file_size - total_read, buffer_size * 2);
+ fstr.skip(skip_by).get();
+ total_read += skip_by;
+ }
+
+ // We should be back at slow start at this stage.
+ BOOST_CHECK_LT(previous_buffer_size, buffer_size);
+ if (initial_read_size) {
+ BOOST_CHECK_EQUAL(previous_buffer_size, *initial_read_size);
+ }
+
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_EQUAL(buf.size(), 0u);
+
+ };
+
+ auto make_fstream = [&] {
+ struct fstream_wrapper {
+ input_stream<char> s;
+ explicit fstream_wrapper(input_stream<char>&& s) : s(std::move(s)) {}
+ fstream_wrapper(fstream_wrapper&&) = default;
+ fstream_wrapper& operator=(fstream_wrapper&&) = default;
+ future<temporary_buffer<char>> read() {
+ return s.read();
+ }
+ future<> skip(uint64_t n) {
+ return s.skip(n);
+ }
+ ~fstream_wrapper() {
+ s.close().get();
+ }
+ };
+ return fstream_wrapper(make_file_input_stream(file(mock_file), 0, file_size, options));
+ };
+
+ BOOST_TEST_MESSAGE("Reading file, no history, expectiong a slow start");
+ read_whole_file_with_slow_start(make_fstream());
+ BOOST_TEST_MESSAGE("Reading file again, everything good so far, read at full speed");
+ read_while_file_at_full_speed(make_fstream());
+ BOOST_TEST_MESSAGE("Reading and skipping a lot");
+ read_and_skip_a_lot(make_fstream());
+ BOOST_TEST_MESSAGE("Reading file, bad history, we are back at slow start...");
+ read_whole_file_with_slow_start(make_fstream());
+ BOOST_TEST_MESSAGE("Reading file yet again, should've recovered by now");
+ read_while_file_at_full_speed(make_fstream());
+ });
+}
diff --git a/src/seastar/tests/unit/futures_test.cc b/src/seastar/tests/unit/futures_test.cc
new file mode 100644
index 000000000..200e33ac9
--- /dev/null
+++ b/src/seastar/tests/unit/futures_test.cc
@@ -0,0 +1,1617 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/stream.hh>
+#include <seastar/util/backtrace.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/shared_future.hh>
+#include <seastar/core/manual_clock.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/util/log.hh>
+#include <boost/iterator/counting_iterator.hpp>
+#include <seastar/testing/thread_test_case.hh>
+
+#include <boost/range/iterator_range.hpp>
+#include <boost/range/irange.hpp>
+
+#include <seastar/core/internal/api-level.hh>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+static_assert(std::is_nothrow_default_constructible_v<gate>,
+ "seastar::gate constructor must not throw");
+static_assert(std::is_nothrow_move_constructible_v<gate>,
+ "seastar::gate move constructor must not throw");
+
+static_assert(std::is_nothrow_default_constructible_v<shared_future<>>);
+static_assert(std::is_nothrow_copy_constructible_v<shared_future<>>);
+static_assert(std::is_nothrow_move_constructible_v<shared_future<>>);
+
+static_assert(std::is_nothrow_move_constructible_v<shared_promise<>>);
+
+class expected_exception : public std::runtime_error {
+public:
+ expected_exception() : runtime_error("expected") {}
+};
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wself-move"
+#endif
+SEASTAR_TEST_CASE(test_self_move) {
+ future_state<std::tuple<std::unique_ptr<int>>> s1;
+ s1.set(std::make_unique<int>(42));
+ s1 = std::move(s1); // no crash, but the value of s1 is not defined.
+
+#if SEASTAR_API_LEVEL < 5
+ future_state<std::tuple<std::unique_ptr<int>>> s2;
+#else
+ future_state<std::unique_ptr<int>> s2;
+#endif
+ s2.set(std::make_unique<int>(42));
+ std::swap(s2, s2);
+ BOOST_REQUIRE_EQUAL(*std::move(s2).get0(), 42);
+
+ promise<std::unique_ptr<int>> p1;
+ p1.set_value(std::make_unique<int>(42));
+ p1 = std::move(p1); // no crash, but the value of p1 is not defined.
+
+ promise<std::unique_ptr<int>> p2;
+ p2.set_value(std::make_unique<int>(42));
+ std::swap(p2, p2);
+ BOOST_REQUIRE_EQUAL(*p2.get_future().get0(), 42);
+
+ auto f1 = make_ready_future<std::unique_ptr<int>>(std::make_unique<int>(42));
+ f1 = std::move(f1); // no crash, but the value of f1 is not defined.
+
+ auto f2 = make_ready_future<std::unique_ptr<int>>(std::make_unique<int>(42));
+ std::swap(f2, f2);
+ BOOST_REQUIRE_EQUAL(*f2.get0(), 42);
+
+ return make_ready_future<>();
+}
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+static subscription<int> get_empty_subscription(std::function<future<> (int)> func) {
+ stream<int> s;
+ auto ret = s.listen(func);
+ s.close();
+ return ret;
+}
+
+SEASTAR_TEST_CASE(test_stream) {
+ auto sub = get_empty_subscription([](int x) {
+ return make_ready_future<>();
+ });
+ return sub.done();
+}
+
+SEASTAR_TEST_CASE(test_stream_drop_sub) {
+ auto s = make_lw_shared<stream<int>>();
+ std::optional<future<>> ret;
+ {
+ auto sub = s->listen([](int x) {
+ return make_ready_future<>();
+ });
+ ret = sub.done();
+ // It is ok to drop the subscription when we only want the competition future.
+ }
+ return s->produce(42).then([ret = std::move(*ret), s] () mutable {
+ s->close();
+ return std::move(ret);
+ });
+}
+
+SEASTAR_TEST_CASE(test_reference) {
+ int a = 42;
+ future<int&> orig = make_ready_future<int&>(a);
+ future<int&> fut = std::move(orig);
+ int& r = fut.get0();
+ r = 43;
+ BOOST_REQUIRE_EQUAL(a, 43);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_set_future_state_with_tuple) {
+ future_state<std::tuple<int>> s1;
+ promise<int> p1;
+ const std::tuple<int> v1(42);
+ s1.set(v1);
+ p1.set_value(v1);
+
+ return make_ready_future<>();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_set_value_make_exception_in_copy) {
+ struct throw_in_copy {
+ throw_in_copy() noexcept = default;
+ throw_in_copy(throw_in_copy&& x) noexcept {
+ }
+ throw_in_copy(const throw_in_copy& x) {
+ throw 42;
+ }
+ };
+ promise<throw_in_copy> p1;
+ throw_in_copy v;
+ p1.set_value(v);
+ BOOST_REQUIRE_THROW(p1.get_future().get(), int);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_set_exception_in_constructor) {
+ struct throw_in_constructor {
+ throw_in_constructor() {
+ throw 42;
+ }
+ };
+ future<throw_in_constructor> f = make_ready_future<throw_in_constructor>();
+ BOOST_REQUIRE(f.failed());
+ BOOST_REQUIRE_THROW(f.get(), int);
+}
+
+SEASTAR_TEST_CASE(test_finally_is_called_on_success_and_failure) {
+ auto finally1 = make_shared<bool>();
+ auto finally2 = make_shared<bool>();
+
+ return make_ready_future().then([] {
+ }).finally([=] {
+ *finally1 = true;
+ }).then([] {
+ throw std::runtime_error("");
+ }).finally([=] {
+ *finally2 = true;
+ }).then_wrapped([=] (auto&& f) {
+ BOOST_REQUIRE(*finally1);
+ BOOST_REQUIRE(*finally2);
+
+ // Should be failed.
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_get_on_promise) {
+ auto p = promise<uint32_t>();
+ p.set_value(10);
+ BOOST_REQUIRE_EQUAL(10u, p.get_future().get0());
+ return make_ready_future();
+}
+
+// An exception class with a controlled what() overload
+class test_exception : public std::exception {
+ sstring _what;
+public:
+ explicit test_exception(sstring what) : _what(std::move(what)) {}
+ virtual const char* what() const noexcept override {
+ return _what.c_str();
+ }
+};
+
+static void check_finally_exception(std::exception_ptr ex) {
+ BOOST_REQUIRE_EQUAL(fmt::format("{}", ex),
+ "seastar::nested_exception: test_exception (bar) (while cleaning up after test_exception (foo))");
+ try {
+ // convert to the concrete type nested_exception
+ std::rethrow_exception(ex);
+ } catch (seastar::nested_exception& ex) {
+ try {
+ std::rethrow_exception(ex.inner);
+ } catch (test_exception& inner) {
+ BOOST_REQUIRE_EQUAL(inner.what(), "bar");
+ }
+ try {
+ ex.rethrow_nested();
+ } catch (test_exception& outer) {
+ BOOST_REQUIRE_EQUAL(outer.what(), "foo");
+ }
+ }
+}
+
+SEASTAR_TEST_CASE(test_finally_exception) {
+ return make_ready_future<>().then([] {
+ throw test_exception("foo");
+ }).finally([] {
+ throw test_exception("bar");
+ }).handle_exception(check_finally_exception);
+}
+
+SEASTAR_TEST_CASE(test_finally_exceptional_future) {
+ return make_ready_future<>().then([] {
+ throw test_exception("foo");
+ }).finally([] {
+ return make_exception_future<>(test_exception("bar"));
+ }).handle_exception(check_finally_exception);
+}
+
+SEASTAR_TEST_CASE(test_finally_waits_for_inner) {
+ auto finally = make_shared<bool>();
+ auto p = make_shared<promise<>>();
+
+ auto f = make_ready_future().then([] {
+ }).finally([=] {
+ return p->get_future().then([=] {
+ *finally = true;
+ });
+ }).then([=] {
+ BOOST_REQUIRE(*finally);
+ });
+ BOOST_REQUIRE(!*finally);
+ p->set_value();
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_finally_is_called_on_success_and_failure__not_ready_to_armed) {
+ auto finally1 = make_shared<bool>();
+ auto finally2 = make_shared<bool>();
+
+ promise<> p;
+ auto f = p.get_future().finally([=] {
+ *finally1 = true;
+ }).then([] {
+ throw std::runtime_error("");
+ }).finally([=] {
+ *finally2 = true;
+ }).then_wrapped([=] (auto &&f) {
+ BOOST_REQUIRE(*finally1);
+ BOOST_REQUIRE(*finally2);
+ try {
+ f.get();
+ } catch (...) {} // silence exceptional future ignored messages
+ });
+
+ p.set_value();
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_exception_from_finally_fails_the_target) {
+ promise<> pr;
+ auto f = pr.get_future().finally([=] {
+ throw std::runtime_error("");
+ }).then([] {
+ BOOST_REQUIRE(false);
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ } catch (...) {} // silence exceptional future ignored messages
+ });
+
+ pr.set_value();
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_exception_from_finally_fails_the_target_on_already_resolved) {
+ return make_ready_future().finally([=] {
+ throw std::runtime_error("");
+ }).then([] {
+ BOOST_REQUIRE(false);
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ } catch (...) {} // silence exceptional future ignored messages
+ });
+}
+
+SEASTAR_TEST_CASE(test_exception_thrown_from_then_wrapped_causes_future_to_fail) {
+ return make_ready_future().then_wrapped([] (auto&& f) {
+ throw std::runtime_error("");
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_exception_thrown_from_then_wrapped_causes_future_to_fail__async_case) {
+ promise<> p;
+
+ auto f = p.get_future().then_wrapped([] (auto&& f) {
+ throw std::runtime_error("");
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+
+ p.set_value();
+
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_failing_intermediate_promise_should_fail_the_master_future) {
+ promise<> p1;
+ promise<> p2;
+
+ auto f = p1.get_future().then([f = p2.get_future()] () mutable {
+ return std::move(f);
+ }).then([] {
+ BOOST_REQUIRE(false);
+ });
+
+ p1.set_value();
+ p2.set_exception(std::runtime_error("boom"));
+
+ return std::move(f).then_wrapped([](auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__not_ready_to_unarmed) {
+ promise<> p1;
+ promise<> p2;
+
+ auto f1 = p1.get_future();
+ auto f2 = p2.get_future();
+
+ f1.forward_to(std::move(p2));
+
+ BOOST_REQUIRE(!f2.available());
+
+ auto called = f2.then([] {});
+
+ p1.set_value();
+ return called;
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__not_ready_to_armed) {
+ promise<> p1;
+ promise<> p2;
+
+ auto f1 = p1.get_future();
+ auto f2 = p2.get_future();
+
+ auto called = f2.then([] {});
+
+ f1.forward_to(std::move(p2));
+
+ BOOST_REQUIRE(!f2.available());
+
+ p1.set_value();
+
+ return called;
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__ready_to_unarmed) {
+ promise<> p2;
+
+ auto f1 = make_ready_future<>();
+ auto f2 = p2.get_future();
+
+ std::move(f1).forward_to(std::move(p2));
+ BOOST_REQUIRE(f2.available());
+
+ return std::move(f2).then_wrapped([] (future<> f) {
+ BOOST_REQUIRE(!f.failed());
+ });
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__ready_to_armed) {
+ promise<> p2;
+
+ auto f1 = make_ready_future<>();
+ auto f2 = p2.get_future();
+
+ auto called = std::move(f2).then([] {});
+
+ BOOST_REQUIRE(f1.available());
+
+ f1.forward_to(std::move(p2));
+ return called;
+}
+
+static void forward_dead_unarmed_promise_with_dead_future_to(promise<>& p) {
+ promise<> p2;
+ p.get_future().forward_to(std::move(p2));
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__ready_to_unarmed_soon_to_be_dead) {
+ promise<> p1;
+ forward_dead_unarmed_promise_with_dead_future_to(p1);
+ make_ready_future<>().forward_to(std::move(p1));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_exception_can_be_thrown_from_do_until_body) {
+ return do_until([] { return false; }, [] {
+ throw expected_exception();
+ return now();
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have failed");
+ } catch (const expected_exception& e) {
+ // expected
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_bare_value_can_be_returned_from_callback) {
+ return now().then([] {
+ return 3;
+ }).then([] (int x) {
+ BOOST_REQUIRE(x == 3);
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_all_iterator_range) {
+ std::vector<future<size_t>> futures;
+ for (size_t i = 0; i != 1000000; ++i) {
+ // Use a mix of available and unavailable futures to exercise
+ // both paths in when_all().
+ auto fut = (i % 2) == 0 ? make_ready_future<>() : later();
+ futures.push_back(fut.then([i] { return i; }));
+ }
+ // Verify the above statement is correct
+ BOOST_REQUIRE(!std::all_of(futures.begin(), futures.end(),
+ [] (auto& f) { return f.available(); }));
+ auto p = make_shared(std::move(futures));
+ return when_all(p->begin(), p->end()).then([p] (std::vector<future<size_t>> ret) {
+ BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [] (auto& f) { return f.available(); }));
+ BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [&ret] (auto& f) { return f.get0() == size_t(&f - ret.data()); }));
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce) {
+ auto square = [] (long x) { return make_ready_future<long>(x*x); };
+ long n = 1000;
+ return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+ square, long(0), std::plus<long>()).then([n] (auto result) {
+ auto m = n - 1; // counting does not include upper bound
+ BOOST_REQUIRE_EQUAL(result, (m * (m + 1) * (2*m + 1)) / 6);
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce_simple) {
+ return do_with(0L, [] (auto& res) {
+ long n = 10;
+ return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+ [] (long x) { return x; },
+ [&res] (long x) { res += x; }).then([n, &res] {
+ long expected = (n * (n - 1)) / 2;
+ BOOST_REQUIRE_EQUAL(res, expected);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce_tuple) {
+ return do_with(0L, 0L, [] (auto& res0, auto& res1) {
+ long n = 10;
+ return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+ [] (long x) { return std::tuple<long, long>(x, -x); },
+ [&res0, &res1] (std::tuple<long, long> t) { res0 += std::get<0>(t); res1 += std::get<1>(t); }).then([n, &res0, &res1] {
+ long expected = (n * (n - 1)) / 2;
+ BOOST_REQUIRE_EQUAL(res0, expected);
+ BOOST_REQUIRE_EQUAL(res1, -expected);
+ });
+ });
+}
+
+// This test doesn't actually test anything - it just waits for the future
+// returned by sleep to complete. However, a bug we had in sleep() caused
+// this test to fail the sanitizer in the debug build, so this is a useful
+// regression test.
+SEASTAR_TEST_CASE(test_sleep) {
+ return sleep(std::chrono::milliseconds(100));
+}
+
+SEASTAR_TEST_CASE(test_do_with_1) {
+ return do_with(1, [] (int& one) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_2) {
+ return do_with(1, 2L, [] (int& one, long two) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ BOOST_REQUIRE_EQUAL(two, 2);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_3) {
+ return do_with(1, 2L, 3, [] (int& one, long two, int three) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ BOOST_REQUIRE_EQUAL(two, 2);
+ BOOST_REQUIRE_EQUAL(three, 3);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_4) {
+ return do_with(1, 2L, 3, 4, [] (int& one, long two, int three, int four) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ BOOST_REQUIRE_EQUAL(two, 2);
+ BOOST_REQUIRE_EQUAL(three, 3);
+ BOOST_REQUIRE_EQUAL(four, 4);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_5) {
+ using func = noncopyable_function<void()>;
+ return do_with(func([] {}), [] (func&) {
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_6) {
+ const int x = 42;
+ return do_with(int(42), x, [](int&, int&) {
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_7) {
+ const int x = 42;
+ return do_with(x, [](int&) {
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_stopping_immediately) {
+ return do_with(int(0), [] (int& count) {
+ return repeat([&count] {
+ ++count;
+ return stop_iteration::yes;
+ }).then([&count] {
+ BOOST_REQUIRE(count == 1);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_stopping_after_two_iterations) {
+ return do_with(int(0), [] (int& count) {
+ return repeat([&count] {
+ ++count;
+ return count == 2 ? stop_iteration::yes : stop_iteration::no;
+ }).then([&count] {
+ BOOST_REQUIRE(count == 2);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_failing_in_the_first_step) {
+ return repeat([] {
+ throw expected_exception();
+ return stop_iteration::no;
+ }).then_wrapped([](auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not happen");
+ } catch (const expected_exception&) {
+ // expected
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_failing_in_the_second_step) {
+ return do_with(int(0), [] (int& count) {
+ return repeat([&count] {
+ ++count;
+ if (count > 1) {
+ throw expected_exception();
+ }
+ return later().then([] { return stop_iteration::no; });
+ }).then_wrapped([&count](auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not happen");
+ } catch (const expected_exception&) {
+ BOOST_REQUIRE(count == 2);
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_parallel_for_each) {
+ return async([] {
+ // empty
+ parallel_for_each(std::vector<int>(), [] (int) -> future<> {
+ BOOST_FAIL("should not reach");
+ abort();
+ }).get();
+
+ // immediate result
+ auto range = boost::copy_range<std::vector<int>>(boost::irange(1, 6));
+ auto sum = 0;
+ parallel_for_each(range, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 15);
+
+ // all suspend
+ sum = 0;
+ parallel_for_each(range, [&sum] (int v) {
+ return later().then([&sum, v] {
+ sum += v;
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 15);
+
+ // throws immediately
+ BOOST_CHECK_EXCEPTION(parallel_for_each(range, [] (int) -> future<> {
+ throw 5;
+ }).get(), int, [] (int v) { return v == 5; });
+
+ // throws after suspension
+ BOOST_CHECK_EXCEPTION(parallel_for_each(range, [] (int) {
+ return later().then([] {
+ throw 5;
+ });
+ }).get(), int, [] (int v) { return v == 5; });
+ });
+}
+
+SEASTAR_TEST_CASE(test_parallel_for_each_early_failure) {
+ return do_with(0, [] (int& counter) {
+ return parallel_for_each(boost::irange(0, 11000), [&counter] (int i) {
+ using namespace std::chrono_literals;
+ // force scheduling
+ return sleep((i % 31 + 1) * 1ms).then([&counter, i] {
+ ++counter;
+ if (i % 1777 == 1337) {
+ return make_exception_future<>(i);
+ }
+ return make_ready_future<>();
+ });
+ }).then_wrapped([&counter] (future<> f) {
+ BOOST_REQUIRE_EQUAL(counter, 11000);
+ BOOST_REQUIRE(f.failed());
+ try {
+ f.get();
+ BOOST_FAIL("wanted an exception");
+ } catch (int i) {
+ BOOST_REQUIRE(i % 1777 == 1337);
+ } catch (...) {
+ BOOST_FAIL("bad exception type");
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_parallel_for_each_waits_for_all_fibers_even_if_one_of_them_failed) {
+ auto can_exit = make_lw_shared<bool>(false);
+ return parallel_for_each(boost::irange(0, 2), [can_exit] (int i) {
+ return later().then([i, can_exit] {
+ if (i == 1) {
+ throw expected_exception();
+ } else {
+ using namespace std::chrono_literals;
+ return sleep(300ms).then([can_exit] {
+ *can_exit = true;
+ });
+ }
+ });
+ }).then_wrapped([can_exit] (auto&& f) {
+ try {
+ f.get();
+ } catch (...) {
+ // expected
+ }
+ BOOST_REQUIRE(*can_exit);
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_parallel_for_each_broken_promise) {
+ auto fut = [] {
+ std::vector<promise<>> v(2);
+ return parallel_for_each(v, [] (promise<>& p) {
+ return p.get_future();
+ });
+ }();
+ BOOST_CHECK_THROW(fut.get(), broken_promise);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_repeat_broken_promise) {
+ auto get_fut = [] {
+ promise<stop_iteration> pr;
+ return pr.get_future();
+ };
+
+ future<> r = repeat([fut = get_fut()] () mutable {
+ return std::move(fut);
+ });
+
+ BOOST_CHECK_THROW(r.get(), broken_promise);
+}
+
+#ifndef SEASTAR_SHUFFLE_TASK_QUEUE
+SEASTAR_TEST_CASE(test_high_priority_task_runs_in_the_middle_of_loops) {
+ auto counter = make_lw_shared<int>(0);
+ auto flag = make_lw_shared<bool>(false);
+ return repeat([counter, flag] {
+ if (*counter == 1) {
+ BOOST_REQUIRE(*flag);
+ return stop_iteration::yes;
+ }
+ engine().add_high_priority_task(make_task([flag] {
+ *flag = true;
+ }));
+ ++(*counter);
+ return stop_iteration::no;
+ });
+}
+#endif
+
+SEASTAR_TEST_CASE(futurize_invoke_val_exception) {
+ return futurize_invoke([] (int arg) { throw expected_exception(); return arg; }, 1).then_wrapped([] (future<int> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) {}
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_val_ok) {
+ return futurize_invoke([] (int arg) { return arg * 2; }, 2).then_wrapped([] (future<int> f) {
+ try {
+ auto x = f.get0();
+ BOOST_REQUIRE_EQUAL(x, 4);
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_val_future_exception) {
+ return futurize_invoke([] (int a) {
+ return sleep(std::chrono::milliseconds(100)).then([] {
+ throw expected_exception();
+ return make_ready_future<int>(0);
+ });
+ }, 0).then_wrapped([] (future<int> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) { }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_val_future_ok) {
+ return futurize_invoke([] (int a) {
+ return sleep(std::chrono::milliseconds(100)).then([a] {
+ return make_ready_future<int>(a * 100);
+ });
+ }, 2).then_wrapped([] (future<int> f) {
+ try {
+ auto x = f.get0();
+ BOOST_REQUIRE_EQUAL(x, 200);
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+SEASTAR_TEST_CASE(futurize_invoke_void_exception) {
+ return futurize_invoke([] (auto arg) { throw expected_exception(); }, 0).then_wrapped([] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) {}
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_void_ok) {
+ return futurize_invoke([] (auto arg) { }, 0).then_wrapped([] (future<> f) {
+ try {
+ f.get();
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_void_future_exception) {
+ return futurize_invoke([] (auto a) {
+ return sleep(std::chrono::milliseconds(100)).then([] {
+ throw expected_exception();
+ });
+ }, 0).then_wrapped([] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) { }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_void_future_ok) {
+ auto a = make_lw_shared<int>(1);
+ return futurize_invoke([] (int& a) {
+ return sleep(std::chrono::milliseconds(100)).then([&a] {
+ a *= 100;
+ });
+ }, *a).then_wrapped([a] (future<> f) {
+ try {
+ f.get();
+ BOOST_REQUIRE_EQUAL(*a, 100);
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_unused_shared_future_is_not_a_broken_future) {
+ promise<> p;
+ shared_future<> s(p.get_future());
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_shared_future_propagates_value_to_all) {
+ return seastar::async([] {
+ promise<shared_ptr<int>> p; // shared_ptr<> to check it deals with emptyable types
+ shared_future<shared_ptr<int>> f(p.get_future());
+
+ auto f1 = f.get_future();
+ auto f2 = f.get_future();
+
+ p.set_value(make_shared<int>(1));
+ BOOST_REQUIRE(*f1.get0() == 1);
+ BOOST_REQUIRE(*f2.get0() == 1);
+ });
+}
+
+template<typename... T>
+void check_fails_with_expected(future<T...> f) {
+ try {
+ f.get();
+ BOOST_FAIL("Should have failed");
+ } catch (expected_exception&) {
+ // expected
+ }
+}
+
+SEASTAR_TEST_CASE(test_shared_future_propagates_value_to_copies) {
+ return seastar::async([] {
+ promise<int> p;
+ auto sf1 = shared_future<int>(p.get_future());
+ auto sf2 = sf1;
+
+ auto f1 = sf1.get_future();
+ auto f2 = sf2.get_future();
+
+ p.set_value(1);
+
+ BOOST_REQUIRE(f1.get0() == 1);
+ BOOST_REQUIRE(f2.get0() == 1);
+ });
+}
+
+SEASTAR_TEST_CASE(test_obtaining_future_from_shared_future_after_it_is_resolved) {
+ promise<int> p1;
+ promise<int> p2;
+ auto sf1 = shared_future<int>(p1.get_future());
+ auto sf2 = shared_future<int>(p2.get_future());
+ p1.set_value(1);
+ p2.set_exception(expected_exception());
+ return sf2.get_future().then_wrapped([f1 = sf1.get_future()] (auto&& f) mutable {
+ check_fails_with_expected(std::move(f));
+ return std::move(f1);
+ }).then_wrapped([] (auto&& f) {
+ BOOST_REQUIRE(f.get0() == 1);
+ });
+}
+
+SEASTAR_TEST_CASE(test_valueless_shared_future) {
+ return seastar::async([] {
+ promise<> p;
+ shared_future<> f(p.get_future());
+
+ auto f1 = f.get_future();
+ auto f2 = f.get_future();
+
+ p.set_value();
+
+ f1.get();
+ f2.get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_future_propagates_errors_to_all) {
+ promise<int> p;
+ shared_future<int> f(p.get_future());
+
+ auto f1 = f.get_future();
+ auto f2 = f.get_future();
+
+ p.set_exception(expected_exception());
+
+ return f1.then_wrapped([f2 = std::move(f2)] (auto&& f) mutable {
+ check_fails_with_expected(std::move(f));
+ return std::move(f2);
+ }).then_wrapped([] (auto&& f) mutable {
+ check_fails_with_expected(std::move(f));
+ });
+}
+
+SEASTAR_TEST_CASE(test_ignored_future_warning) {
+ // This doesn't warn:
+ promise<> p;
+ p.set_exception(expected_exception());
+ future<> f = p.get_future();
+ f.ignore_ready_future();
+
+ // And by analogy, neither should this
+ shared_promise<> p2;
+ p2.set_exception(expected_exception());
+ future<> f2 = p2.get_shared_future();
+ f2.ignore_ready_future();
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_futurize_from_tuple) {
+ std::tuple<int> v1 = std::make_tuple(3);
+ std::tuple<> v2 = {};
+ future<int> fut1 = futurize<int>::from_tuple(v1);
+ future<> fut2 = futurize<void>::from_tuple(v2);
+ BOOST_REQUIRE(fut1.get0() == std::get<0>(v1));
+#if SEASTAR_API_LEVEL < 5
+ BOOST_REQUIRE(fut2.get() == v2);
+#endif
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_repeat_until_value) {
+ return do_with(int(), [] (int& counter) {
+ return repeat_until_value([&counter] () -> future<std::optional<int>> {
+ if (counter == 10000) {
+ return make_ready_future<std::optional<int>>(counter);
+ } else {
+ ++counter;
+ return make_ready_future<std::optional<int>>(std::nullopt);
+ }
+ }).then([&counter] (int result) {
+ BOOST_REQUIRE(counter == 10000);
+ BOOST_REQUIRE(result == counter);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_repeat_until_value_implicit_future) {
+ // Same as above, but returning std::optional<int> instead of future<std::optional<int>>
+ return do_with(int(), [] (int& counter) {
+ return repeat_until_value([&counter] {
+ if (counter == 10000) {
+ return std::optional<int>(counter);
+ } else {
+ ++counter;
+ return std::optional<int>(std::nullopt);
+ }
+ }).then([&counter] (int result) {
+ BOOST_REQUIRE(counter == 10000);
+ BOOST_REQUIRE(result == counter);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_repeat_until_value_exception) {
+ return repeat_until_value([] {
+ throw expected_exception();
+ return std::optional<int>(43);
+ }).then_wrapped([] (future<int> f) {
+ check_fails_with_expected(std::move(f));
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_allx) {
+ return when_all(later(), later(), make_ready_future()).discard_result();
+}
+
+// A noncopyable and nonmovable struct
+struct non_copy_non_move {
+ non_copy_non_move() = default;
+ non_copy_non_move(non_copy_non_move&&) = delete;
+ non_copy_non_move(const non_copy_non_move&) = delete;
+};
+
+SEASTAR_TEST_CASE(test_when_all_functions) {
+ auto f = [x = non_copy_non_move()] {
+ (void)x;
+ return make_ready_future<int>(42);
+ };
+ return when_all(f, [] {
+ throw 42;
+ return make_ready_future<>();
+ }, later()).then([] (std::tuple<future<int>, future<>, future<>> res) {
+ BOOST_REQUIRE_EQUAL(std::get<0>(res).get0(), 42);
+
+ BOOST_REQUIRE(std::get<1>(res).available());
+ BOOST_REQUIRE(std::get<1>(res).failed());
+ std::get<1>(res).ignore_ready_future();
+
+ BOOST_REQUIRE(std::get<2>(res).available());
+ BOOST_REQUIRE(!std::get<2>(res).failed());
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_all_succeed_functions) {
+ auto f = [x = non_copy_non_move()] {
+ (void)x;
+ return make_ready_future<int>(42);
+ };
+ return when_all_succeed(f, [] {
+ throw 42;
+ return make_ready_future<>();
+ }, later()).then_wrapped([] (auto res) { // type of `res` changes when SESTAR_API_LEVEL < 3
+ BOOST_REQUIRE(res.available());
+ BOOST_REQUIRE(res.failed());
+ res.ignore_ready_future();
+ return make_ready_future<>();
+ });
+}
+
+template<typename E, typename... T>
+static void check_failed_with(future<T...>&& f) {
+ BOOST_REQUIRE(f.failed());
+ try {
+ f.get();
+ BOOST_FAIL("exception expected");
+ } catch (const E& e) {
+ // expected
+ } catch (...) {
+ BOOST_FAIL(format("wrong exception: {}", std::current_exception()));
+ }
+}
+
+template<typename... T>
+static void check_timed_out(future<T...>&& f) {
+ check_failed_with<timed_out_error>(std::move(f));
+}
+
+SEASTAR_TEST_CASE(test_with_timeout_when_it_times_out) {
+ return seastar::async([] {
+ promise<> pr;
+ auto f = with_timeout(manual_clock::now() + 2s, pr.get_future());
+
+ BOOST_REQUIRE(!f.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!f.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_timed_out(std::move(f));
+
+ pr.set_value();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_future_get_future_after_timeout) {
+ // This used to crash because shared_future checked if the list of
+ // pending futures was empty to decide if it had already called
+ // then_wrapped. If all pending futures timed out, it would call
+ // it again.
+ promise<> pr;
+ shared_future<with_clock<manual_clock>> sfut(pr.get_future());
+ future<> fut1 = sfut.get_future(manual_clock::now() + 1s);
+
+ manual_clock::advance(1s);
+
+ check_timed_out(std::move(fut1));
+
+ future<> fut2 = sfut.get_future(manual_clock::now() + 1s);
+ manual_clock::advance(1s);
+ check_timed_out(std::move(fut2));
+
+ future<> fut3 = sfut.get_future(manual_clock::now() + 1s);
+ pr.set_value();
+ fut3.get();
+}
+
+SEASTAR_TEST_CASE(test_custom_exception_factory_in_with_timeout) {
+ return seastar::async([] {
+ class custom_error : public std::exception {
+ public:
+ virtual const char* what() const noexcept {
+ return "timedout";
+ }
+ };
+ struct my_exception_factory {
+ static auto timeout() {
+ return custom_error();
+ }
+ };
+ promise<> pr;
+ auto f = with_timeout<my_exception_factory>(manual_clock::now() + 1s, pr.get_future());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_failed_with<custom_error>(std::move(f));
+ });
+}
+
+SEASTAR_TEST_CASE(test_with_timeout_when_it_does_not_time_out) {
+ return seastar::async([] {
+ {
+ promise<int> pr;
+ auto f = with_timeout(manual_clock::now() + 1s, pr.get_future());
+
+ pr.set_value(42);
+
+ BOOST_REQUIRE_EQUAL(f.get0(), 42);
+ }
+
+ // Check that timer was indeed cancelled
+ manual_clock::advance(1s);
+ later().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_future_with_timeout) {
+ return seastar::async([] {
+ shared_promise<with_clock<manual_clock>, int> pr;
+ auto f1 = pr.get_shared_future(manual_clock::now() + 1s);
+ auto f2 = pr.get_shared_future(manual_clock::now() + 2s);
+ auto f3 = pr.get_shared_future();
+
+ BOOST_REQUIRE(!f1.available());
+ BOOST_REQUIRE(!f2.available());
+ BOOST_REQUIRE(!f3.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_timed_out(std::move(f1));
+ BOOST_REQUIRE(!f2.available());
+ BOOST_REQUIRE(!f3.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_timed_out(std::move(f2));
+ BOOST_REQUIRE(!f3.available());
+
+ pr.set_value(42);
+
+ BOOST_REQUIRE_EQUAL(42, f3.get0());
+ });
+}
+
+#if SEASTAR_API_LEVEL < 4
+#define THEN_UNPACK then
+#else
+#define THEN_UNPACK then_unpack
+#endif
+
+SEASTAR_TEST_CASE(test_when_all_succeed_tuples) {
+ return seastar::when_all_succeed(
+ make_ready_future<>(),
+ make_ready_future<sstring>("hello world"),
+ make_ready_future<int>(42),
+ make_ready_future<>(),
+ make_ready_future<std::tuple<int, sstring>>(std::tuple(84, "hi")),
+ make_ready_future<bool>(true)
+ ).THEN_UNPACK([] (sstring msg, int v, std::tuple<int, sstring> t, bool b) {
+ BOOST_REQUIRE_EQUAL(msg, "hello world");
+ BOOST_REQUIRE_EQUAL(v, 42);
+ BOOST_REQUIRE_EQUAL(std::get<0>(t), 84);
+ BOOST_REQUIRE_EQUAL(std::get<1>(t), "hi");
+ BOOST_REQUIRE_EQUAL(b, true);
+
+ return seastar::when_all_succeed(
+ make_exception_future<>(42),
+ make_ready_future<sstring>("hello world"),
+ make_exception_future<int>(43),
+ make_ready_future<>()
+ ).THEN_UNPACK([] (sstring, int) {
+ BOOST_FAIL("shouldn't reach");
+ return false;
+ }).handle_exception([] (auto excp) {
+ try {
+ std::rethrow_exception(excp);
+ } catch (int v) {
+ BOOST_REQUIRE(v == 42 || v == 43);
+ return true;
+ } catch (...) { }
+ return false;
+ }).then([] (auto ret) {
+ BOOST_REQUIRE(ret);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_all_succeed_vector) {
+ std::vector<future<>> vecs;
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ return seastar::when_all_succeed(vecs.begin(), vecs.end()).then([] {
+ std::vector<future<>> vecs;
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_exception_future<>(42));
+ vecs.emplace_back(make_exception_future<>(43));
+ return seastar::when_all_succeed(vecs.begin(), vecs.end());
+ }).then([] {
+ BOOST_FAIL("shouldn't reach");
+ return false;
+ }).handle_exception([] (auto excp) {
+ try {
+ std::rethrow_exception(excp);
+ } catch (int v) {
+ BOOST_REQUIRE(v == 42 || v == 43);
+ return true;
+ } catch (...) { }
+ return false;
+ }).then([] (auto ret) {
+ BOOST_REQUIRE(ret);
+
+ std::vector<future<int>> vecs;
+ vecs.emplace_back(make_ready_future<int>(1));
+ vecs.emplace_back(make_ready_future<int>(2));
+ vecs.emplace_back(make_ready_future<int>(3));
+ return seastar::when_all_succeed(vecs.begin(), vecs.end());
+ }).then([] (std::vector<int> vals) {
+ BOOST_REQUIRE_EQUAL(vals.size(), 3u);
+ BOOST_REQUIRE_EQUAL(vals[0], 1);
+ BOOST_REQUIRE_EQUAL(vals[1], 2);
+ BOOST_REQUIRE_EQUAL(vals[2], 3);
+
+ std::vector<future<int>> vecs;
+ vecs.emplace_back(make_ready_future<int>(1));
+ vecs.emplace_back(make_ready_future<int>(2));
+ vecs.emplace_back(make_exception_future<int>(42));
+ vecs.emplace_back(make_exception_future<int>(43));
+ return seastar::when_all_succeed(vecs.begin(), vecs.end());
+ }).then([] (std::vector<int>) {
+ BOOST_FAIL("shouldn't reach");
+ return false;
+ }).handle_exception([] (auto excp) {
+ try {
+ std::rethrow_exception(excp);
+ } catch (int v) {
+ BOOST_REQUIRE(v == 42 || v == 43);
+ return true;
+ } catch (...) { }
+ return false;
+ }).then([] (auto ret) {
+ BOOST_REQUIRE(ret);
+ });
+}
+
+SEASTAR_TEST_CASE(test_futurize_mutable) {
+ int count = 0;
+ return seastar::repeat([count]() mutable {
+ ++count;
+ if (count == 3) {
+ return seastar::stop_iteration::yes;
+ }
+ return seastar::stop_iteration::no;
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_broken_promises) {
+ std::optional<future<>> f;
+ std::optional<future<>> f2;
+ { // Broken after attaching a continuation
+ auto p = promise<>();
+ f = p.get_future();
+ f2 = f->then_wrapped([&] (future<> f3) {
+ BOOST_CHECK(f3.failed());
+ BOOST_CHECK_THROW(f3.get(), broken_promise);
+ f = { };
+ });
+ }
+ f2->get();
+ BOOST_CHECK(!f);
+
+ { // Broken before attaching a continuation
+ auto p = promise<>();
+ f = p.get_future();
+ }
+ f->then_wrapped([&] (future<> f3) {
+ BOOST_CHECK(f3.failed());
+ BOOST_CHECK_THROW(f3.get(), broken_promise);
+ f = { };
+ }).get();
+ BOOST_CHECK(!f);
+
+ { // Broken before suspending a thread
+ auto p = promise<>();
+ f = p.get_future();
+ }
+ BOOST_CHECK_THROW(f->get(), broken_promise);
+}
+
+SEASTAR_TEST_CASE(test_warn_on_broken_promise_with_no_future) {
+ // Example code where we expect a "Exceptional future ignored"
+ // warning.
+ promise<> p;
+ // Intentionally destroy the future
+ (void)p.get_future();
+
+ with_allow_abandoned_failed_futures(1, [&] {
+ p.set_exception(std::runtime_error("foo"));
+ });
+
+ return make_ready_future<>();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_exception_future_with_backtrace) {
+ int counter = 0;
+ auto inner = [&] (bool return_exception) mutable {
+ if (!return_exception) {
+ return make_ready_future<int>(++counter);
+ } else {
+ return make_exception_future_with_backtrace<int>(expected_exception());
+ }
+ };
+ auto outer = [&] (bool return_exception) {
+ return inner(return_exception).then([] (int i) {
+ return make_ready_future<int>(-i);
+ });
+ };
+
+ BOOST_REQUIRE_EQUAL(outer(false).get0(), -1);
+ BOOST_REQUIRE_EQUAL(counter, 1);
+
+ BOOST_CHECK_THROW(outer(true).get0(), expected_exception);
+ BOOST_REQUIRE_EQUAL(counter, 1);
+
+ // Example code where we expect a "Exceptional future ignored"
+ // warning.
+ (void)outer(true).then_wrapped([](future<int> fut) {
+ with_allow_abandoned_failed_futures(1, [fut = std::move(fut)]() mutable {
+ auto foo = std::move(fut);
+ });
+ });
+}
+
+class throw_on_move {
+ int _i;
+public:
+ throw_on_move(int i = 0) noexcept {
+ _i = i;
+ }
+ throw_on_move(const throw_on_move&) = delete;
+ throw_on_move(throw_on_move&&) {
+ _i = -1;
+ throw expected_exception();
+ }
+
+ int value() const {
+ return _i;
+ }
+};
+
+SEASTAR_TEST_CASE(test_async_throw_on_move) {
+ return async([] (throw_on_move t) {
+ BOOST_CHECK(false);
+ }, throw_on_move()).handle_exception_type([] (const expected_exception&) {
+ return make_ready_future<>();
+ });
+}
+
+future<> func4() {
+ return later().then([] {
+ seastar_logger.info("backtrace: {}", current_backtrace());
+ });
+}
+
+void func3() {
+ seastar::async([] {
+ func4().get();
+ }).get();
+}
+
+future<> func2() {
+ return seastar::async([] {
+ func3();
+ });
+}
+
+future<> func1() {
+ return later().then([] {
+ return func2();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_backtracing) {
+ func1().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_then_unpack) {
+ make_ready_future<std::tuple<>>().then_unpack([] () {
+ BOOST_REQUIRE(true);
+ }).get();
+ make_ready_future<std::tuple<int>>(std::tuple<int>(1)).then_unpack([] (int x) {
+ BOOST_REQUIRE(x == 1);
+ }).get();
+ make_ready_future<std::tuple<int, long>>(std::tuple<int, long>(1, 2)).then_unpack([] (int x, long y) {
+ BOOST_REQUIRE(x == 1 && y == 2);
+ }).get();
+ make_ready_future<std::tuple<std::unique_ptr<int>>>(std::tuple(std::make_unique<int>(42))).then_unpack([] (std::unique_ptr<int> p1) {
+ BOOST_REQUIRE(*p1 == 42);
+ }).get();
+}
+
+future<> test_then_function_f() {
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_then_function) {
+ return make_ready_future<>().then(test_then_function_f);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_with_gate) {
+ gate g;
+ int counter = 0;
+ int gate_closed_errors = 0;
+ int other_errors = 0;
+
+ // test normal operation when gate is opened
+ BOOST_CHECK_NO_THROW(with_gate(g, [&] { counter++; }).get());
+ BOOST_REQUIRE_EQUAL(counter, 1);
+
+ // test that an exception returned by the calling func
+ // is propagated to with_gate future
+ counter = gate_closed_errors = other_errors = 0;
+ BOOST_CHECK_NO_THROW(with_gate(g, [&] {
+ counter++;
+ return make_exception_future<>(expected_exception());
+ }).handle_exception_type([&] (gate_closed_exception& e) {
+ gate_closed_errors++;
+ }).handle_exception([&] (std::exception_ptr) {
+ other_errors++;
+ }).get());
+ BOOST_REQUIRE(counter);
+ BOOST_REQUIRE(!gate_closed_errors);
+ BOOST_REQUIRE(other_errors);
+
+ g.close().get();
+
+ // test that with_gate.get() throws when the gate is closed
+ counter = gate_closed_errors = other_errors = 0;
+ BOOST_CHECK_THROW(with_gate(g, [&] { counter++; }).get(), gate_closed_exception);
+ BOOST_REQUIRE(!counter);
+
+ // test that with_gate throws when the gate is closed
+ counter = gate_closed_errors = other_errors = 0;
+ BOOST_CHECK_THROW(with_gate(g, [&] {
+ counter++;
+ }).then_wrapped([&] (future<> f) {
+ auto eptr = f.get_exception();
+ try {
+ std::rethrow_exception(eptr);
+ } catch (gate_closed_exception& e) {
+ gate_closed_errors++;
+ } catch (...) {
+ other_errors++;
+ }
+ }).get(), gate_closed_exception);
+ BOOST_REQUIRE(!counter);
+ BOOST_REQUIRE(!gate_closed_errors);
+ BOOST_REQUIRE(!other_errors);
+
+ // test that try_with_gate returns gate_closed_exception when the gate is closed
+ counter = gate_closed_errors = other_errors = 0;
+ try_with_gate(g, [&] { counter++; }).handle_exception_type([&] (gate_closed_exception& e) {
+ gate_closed_errors++;
+ }).handle_exception([&] (std::exception_ptr) {
+ other_errors++;
+ }).get();
+ BOOST_REQUIRE(!counter);
+ BOOST_REQUIRE(gate_closed_errors);
+ BOOST_REQUIRE(!other_errors);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_max_concurrent_for_each) {
+ BOOST_TEST_MESSAGE("empty range");
+ max_concurrent_for_each(std::vector<int>(), 3, [] (int) {
+ BOOST_FAIL("should not reach");
+ return make_exception_future<>(std::bad_function_call());
+ }).get();
+
+ auto range = boost::copy_range<std::vector<int>>(boost::irange(1, 8));
+
+ BOOST_TEST_MESSAGE("iterator");
+ auto sum = 0;
+ max_concurrent_for_each(range.begin(), range.end(), 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("const iterator");
+ sum = 0;
+ max_concurrent_for_each(range.cbegin(), range.cend(), 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("reverse iterator");
+ sum = 0;
+ max_concurrent_for_each(range.rbegin(), range.rend(), 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("immediate result");
+ sum = 0;
+ max_concurrent_for_each(range, 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("suspend");
+ sum = 0;
+ max_concurrent_for_each(range, 3, [&sum] (int v) {
+ return later().then([&sum, v] {
+ sum += v;
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("throw immediately");
+ sum = 0;
+ BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3, [&sum] (int v) {
+ sum += v;
+ if (v == 1) {
+ throw 5;
+ }
+ return make_ready_future<>();
+ }).get(), int, [] (int v) { return v == 5; });
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("throw after suspension");
+ sum = 0;
+ BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3, [&sum] (int v) {
+ return later().then([&sum, v] {
+ sum += v;
+ if (v == 2) {
+ throw 5;
+ }
+ });
+ }).get(), int, [] (int v) { return v == 5; });
+
+ BOOST_TEST_MESSAGE("concurrency higher than vector length");
+ sum = 0;
+ max_concurrent_for_each(range, range.size() + 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+}
diff --git a/src/seastar/tests/unit/httpd_test.cc b/src/seastar/tests/unit/httpd_test.cc
new file mode 100644
index 000000000..2428f038d
--- /dev/null
+++ b/src/seastar/tests/unit/httpd_test.cc
@@ -0,0 +1,889 @@
+/*
+ * Copyright 2015 Cloudius Systems
+ */
+
+#include <seastar/http/httpd.hh>
+#include <seastar/http/handlers.hh>
+#include <seastar/http/matcher.hh>
+#include <seastar/http/matchrules.hh>
+#include <seastar/json/formatter.hh>
+#include <seastar/http/routes.hh>
+#include <seastar/http/exception.hh>
+#include <seastar/http/transformers.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/when_all.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include "loopback_socket.hh"
+#include <boost/algorithm/string.hpp>
+#include <seastar/core/thread.hh>
+#include <seastar/util/noncopyable_function.hh>
+#include <seastar/http/json_path.hh>
+#include <sstream>
+
+using namespace seastar;
+using namespace httpd;
+
+class handl : public httpd::handler_base {
+public:
+ virtual future<std::unique_ptr<reply> > handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
+ rep->done("html");
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+};
+
+SEASTAR_TEST_CASE(test_reply)
+{
+ reply r;
+ r.set_content_type("txt");
+ BOOST_REQUIRE_EQUAL(r._headers["Content-Type"], sstring("text/plain"));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_str_matcher)
+{
+
+ str_matcher m("/hello");
+ parameters param;
+ BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_param_matcher)
+{
+
+ param_matcher m("param");
+ parameters param;
+ BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
+ BOOST_REQUIRE_EQUAL(param.path("param"), "/hello");
+ BOOST_REQUIRE_EQUAL(param["param"], "hello");
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_match_rule)
+{
+
+ parameters param;
+ handl* h = new handl();
+ match_rule mr(h);
+ mr.add_str("/hello").add_param("param");
+ httpd::handler_base* res = mr.get("/hello/val1", param);
+ BOOST_REQUIRE_EQUAL(res, h);
+ BOOST_REQUIRE_EQUAL(param["param"], "val1");
+ res = mr.get("/hell/val1", param);
+ httpd::handler_base* nl = nullptr;
+ BOOST_REQUIRE_EQUAL(res, nl);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_match_rule_order)
+{
+ parameters param;
+ routes route;
+
+ handl* h1 = new handl();
+ route.add(operation_type::GET, url("/hello"), h1);
+
+ handl* h2 = new handl();
+ route.add(operation_type::GET, url("/hello"), h2);
+
+ auto rh = route.get_handler(GET, "/hello", param);
+ BOOST_REQUIRE_EQUAL(rh, h1);
+
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_put_drop_rule)
+{
+ routes rts;
+ auto h = std::make_unique<handl>();
+ parameters params;
+
+ {
+ auto reg = handler_registration(rts, *h, "/hello", operation_type::GET);
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ BOOST_REQUIRE_EQUAL(res, h.get());
+ }
+
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ httpd::handler_base* nl = nullptr;
+ BOOST_REQUIRE_EQUAL(res, nl);
+ return make_ready_future<>();
+}
+
+// Putting a duplicated exact rule would result
+// in a memory leak due to the fact that rules are implemented
+// as raw pointers. In order to prevent such leaks,
+// an exception is thrown if somebody tries to put
+// a duplicated rule without removing the old one first.
+// The interface demands that the callee allocates the handle,
+// so it should also expect the callee to free it before
+// overwriting.
+SEASTAR_TEST_CASE(test_duplicated_exact_rule)
+{
+ parameters param;
+ routes route;
+
+ handl* h1 = new handl;
+ route.put(operation_type::GET, "/hello", h1);
+
+ handl* h2 = new handl;
+ BOOST_REQUIRE_THROW(route.put(operation_type::GET, "/hello", h2), std::runtime_error);
+
+ delete route.drop(operation_type::GET, "/hello");
+ route.put(operation_type::GET, "/hello", h2);
+
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_add_del_cookie)
+{
+ routes rts;
+ handl* h = new handl();
+ match_rule mr(h);
+ mr.add_str("/hello");
+ parameters params;
+
+ {
+ auto reg = rule_registration(rts, mr, operation_type::GET);
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ BOOST_REQUIRE_EQUAL(res, h);
+ }
+
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ httpd::handler_base* nl = nullptr;
+ BOOST_REQUIRE_EQUAL(res, nl);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_formatter)
+{
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(true), "true");
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(false), "false");
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(1), "1");
+ const char* txt = "efg";
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(txt), "\"efg\"");
+ sstring str = "abc";
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(str), "\"abc\"");
+ float f = 1;
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(f), "1");
+ f = 1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
+ f = -1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
+ f = 0.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(f), std::invalid_argument);
+ double d = -1;
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(d), "-1");
+ d = 1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
+ d = -1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
+ d = 0.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(d), std::invalid_argument);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_decode_url) {
+ request req;
+ req._url = "/a?q=%23%24%23";
+ sstring url = http_server::connection::set_query_param(req);
+ BOOST_REQUIRE_EQUAL(url, "/a");
+ BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#");
+ req._url = "/a?a=%23%24%23&b=%22%26%22";
+ http_server::connection::set_query_param(req);
+ BOOST_REQUIRE_EQUAL(req.get_query_param("a"), "#$#");
+ BOOST_REQUIRE_EQUAL(req.get_query_param("b"), "\"&\"");
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_routes) {
+ handl* h1 = new handl();
+ handl* h2 = new handl();
+ routes route;
+ route.add(operation_type::GET, url("/api").remainder("path"), h1);
+ route.add(operation_type::GET, url("/"), h2);
+ std::unique_ptr<request> req = std::make_unique<request>();
+ std::unique_ptr<reply> rep = std::make_unique<reply>();
+
+ auto f1 =
+ route.handle("/api", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
+ });
+ req.reset(new request);
+ rep.reset(new reply);
+
+ auto f2 =
+ route.handle("/", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
+ });
+ req.reset(new request);
+ rep.reset(new reply);
+ auto f3 =
+ route.handle("/api/abc", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ });
+ req.reset(new request);
+ rep.reset(new reply);
+ auto f4 =
+ route.handle("/ap", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ BOOST_REQUIRE_EQUAL((int )rep->_status,
+ (int )reply::status_type::not_found);
+ });
+ return when_all(std::move(f1), std::move(f2), std::move(f3), std::move(f4))
+ .then([] (std::tuple<future<>, future<>, future<>, future<>> fs) {
+ std::get<0>(fs).get();
+ std::get<1>(fs).get();
+ std::get<2>(fs).get();
+ std::get<3>(fs).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_json_path) {
+ shared_ptr<bool> res1 = make_shared<bool>(false);
+ shared_ptr<bool> res2 = make_shared<bool>(false);
+ shared_ptr<bool> res3 = make_shared<bool>(false);
+ shared_ptr<routes> route = make_shared<routes>();
+ path_description path1("/my/path",GET,"path1",
+ {{"param1", path_description::url_component_type::PARAM}
+ ,{"/text", path_description::url_component_type::FIXED_STRING}},{});
+ path_description path2("/my/path",GET,"path2",
+ {{"param1", path_description::url_component_type::PARAM}
+ ,{"param2", path_description::url_component_type::PARAM}},{});
+ path_description path3("/my/path",GET,"path3",
+ {{"param1", path_description::url_component_type::PARAM}
+ ,{"param2", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH}},{});
+
+ path1.set(*route, [res1] (const_req req) {
+ (*res1) = true;
+ BOOST_REQUIRE_EQUAL(req.param["param1"], "value1");
+ return "";
+ });
+
+ path2.set(*route, [res2] (const_req req) {
+ (*res2) = true;
+ BOOST_REQUIRE_EQUAL(req.param["param1"], "value2");
+ BOOST_REQUIRE_EQUAL(req.param["param2"], "text1");
+ return "";
+ });
+
+ path3.set(*route, [res3] (const_req req) {
+ (*res3) = true;
+ BOOST_REQUIRE_EQUAL(req.param["param1"], "value3");
+ BOOST_REQUIRE_EQUAL(req.param["param2"], "text2/text3");
+ return "";
+ });
+
+ auto f1 = route->handle("/my/path/value1/text", std::make_unique<request>(), std::make_unique<reply>()).then([res1, route] (auto f) {
+ BOOST_REQUIRE_EQUAL(*res1, true);
+ });
+
+ auto f2 = route->handle("/my/path/value2/text1", std::make_unique<request>(), std::make_unique<reply>()).then([res2, route] (auto f) {
+ BOOST_REQUIRE_EQUAL(*res2, true);
+ });
+
+ auto f3 = route->handle("/my/path/value3/text2/text3", std::make_unique<request>(), std::make_unique<reply>()).then([res3, route] (auto f) {
+ BOOST_REQUIRE_EQUAL(*res3, true);
+ });
+
+ return when_all(std::move(f1), std::move(f2), std::move(f3))
+ .then([] (std::tuple<future<>, future<>, future<>> fs) {
+ std::get<0>(fs).get();
+ std::get<1>(fs).get();
+ std::get<2>(fs).get();
+ });
+}
+
+/*!
+ * \brief a helper data sink that stores everything it gets in a stringstream
+ */
+class memory_data_sink_impl : public data_sink_impl {
+ std::stringstream& _ss;
+public:
+ memory_data_sink_impl(std::stringstream& ss) : _ss(ss) {
+ }
+ virtual future<> put(net::packet data) override {
+ abort();
+ return make_ready_future<>();
+ }
+ virtual future<> put(temporary_buffer<char> buf) override {
+ _ss.write(buf.get(), buf.size());
+ return make_ready_future<>();
+ }
+ virtual future<> flush() override {
+ return make_ready_future<>();
+ }
+
+ virtual future<> close() override {
+ return make_ready_future<>();
+ }
+};
+
+class memory_data_sink : public data_sink {
+public:
+ memory_data_sink(std::stringstream& ss)
+ : data_sink(std::make_unique<memory_data_sink_impl>(ss)) {}
+};
+
+future<> test_transformer_stream(std::stringstream& ss, content_replace& cr, std::vector<sstring>&& buffer_parts) {
+ std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
+ ss.str("");
+ req->_headers["Host"] = "localhost";
+ return do_with(output_stream<char>(cr.transform(std::move(req), "json", output_stream<char>(memory_data_sink(ss), 32000, true))),
+ std::vector<sstring>(std::move(buffer_parts)), [] (output_stream<char>& os, std::vector<sstring>& parts) {
+ return do_for_each(parts, [&os](auto& p) {
+ return os.write(p);
+ }).then([&os] {
+ return os.close();
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_transformer) {
+ return do_with(std::stringstream(), content_replace("json"), [] (std::stringstream& ss, content_replace& cr) {
+ return do_with(output_stream<char>(cr.transform(std::make_unique<seastar::httpd::request>(), "html", output_stream<char>(memory_data_sink(ss), 32000, true))),
+ [] (output_stream<char>& os) {
+ return os.write(sstring("hello-{{Protocol}}-xyz-{{Host}}")).then([&os] {
+ return os.close();
+ });
+ }).then([&ss, &cr] () {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Protocol}}-xyz-{{Host}}");
+ return test_transformer_stream(ss, cr, {"hell", "o-{", "{Pro", "tocol}}-xyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-http-xyz-localhost{{Pr");
+ return test_transformer_stream(ss, cr, {"hell", "o-{{", "Pro", "tocol}}{{Protocol}}-{{Protoxyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-httphttp-{{Protoxyz-localhost{{Pr");
+ return test_transformer_stream(ss, cr, {"hell", "o-{{Pro", "t{{Protocol}}ocol}}", "{{Host}}"}).then([&ss] {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Prothttpocol}}localhost");
+ });
+ });
+ });
+ });
+ });
+}
+
+struct http_consumer {
+ std::map<sstring, std::string> _headers;
+ std::string _body;
+ uint32_t _remain = 0;
+ std::string _current;
+ char last = '\0';
+ uint32_t _size = 0;
+ bool _concat = true;
+
+ enum class status_type {
+ READING_HEADERS,
+ CHUNK_SIZE,
+ CHUNK_BODY,
+ CHUNK_END,
+ READING_BODY_BY_SIZE,
+ DONE
+ };
+ status_type status = status_type::READING_HEADERS;
+
+ bool read(const temporary_buffer<char>& b) {
+ for (auto c : b) {
+ if (last =='\r' && c == '\n') {
+ if (_current == "") {
+ if (status == status_type::READING_HEADERS || (status == status_type::CHUNK_BODY && _remain == 0)) {
+ if (status == status_type::READING_HEADERS && _headers.find("Content-Length") != _headers.end()) {
+ _remain = stoi(_headers["Content-Length"], nullptr, 16);
+ if (_remain == 0) {
+ status = status_type::DONE;
+ break;
+ }
+ status = status_type::READING_BODY_BY_SIZE;
+ } else {
+ status = status_type::CHUNK_SIZE;
+ }
+ } else if (status == status_type::CHUNK_END) {
+ status = status_type::DONE;
+ break;
+ }
+ } else {
+ switch (status) {
+ case status_type::READING_HEADERS: add_header(_current);
+ break;
+ case status_type::CHUNK_SIZE: set_chunk(_current);
+ break;
+ default:
+ break;
+ }
+ _current = "";
+ }
+ last = '\0';
+ } else {
+ if (last != '\0') {
+ if (status == status_type::CHUNK_BODY || status == status_type::READING_BODY_BY_SIZE) {
+ if (_concat) {
+ _body = _body + last;
+ }
+ _size++;
+ _remain--;
+ if (_remain <= 1 && status == status_type::READING_BODY_BY_SIZE) {
+ if (_concat) {
+ _body = _body + c;
+ }
+ _size++;
+ status = status_type::DONE;
+ break;
+ }
+ } else {
+ _current = _current + last;
+ }
+
+ }
+ last = c;
+ }
+ }
+ return status == status_type::DONE;
+ }
+
+ void set_chunk(const std::string& s) {
+ _remain = stoi(s, nullptr, 16);
+ if (_remain == 0) {
+ status = status_type::CHUNK_END;
+ } else {
+ status = status_type::CHUNK_BODY;
+ }
+ }
+
+ void add_header(const std::string& s) {
+ std::vector<std::string> strs;
+ boost::split(strs, s, boost::is_any_of(":"));
+ if (strs.size() > 1) {
+ _headers[strs[0]] = strs[1];
+ }
+ }
+};
+
+class test_client_server {
+public:
+ static future<> write_request(output_stream<char>& output) {
+ return output.write(sstring("GET /test HTTP/1.1\r\nHost: myhost.org\r\n\r\n")).then([&output]{
+ return output.flush();
+ });
+ }
+
+ static future<> run_test(std::function<future<>(output_stream<char> &&)>&& write_func, std::function<bool(size_t, http_consumer&)> reader) {
+ return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
+ [reader, &write_func] (loopback_connection_factory& lcf, auto& server) {
+ return do_with(loopback_socket_impl(lcf), [&server, &lcf, reader, &write_func](loopback_socket_impl& lsi) {
+ httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
+
+ auto client = seastar::async([&lsi, reader] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+ bool more = true;
+ size_t count = 0;
+ while (more) {
+ http_consumer htp;
+ htp._concat = false;
+
+ write_request(output).get();
+ repeat([&input, &htp] {
+ return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
+ return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
+ make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ }).get();
+ std::cout << htp._body << std::endl;
+ more = reader(count, htp);
+ count++;
+ }
+ if (input.eof()) {
+ input.close().get();
+ }
+ });
+
+ auto server_setup = seastar::async([&server, &write_func] {
+ class test_handler : public handler_base {
+ size_t count = 0;
+ http_server& _server;
+ std::function<future<>(output_stream<char> &&)> _write_func;
+ promise<> _all_message_sent;
+ public:
+ test_handler(http_server& server, std::function<future<>(output_stream<char> &&)>&& write_func) : _server(server), _write_func(write_func) {
+ }
+ future<std::unique_ptr<reply>> handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
+ rep->write_body("json", std::move(_write_func));
+ count++;
+ _all_message_sent.set_value();
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+ future<> wait_for_message() {
+ return _all_message_sent.get_future();
+ }
+ };
+ auto handler = new test_handler(*server, std::move(write_func));
+ server->_routes.put(GET, "/test", handler);
+ when_all(server->do_accepts(0), handler->wait_for_message()).get();
+ });
+ return when_all(std::move(client), std::move(server_setup));
+ }).discard_result().then_wrapped([&server] (auto f) {
+ f.ignore_ready_future();
+ return server->stop();
+ });
+ });
+ }
+ static future<> run(std::vector<std::tuple<bool, size_t>> tests) {
+ return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
+ [tests] (loopback_connection_factory& lcf, auto& server) {
+ return do_with(loopback_socket_impl(lcf), [&server, &lcf, tests](loopback_socket_impl& lsi) {
+ httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
+
+ auto client = seastar::async([&lsi, tests] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+ bool more = true;
+ size_t count = 0;
+ while (more) {
+ http_consumer htp;
+ write_request(output).get();
+ repeat([&input, &htp] {
+ return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
+ return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
+ make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ }).get();
+ if (std::get<bool>(tests[count])) {
+ BOOST_REQUIRE_EQUAL(htp._body.length(), std::get<size_t>(tests[count]));
+ } else {
+ BOOST_REQUIRE_EQUAL(input.eof(), true);
+ more = false;
+ }
+ count++;
+ if (count == tests.size()) {
+ more = false;
+ }
+ }
+ if (input.eof()) {
+ input.close().get();
+ }
+ });
+
+ auto server_setup = seastar::async([&server, tests] {
+ class test_handler : public handler_base {
+ size_t count = 0;
+ http_server& _server;
+ std::vector<std::tuple<bool, size_t>> _tests;
+ promise<> _all_message_sent;
+ public:
+ test_handler(http_server& server, const std::vector<std::tuple<bool, size_t>>& tests) : _server(server), _tests(tests) {
+ }
+ future<std::unique_ptr<reply>> handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
+ rep->write_body("txt", make_writer(std::get<size_t>(_tests[count]), std::get<bool>(_tests[count])));
+ count++;
+ if (count == _tests.size()) {
+ _all_message_sent.set_value();
+ }
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+ future<> wait_for_message() {
+ return _all_message_sent.get_future();
+ }
+ };
+ auto handler = new test_handler(*server, tests);
+ server->_routes.put(GET, "/test", handler);
+ when_all(server->do_accepts(0), handler->wait_for_message()).get();
+ });
+ return when_all(std::move(client), std::move(server_setup));
+ }).discard_result().then_wrapped([&server] (auto f) {
+ f.ignore_ready_future();
+ return server->stop();
+ });
+ });
+ }
+
+ static noncopyable_function<future<>(output_stream<char>&& o_stream)> make_writer(size_t len, bool success) {
+ return [len, success] (output_stream<char>&& o_stream) mutable {
+ return do_with(output_stream<char>(std::move(o_stream)), uint32_t(len/10), [success](output_stream<char>& str, uint32_t& remain) {
+ if (remain == 0) {
+ if (success) {
+ return str.close();
+ } else {
+ throw std::runtime_error("Throwing exception before writing");
+ }
+ }
+ return repeat([&str, &remain] () mutable {
+ return str.write("1234567890").then([&remain]() mutable {
+ remain--;
+ return (remain == 0)? make_ready_future<stop_iteration>(stop_iteration::yes) : make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ }).then([&str, success] {
+ if (!success) {
+ return str.flush();
+ }
+ return make_ready_future<>();
+ }).then([&str, success] {
+ if (success) {
+ return str.close();
+ } else {
+ throw std::runtime_error("Throwing exception after writing");
+ }
+ });
+ });
+ };
+ }
+};
+
+SEASTAR_TEST_CASE(test_message_with_error_non_empty_body) {
+ std::vector<std::tuple<bool, size_t>> tests = {
+ std::make_tuple(true, 100),
+ std::make_tuple(false, 10000)};
+ return test_client_server::run(tests);
+}
+
+SEASTAR_TEST_CASE(test_simple_chunked) {
+ std::vector<std::tuple<bool, size_t>> tests = {
+ std::make_tuple(true, 100000),
+ std::make_tuple(true, 100)};
+ return test_client_server::run(tests);
+}
+
+SEASTAR_TEST_CASE(test_http_client_server_full) {
+ std::vector<std::tuple<bool, size_t>> tests = {
+ std::make_tuple(true, 100),
+ std::make_tuple(true, 10000),
+ std::make_tuple(true, 100),
+ std::make_tuple(true, 0),
+ std::make_tuple(true, 5000),
+ std::make_tuple(true, 10000),
+ std::make_tuple(true, 9000),
+ std::make_tuple(true, 10000)};
+ return test_client_server::run(tests);
+}
+
+/*
+ * return string in the given size
+ * The string size takes the quotes into consideration.
+ */
+std::string get_value(int size) {
+ std::stringstream res;
+ for (auto i = 0; i < size - 2; i++) {
+ res << "a";
+ }
+ return res.str();
+}
+
+/*
+ * A helper object that map to a big json string
+ * in the format of:
+ * {"valu": "aaa....aa", "valu": "aaa....aa", "valu": "aaa....aa"...}
+ *
+ * The object can have an arbitrary size in multiplication of 10000 bytes
+ * */
+struct extra_big_object : public json::json_base {
+ json::json_element<sstring>* value;
+ extra_big_object(size_t size) {
+ value = new json::json_element<sstring>;
+ // size = brackets + (name + ": " + get_value) * n + ", " * (n-1)
+ // size = 2 + (name + 6 + get_value) * n - 2
+ value->_name = "valu";
+ *value = get_value(9990);
+ for (size_t i = 0; i < size/10000; i++) {
+ _elements.emplace_back(value);
+ }
+ }
+
+ virtual ~extra_big_object() {
+ delete value;
+ }
+
+ extra_big_object(const extra_big_object& o) {
+ value = new json::json_element<sstring>;
+ value->_name = o.value->_name;
+ *value = (*o.value)();
+ for (size_t i = 0; i < o._elements.size(); i++) {
+ _elements.emplace_back(value);
+ }
+ }
+};
+
+SEASTAR_TEST_CASE(json_stream) {
+ std::vector<extra_big_object> vec;
+ size_t num_objects = 1000;
+ size_t total_size = num_objects * 1000001 + 1;
+ for (size_t i = 0; i < num_objects; i++) {
+ vec.emplace_back(1000000);
+ }
+ return test_client_server::run_test(json::stream_object(vec), [total_size](size_t s, http_consumer& h) {
+ BOOST_REQUIRE_EQUAL(h._size, total_size);
+ return false;
+ });
+}
+
+class json_test_handler : public handler_base {
+ std::function<future<>(output_stream<char> &&)> _write_func;
+public:
+ json_test_handler(std::function<future<>(output_stream<char> &&)>&& write_func) : _write_func(write_func) {
+ }
+ future<std::unique_ptr<reply>> handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
+ rep->write_body("json", _write_func);
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+};
+
+SEASTAR_TEST_CASE(content_length_limit) {
+ return seastar::async([] {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ server.set_content_length_limit(11);
+ loopback_socket_impl lsi(lcf);
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+
+ future<> client = seastar::async([&lsi] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 11\r\n\r\nxxxxxxxxxxx")).get();
+ output.flush().get();
+ resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\n\r\nxxxxxxxxxxxxxxxx")).get();
+ output.flush().get();
+ resp = input.read().get0();
+ BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
+
+ input.close().get();
+ output.close().get();
+ });
+
+ auto handler = new json_test_handler(json::stream_object("hello"));
+ server._routes.put(GET, "/test", handler);
+ server.do_accepts(0).get();
+
+ client.get();
+ server.stop().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_100_continue) {
+ return seastar::async([] {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ server.set_content_length_limit(11);
+ loopback_socket_impl lsi(lcf);
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+ future<> client = seastar::async([&lsi] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+
+ for (auto version : {sstring("1.0"), sstring("1.1")}) {
+ for (auto content : {sstring(""), sstring("xxxxxxxxxxx")}) {
+ for (auto expect : {sstring(""), sstring("Expect: 100-continue\r\n"), sstring("Expect: 100-cOnTInUE\r\n")}) {
+ auto content_len = content.empty() ? sstring("") : (sstring("Content-Length: ") + to_sstring(content.length()) + sstring("\r\n"));
+ sstring req = sstring("GET /test HTTP/") + version + sstring("\r\nHost: test\r\nConnection: Keep-Alive\r\n") + content_len + expect + sstring("\r\n");
+ output.write(req).get();
+ output.flush().get();
+ bool already_ok = false;
+ if (version == "1.1" && expect.length()) {
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
+ already_ok = content.empty() && std::string(resp.get(), resp.size()).find("200 OK") != std::string::npos;
+ }
+ if (!already_ok) {
+ //If the body is empty, the final response might have already been read
+ output.write(content).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+ }
+ }
+ }
+ }
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
+
+ input.close().get();
+ output.close().get();
+ });
+
+ auto handler = new json_test_handler(json::stream_object("hello"));
+ server._routes.put(GET, "/test", handler);
+ server.do_accepts(0).get();
+
+ client.get();
+ server.stop().get();
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_unparsable_request) {
+ // Test if a message that cannot be parsed as a http request is being replied with a 400 Bad Request response
+ return seastar::async([] {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ loopback_socket_impl lsi(lcf);
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+ future<> client = seastar::async([&lsi] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+
+ output.write(sstring("GET /test HTTP/1.1\r\nhello\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("400 Bad Request"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("Can't parse the request"), std::string::npos);
+
+ input.close().get();
+ output.close().get();
+ });
+
+ auto handler = new json_test_handler(json::stream_object("hello"));
+ server._routes.put(GET, "/test", handler);
+ server.do_accepts(0).get();
+
+ client.get();
+ server.stop().get();
+ });
+}
+
+SEASTAR_TEST_CASE(case_insensitive_header) {
+ std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
+ req->_headers["conTEnt-LengtH"] = "17";
+ BOOST_REQUIRE_EQUAL(req->get_header("content-length"), "17");
+ BOOST_REQUIRE_EQUAL(req->get_header("Content-Length"), "17");
+ BOOST_REQUIRE_EQUAL(req->get_header("cOnTeNT-lEnGTh"), "17");
+ return make_ready_future<>();
+}
+
+SEASTAR_THREAD_TEST_CASE(multiple_connections) {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+ socket_address addr{ipv4_addr()};
+
+ std::vector<connected_socket> socks;
+ // Make sure one shard has two connections pending.
+ for (unsigned i = 0; i <= smp::count; ++i) {
+ socks.push_back(loopback_socket_impl(lcf).connect(addr, addr).get0());
+ }
+
+ server.do_accepts(0).get();
+ server.stop().get();
+ lcf.destroy_all_shards().get();
+}
diff --git a/src/seastar/tests/unit/ipv6_test.cc b/src/seastar/tests/unit/ipv6_test.cc
new file mode 100644
index 000000000..d96401b22
--- /dev/null
+++ b/src/seastar/tests/unit/ipv6_test.cc
@@ -0,0 +1,105 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+static logger iplog("ipv6");
+
+static bool check_ipv6_support() {
+ if (!engine().net().supports_ipv6()) {
+ iplog.info("No IPV6 support detected. Skipping...");
+ return false;
+ }
+ return true;
+}
+
+SEASTAR_TEST_CASE(udp_packet_test) {
+ if (!check_ipv6_support()) {
+ return make_ready_future<>();
+ }
+
+ auto sc = make_udp_channel(ipv6_addr{"::1"});
+
+ BOOST_REQUIRE(sc.local_address().addr().is_ipv6());
+
+ auto cc = make_udp_channel(ipv6_addr{"::1"});
+
+ auto f1 = cc.send(sc.local_address(), "apa");
+
+ return f1.then([cc = std::move(cc), sc = std::move(sc)]() mutable {
+ auto src = cc.local_address();
+ cc.close();
+ auto f2 = sc.receive();
+
+ return f2.then([sc = std::move(sc), src](auto pkt) mutable {
+ auto a = sc.local_address();
+ sc.close();
+ BOOST_REQUIRE_EQUAL(src, pkt.get_src());
+ auto dst = pkt.get_dst();
+ // Don't always get a dst address.
+ if (dst != socket_address()) {
+ BOOST_REQUIRE_EQUAL(a, pkt.get_dst());
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(tcp_packet_test) {
+ if (!check_ipv6_support()) {
+ return make_ready_future<>();
+ }
+
+ return async([] {
+ auto sc = server_socket(engine().net().listen(ipv6_addr{"::1"}, {}));
+ auto la = sc.local_address();
+
+ BOOST_REQUIRE(la.addr().is_ipv6());
+
+ auto cc = connect(la).get0();
+ auto lc = std::move(sc.accept().get0().connection);
+
+ auto strm = cc.output();
+ strm.write("los lobos").get();
+ strm.flush().get();
+
+ auto in = lc.input();
+
+ using consumption_result_type = typename input_stream<char>::consumption_result_type;
+ using stop_consuming_type = typename consumption_result_type::stop_consuming_type;
+ using tmp_buf = stop_consuming_type::tmp_buf;
+
+ in.consume([](tmp_buf buf) {
+ return make_ready_future<consumption_result_type>(stop_consuming<char>({}));
+ }).get();
+
+ strm.close().get();
+ in.close().get();
+ sc.abort_accept();
+ });
+}
+
diff --git a/src/seastar/tests/unit/json_formatter_test.cc b/src/seastar/tests/unit/json_formatter_test.cc
new file mode 100644
index 000000000..213b93bbd
--- /dev/null
+++ b/src/seastar/tests/unit/json_formatter_test.cc
@@ -0,0 +1,52 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB.
+ */
+#include <vector>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/json/formatter.hh>
+
+using namespace seastar;
+using namespace json;
+
+SEASTAR_TEST_CASE(test_simple_values) {
+ BOOST_CHECK_EQUAL("3", formatter::to_json(3));
+ BOOST_CHECK_EQUAL("3", formatter::to_json(3.0));
+ BOOST_CHECK_EQUAL("3.5", formatter::to_json(3.5));
+ BOOST_CHECK_EQUAL("true", formatter::to_json(true));
+ BOOST_CHECK_EQUAL("false", formatter::to_json(false));
+ BOOST_CHECK_EQUAL("\"apa\"", formatter::to_json("apa"));
+
+ return make_ready_future();
+}
+
+SEASTAR_TEST_CASE(test_collections) {
+ BOOST_CHECK_EQUAL("{1:2,3:4}", formatter::to_json(std::map<int,int>({{1,2},{3,4}})));
+ BOOST_CHECK_EQUAL("[1,2,3,4]", formatter::to_json(std::vector<int>({1,2,3,4})));
+ BOOST_CHECK_EQUAL("[{1:2},{3:4}]", formatter::to_json(std::vector<std::pair<int,int>>({{1,2},{3,4}})));
+ BOOST_CHECK_EQUAL("[{1:2},{3:4}]", formatter::to_json(std::vector<std::map<int,int>>({{{1,2}},{{3,4}}})));
+ BOOST_CHECK_EQUAL("[[1,2],[3,4]]", formatter::to_json(std::vector<std::vector<int>>({{1,2},{3,4}})));
+
+ return make_ready_future();
+}
diff --git a/src/seastar/tests/unit/locking_test.cc b/src/seastar/tests/unit/locking_test.cc
new file mode 100644
index 000000000..55a1251b8
--- /dev/null
+++ b/src/seastar/tests/unit/locking_test.cc
@@ -0,0 +1,223 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB.
+ */
+
+#include <chrono>
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/rwlock.hh>
+#include <seastar/core/shared_mutex.hh>
+#include <seastar/util/alloc_failure_injector.hh>
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_THREAD_TEST_CASE(test_rwlock) {
+ rwlock l;
+
+ l.for_write().lock().get();
+ BOOST_REQUIRE(!l.try_write_lock());
+ BOOST_REQUIRE(!l.try_read_lock());
+ l.for_write().unlock();
+
+ l.for_read().lock().get();
+ BOOST_REQUIRE(!l.try_write_lock());
+ BOOST_REQUIRE(l.try_read_lock());
+ l.for_read().lock().get();
+ l.for_read().unlock();
+ l.for_read().unlock();
+ l.for_read().unlock();
+
+ BOOST_REQUIRE(l.try_write_lock());
+ l.for_write().unlock();
+}
+
+SEASTAR_TEST_CASE(test_with_lock_mutable) {
+ return do_with(rwlock(), [](rwlock& l) {
+ return with_lock(l.for_read(), [p = std::make_unique<int>(42)] () mutable {});
+ });
+}
+
+SEASTAR_TEST_CASE(test_rwlock_exclusive) {
+ return do_with(rwlock(), unsigned(0), [] (rwlock& l, unsigned& counter) {
+ return parallel_for_each(boost::irange(0, 10), [&l, &counter] (int idx) {
+ return with_lock(l.for_write(), [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(1ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_rwlock_shared) {
+ return do_with(rwlock(), unsigned(0), unsigned(0), [] (rwlock& l, unsigned& counter, unsigned& max) {
+ return parallel_for_each(boost::irange(0, 10), [&l, &counter, &max] (int idx) {
+ return with_lock(l.for_read(), [&counter, &max] {
+ ++counter;
+ max = std::max(max, counter);
+ return sleep(1ms).then([&counter] {
+ --counter;
+ });
+ });
+ }).finally([&counter, &max] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ BOOST_REQUIRE_NE(max, 0u);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_rwlock_failed_func) {
+ rwlock l;
+
+ // verify that the rwlock is unlocked when func fails
+ future<> fut = with_lock(l.for_read(), [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ fut = with_lock(l.for_write(), [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ BOOST_REQUIRE(l.try_write_lock());
+ l.for_write().unlock();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_failed_with_lock) {
+ struct test_lock {
+ future<> lock() noexcept {
+ return make_exception_future<>(std::runtime_error("injected"));
+ }
+ void unlock() noexcept {
+ BOOST_REQUIRE(false);
+ }
+ };
+
+ test_lock l;
+
+ // if l.lock() fails neither the function nor l.unlock()
+ // should be called.
+ BOOST_REQUIRE_THROW(with_lock(l, [] {
+ BOOST_REQUIRE(false);
+ }).get(), std::runtime_error);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_mutex) {
+ shared_mutex sm;
+
+ sm.lock().get();
+ BOOST_REQUIRE(!sm.try_lock());
+ BOOST_REQUIRE(!sm.try_lock_shared());
+ sm.unlock();
+
+ sm.lock_shared().get();
+ BOOST_REQUIRE(!sm.try_lock());
+ BOOST_REQUIRE(sm.try_lock_shared());
+ sm.lock_shared().get();
+ sm.unlock_shared();
+ sm.unlock_shared();
+ sm.unlock_shared();
+
+ BOOST_REQUIRE(sm.try_lock());
+ sm.unlock();
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_exclusive) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ return parallel_for_each(boost::irange(0, 10), [&sm, &counter] (int idx) {
+ return with_lock(sm, [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(1ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_shared) {
+ return do_with(shared_mutex(), unsigned(0), unsigned(0), [] (shared_mutex& sm, unsigned& counter, unsigned& max) {
+ return parallel_for_each(boost::irange(0, 10), [&sm, &counter, &max] (int idx) {
+ return with_shared(sm, [&counter, &max] {
+ ++counter;
+ max = std::max(max, counter);
+ return sleep(1ms).then([&counter] {
+ --counter;
+ });
+ });
+ }).finally([&counter, &max] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ BOOST_REQUIRE_NE(max, 0u);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_mutex_failed_func) {
+ shared_mutex sm;
+
+ // verify that the shared_mutex is unlocked when func fails
+ future<> fut = with_shared(sm, [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ fut = with_lock(sm, [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ BOOST_REQUIRE(sm.try_lock());
+ sm.unlock();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_mutex_failed_lock) {
+#ifdef SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION
+ shared_mutex sm;
+
+ // if l.lock() fails neither the function nor l.unlock()
+ // should be called.
+ sm.lock().get();
+ seastar::memory::local_failure_injector().fail_after(0);
+ BOOST_REQUIRE_THROW(with_shared(sm, [] {
+ BOOST_REQUIRE(false);
+ }).get(), std::bad_alloc);
+
+ seastar::memory::local_failure_injector().fail_after(0);
+ BOOST_REQUIRE_THROW(with_lock(sm, [] {
+ BOOST_REQUIRE(false);
+ }).get(), std::bad_alloc);
+ sm.unlock();
+
+ seastar::memory::local_failure_injector().cancel();
+#endif // SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION
+}
diff --git a/src/seastar/tests/unit/log_buf_test.cc b/src/seastar/tests/unit/log_buf_test.cc
new file mode 100644
index 000000000..fbb11a7b1
--- /dev/null
+++ b/src/seastar/tests/unit/log_buf_test.cc
@@ -0,0 +1,55 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2020 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(log_buf_realloc) {
+ std::array<char, 128> external_buf;
+
+ const auto external_buf_ptr = reinterpret_cast<uintptr_t>(external_buf.data());
+
+ internal::log_buf b(external_buf.data(), external_buf.size());
+
+ BOOST_REQUIRE_EQUAL(reinterpret_cast<uintptr_t>(b.data()), external_buf_ptr);
+
+ auto it = b.back_insert_begin();
+
+ BOOST_REQUIRE_EQUAL(reinterpret_cast<uintptr_t>(&*it), external_buf_ptr);
+
+ for (auto i = 0; i < 128; ++i) {
+ *it++ = 'a';
+ }
+
+ *it = 'a'; // should trigger realloc
+
+ BOOST_REQUIRE_NE(reinterpret_cast<uintptr_t>(b.data()), reinterpret_cast<uintptr_t>(external_buf.data()));
+ BOOST_REQUIRE_NE(reinterpret_cast<uintptr_t>(&*it), reinterpret_cast<uintptr_t>(external_buf.data() + 128));
+
+ const char* p = b.data();
+ for (auto i = 0; i < 129; ++i) {
+ BOOST_REQUIRE_EQUAL(p[i], 'a');
+ }
+
+ return make_ready_future<>();
+}
diff --git a/src/seastar/tests/unit/loopback_socket.hh b/src/seastar/tests/unit/loopback_socket.hh
new file mode 100644
index 000000000..c21653031
--- /dev/null
+++ b/src/seastar/tests/unit/loopback_socket.hh
@@ -0,0 +1,278 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#pragma once
+
+#include <system_error>
+#include <seastar/core/iostream.hh>
+#include <seastar/core/circular_buffer.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/queue.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/net/stack.hh>
+#include <seastar/core/sharded.hh>
+
+namespace seastar {
+
+struct loopback_error_injector {
+ virtual ~loopback_error_injector() {};
+ virtual bool server_rcv_error() { return false; }
+ virtual bool server_snd_error() { return false; }
+ virtual bool client_rcv_error() { return false; }
+ virtual bool client_snd_error() { return false; }
+};
+
+class loopback_buffer {
+public:
+ enum class type : uint8_t {
+ CLIENT_TX,
+ SERVER_TX
+ };
+private:
+ bool _aborted = false;
+ queue<temporary_buffer<char>> _q{1};
+ loopback_error_injector* _error_injector;
+ type _type;
+public:
+ loopback_buffer(loopback_error_injector* error_injection, type t) : _error_injector(error_injection), _type(t) {}
+ future<> push(temporary_buffer<char>&& b) {
+ if (_aborted) {
+ return make_exception_future<>(std::system_error(EPIPE, std::system_category()));
+ }
+ bool error = false;
+ if (_error_injector) {
+ error = _type == type::CLIENT_TX ? _error_injector->client_snd_error() : _error_injector->server_snd_error();
+ }
+ if (error) {
+ shutdown();
+ return make_exception_future<>(std::runtime_error("test injected error on send"));
+ }
+ return _q.push_eventually(std::move(b));
+ }
+ future<temporary_buffer<char>> pop() {
+ if (_aborted) {
+ return make_exception_future<temporary_buffer<char>>(std::system_error(EPIPE, std::system_category()));
+ }
+ bool error = false;
+ if (_error_injector) {
+ error = _type == type::CLIENT_TX ? _error_injector->client_rcv_error() : _error_injector->server_rcv_error();
+ }
+ if (error) {
+ shutdown();
+ return make_exception_future<temporary_buffer<char>>(std::runtime_error("test injected error on receive"));
+ }
+ return _q.pop_eventually();
+ }
+ void shutdown() {
+ _aborted = true;
+ _q.abort(std::make_exception_ptr(std::system_error(EPIPE, std::system_category())));
+ }
+};
+
+class loopback_data_sink_impl : public data_sink_impl {
+ foreign_ptr<lw_shared_ptr<loopback_buffer>>& _buffer;
+public:
+ explicit loopback_data_sink_impl(foreign_ptr<lw_shared_ptr<loopback_buffer>>& buffer)
+ : _buffer(buffer) {
+ }
+ future<> put(net::packet data) override {
+ return do_with(data.release(), [this] (std::vector<temporary_buffer<char>>& bufs) {
+ return do_for_each(bufs, [this] (temporary_buffer<char>& buf) {
+ return smp::submit_to(_buffer.get_owner_shard(), [this, b = buf.get(), s = buf.size()] {
+ return _buffer->push(temporary_buffer<char>(b, s));
+ });
+ });
+ });
+ }
+ future<> close() override {
+ return smp::submit_to(_buffer.get_owner_shard(), [this] {
+ return _buffer->push({}).handle_exception_type([] (std::system_error& err) {
+ if (err.code().value() != EPIPE) {
+ throw err;
+ }
+ });
+ });
+ }
+};
+
+class loopback_data_source_impl : public data_source_impl {
+ bool _eof = false;
+ lw_shared_ptr<loopback_buffer> _buffer;
+public:
+ explicit loopback_data_source_impl(lw_shared_ptr<loopback_buffer> buffer)
+ : _buffer(std::move(buffer)) {
+ }
+ future<temporary_buffer<char>> get() override {
+ return _buffer->pop().then_wrapped([this] (future<temporary_buffer<char>>&& b) {
+ _eof = b.failed();
+ if (!_eof) {
+ // future::get0() is destructive, so we have to play these games
+ // FIXME: make future::get0() non-destructive
+ auto&& tmp = b.get0();
+ _eof = tmp.empty();
+ b = make_ready_future<temporary_buffer<char>>(std::move(tmp));
+ }
+ return std::move(b);
+ });
+ }
+ future<> close() override {
+ if (!_eof) {
+ _buffer->shutdown();
+ }
+ return make_ready_future<>();
+ }
+};
+
+
+class loopback_connected_socket_impl : public net::connected_socket_impl {
+ foreign_ptr<lw_shared_ptr<loopback_buffer>> _tx;
+ lw_shared_ptr<loopback_buffer> _rx;
+public:
+ loopback_connected_socket_impl(foreign_ptr<lw_shared_ptr<loopback_buffer>> tx, lw_shared_ptr<loopback_buffer> rx)
+ : _tx(std::move(tx)), _rx(std::move(rx)) {
+ }
+ data_source source() override {
+ return data_source(std::make_unique<loopback_data_source_impl>(_rx));
+ }
+ data_sink sink() override {
+ return data_sink(std::make_unique<loopback_data_sink_impl>(_tx));
+ }
+ void shutdown_input() override {
+ _rx->shutdown();
+ }
+ void shutdown_output() override {
+ (void)smp::submit_to(_tx.get_owner_shard(), [this] {
+ // FIXME: who holds to _tx?
+ _tx->shutdown();
+ });
+ }
+ void set_nodelay(bool nodelay) override {
+ }
+ bool get_nodelay() const override {
+ return true;
+ }
+ void set_keepalive(bool keepalive) override {}
+ bool get_keepalive() const override {
+ return false;
+ }
+ void set_keepalive_parameters(const net::keepalive_params&) override {}
+ net::keepalive_params get_keepalive_parameters() const override {
+ return net::tcp_keepalive_params {std::chrono::seconds(0), std::chrono::seconds(0), 0};
+ }
+ void set_sockopt(int level, int optname, const void* data, size_t len) override {
+ throw std::runtime_error("Setting custom socket options is not supported for loopback");
+ }
+ int get_sockopt(int level, int optname, void* data, size_t len) const override {
+ throw std::runtime_error("Getting custom socket options is not supported for loopback");
+ }
+};
+
+class loopback_server_socket_impl : public net::server_socket_impl {
+ lw_shared_ptr<queue<connected_socket>> _pending;
+public:
+ explicit loopback_server_socket_impl(lw_shared_ptr<queue<connected_socket>> q)
+ : _pending(std::move(q)) {
+ }
+ future<accept_result> accept() override {
+ return _pending->pop_eventually().then([] (connected_socket&& cs) {
+ return make_ready_future<accept_result>(accept_result{std::move(cs), socket_address()});
+ });
+ }
+ void abort_accept() override {
+ _pending->abort(std::make_exception_ptr(std::system_error(ECONNABORTED, std::system_category())));
+ }
+ socket_address local_address() const override {
+ // CMH dummy
+ return {};
+ }
+};
+
+
+class loopback_connection_factory {
+ unsigned _shard = 0;
+ std::vector<lw_shared_ptr<queue<connected_socket>>> _pending;
+public:
+ loopback_connection_factory() {
+ _pending.resize(smp::count);
+ }
+ server_socket get_server_socket() {
+ if (!_pending[this_shard_id()]) {
+ _pending[this_shard_id()] = make_lw_shared<queue<connected_socket>>(10);
+ }
+ return server_socket(std::make_unique<loopback_server_socket_impl>(_pending[this_shard_id()]));
+ }
+ future<> make_new_server_connection(foreign_ptr<lw_shared_ptr<loopback_buffer>> b1, lw_shared_ptr<loopback_buffer> b2) {
+ if (!_pending[this_shard_id()]) {
+ _pending[this_shard_id()] = make_lw_shared<queue<connected_socket>>(10);
+ }
+ return _pending[this_shard_id()]->push_eventually(connected_socket(std::make_unique<loopback_connected_socket_impl>(std::move(b1), b2)));
+ }
+ connected_socket make_new_client_connection(lw_shared_ptr<loopback_buffer> b1, foreign_ptr<lw_shared_ptr<loopback_buffer>> b2) {
+ return connected_socket(std::make_unique<loopback_connected_socket_impl>(std::move(b2), b1));
+ }
+ unsigned next_shard() {
+ return _shard++ % smp::count;
+ }
+ void destroy_shard(unsigned shard) {
+ _pending[shard] = nullptr;
+ }
+ future<> destroy_all_shards() {
+ return smp::invoke_on_all([this] () {
+ destroy_shard(this_shard_id());
+ });
+ }
+};
+
+class loopback_socket_impl : public net::socket_impl {
+ loopback_connection_factory& _factory;
+ loopback_error_injector* _error_injector;
+ lw_shared_ptr<loopback_buffer> _b1;
+ foreign_ptr<lw_shared_ptr<loopback_buffer>> _b2;
+public:
+ loopback_socket_impl(loopback_connection_factory& factory, loopback_error_injector* error_injector = nullptr)
+ : _factory(factory), _error_injector(error_injector)
+ { }
+ future<connected_socket> connect(socket_address sa, socket_address local, seastar::transport proto = seastar::transport::TCP) override {
+ auto shard = _factory.next_shard();
+ _b1 = make_lw_shared<loopback_buffer>(_error_injector, loopback_buffer::type::SERVER_TX);
+ return smp::submit_to(shard, [this, b1 = make_foreign(_b1)] () mutable {
+ auto b2 = make_lw_shared<loopback_buffer>(_error_injector, loopback_buffer::type::CLIENT_TX);
+ _b2 = make_foreign(b2);
+ return _factory.make_new_server_connection(std::move(b1), b2).then([b2] {
+ return make_foreign(b2);
+ });
+ }).then([this] (foreign_ptr<lw_shared_ptr<loopback_buffer>> b2) {
+ return _factory.make_new_client_connection(_b1, std::move(b2));
+ });
+ }
+ virtual void set_reuseaddr(bool reuseaddr) override {}
+ virtual bool get_reuseaddr() const override { return false; };
+
+ void shutdown() override {
+ _b1->shutdown();
+ (void)smp::submit_to(_b2.get_owner_shard(), [b2 = std::move(_b2)] {
+ b2->shutdown();
+ });
+ }
+};
+
+}
diff --git a/src/seastar/tests/unit/lowres_clock_test.cc b/src/seastar/tests/unit/lowres_clock_test.cc
new file mode 100644
index 000000000..1beb230b6
--- /dev/null
+++ b/src/seastar/tests/unit/lowres_clock_test.cc
@@ -0,0 +1,118 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/core/lowres_clock.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/loop.hh>
+
+#include <ctime>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+
+using namespace seastar;
+
+//
+// Sanity check the accuracy of the steady low-resolution clock.
+//
+SEASTAR_TEST_CASE(steady_clock_sanity) {
+ return do_with(lowres_clock::now(), [](auto &&t1) {
+ static constexpr auto sleep_duration = std::chrono::milliseconds(100);
+
+ return ::seastar::sleep(sleep_duration).then([&t1] {
+ auto const elapsed = lowres_clock::now() - t1;
+ auto const minimum_elapsed = 0.9 * sleep_duration;
+
+ BOOST_REQUIRE(elapsed >= minimum_elapsed);
+
+ return make_ready_future<>();
+ });
+ });
+}
+
+//
+// At the very least, we can verify that the low-resolution system clock is within a second of the
+// high-resolution system clock.
+//
+SEASTAR_TEST_CASE(system_clock_sanity) {
+ static const auto check_matching = [] {
+ auto const system_time = std::chrono::system_clock::now();
+ auto const lowres_time = lowres_system_clock::now();
+
+ auto const t1 = std::chrono::system_clock::to_time_t(system_time);
+ auto const t2 = lowres_system_clock::to_time_t(lowres_time);
+
+ std::tm *lt1 = std::localtime(&t1);
+ std::tm *lt2 = std::localtime(&t2);
+
+ return (lt1->tm_isdst == lt2->tm_isdst) &&
+ (lt1->tm_year == lt2->tm_year) &&
+ (lt1->tm_mon == lt2->tm_mon) &&
+ (lt1->tm_yday == lt2->tm_yday) &&
+ (lt1->tm_mday == lt2->tm_mday) &&
+ (lt1->tm_wday == lt2->tm_wday) &&
+ (lt1->tm_hour == lt2->tm_hour) &&
+ (lt1->tm_min == lt2->tm_min) &&
+ (lt1->tm_sec == lt2->tm_sec);
+ };
+
+ //
+ // Check two out of three samples in order to account for the possibility that the high-resolution clock backing
+ // the low-resoltuion clock was captured in the range of the 990th to 999th millisecond of the second. This would
+ // make the low-resolution clock and the high-resolution clock disagree on the current second.
+ //
+
+ return do_with(0ul, 0ul, [](std::size_t& index, std::size_t& success_count) {
+ return repeat([&index, &success_count] {
+ if (index >= 3) {
+ BOOST_REQUIRE_GE(success_count, 2u);
+ return make_ready_future<stop_iteration>(stop_iteration::yes);
+ }
+
+ return ::seastar::sleep(std::chrono::milliseconds(10)).then([&index, &success_count] {
+ if (check_matching()) {
+ ++success_count;
+ }
+
+ ++index;
+ return stop_iteration::no;
+ });
+ });
+ });
+}
+
+//
+// Verify that the low-resolution clock updates its reported time point over time.
+//
+SEASTAR_TEST_CASE(system_clock_dynamic) {
+ return do_with(lowres_system_clock::now(), [](auto &&t1) {
+ return seastar::sleep(std::chrono::milliseconds(100)).then([&t1] {
+ auto const t2 = lowres_system_clock::now();
+ BOOST_REQUIRE_NE(t1.time_since_epoch().count(), t2.time_since_epoch().count());
+
+ return make_ready_future<>();
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/metrics_test.cc b/src/seastar/tests/unit/metrics_test.cc
new file mode 100644
index 000000000..b6906010c
--- /dev/null
+++ b/src/seastar/tests/unit/metrics_test.cc
@@ -0,0 +1,170 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2019 ScyllaDB.
+ */
+
+#include <seastar/core/metrics_registration.hh>
+#include <seastar/core/metrics.hh>
+#include <seastar/core/metrics_api.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/scheduling.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/io_queue.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <boost/range/irange.hpp>
+
+SEASTAR_TEST_CASE(test_add_group) {
+ using namespace seastar::metrics;
+ // Just has to compile:
+ metric_groups()
+ .add_group("g1", {})
+ .add_group("g2", std::vector<metric_definition>());
+ return seastar::make_ready_future();
+}
+
+/**
+ * This function return the different name label values
+ * for the named metric.
+ *
+ * @note: If the statistic or label doesn't exist, the test
+ * that calls this function will fail.
+ *
+ * @param metric_name - the metric name
+ * @param label_name - the label name
+ * @return a set containing all the different values
+ * of the label.
+ */
+static std::set<seastar::sstring> get_label_values(seastar::sstring metric_name, seastar::sstring label_name) {
+ namespace smi = seastar::metrics::impl;
+ auto all_metrics = smi::get_values();
+ const auto& all_metadata = *all_metrics->metadata;
+ const auto qp_group = find_if(cbegin(all_metadata), cend(all_metadata),
+ [&metric_name] (const auto& x) { return x.mf.name == metric_name; });
+ BOOST_REQUIRE(qp_group != cend(all_metadata));
+ std::set<seastar::sstring> labels;
+ for (const auto& metric : qp_group->metrics) {
+ const auto found = metric.id.labels().find(label_name);
+ BOOST_REQUIRE(found != metric.id.labels().cend());
+ labels.insert(found->second);
+ }
+ return labels;
+}
+
+SEASTAR_THREAD_TEST_CASE(test_renaming_scheuling_groups) {
+ // this seams a little bit out of place but the
+ // renaming functionality is primarily for statistics
+ // otherwise those classes could have just been reused
+ // without renaming them.
+ using namespace seastar;
+
+ static const char* name1 = "A";
+ static const char* name2 = "B";
+ scheduling_group sg = create_scheduling_group("hello", 111).get0();
+ boost::integer_range<int> rng(0, 1000);
+ // repeatedly change the group name back and forth in
+ // decresing time intervals to see if it generate double
+ //registration statistics errors.
+ for (auto&& i : rng) {
+ const char* name = i%2 ? name1 : name2;
+ const char* prev_name = i%2 ? name2 : name1;
+ sleep(std::chrono::microseconds(100000/(i+1))).get();
+ rename_scheduling_group(sg, name).get();
+ std::set<sstring> label_vals = get_label_values(sstring("scheduler_shares"), sstring("group"));
+ // validate that the name that we *renamed to* is in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(name)) != label_vals.end());
+ // validate that the name that we *renamed from* is *not* in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(prev_name)) == label_vals.end());
+ }
+
+ smp::invoke_on_all([sg] () {
+ return do_with(std::uniform_int_distribution<int>(), boost::irange<int>(0, 1000),
+ [sg] (std::uniform_int_distribution<int>& dist, boost::integer_range<int>& rng) {
+ // flip a fair coin and rename to one of two options and rename to that
+ // scheduling group name, do it 1000 in parallel on all shards so there
+ // is a chance of collision.
+ return do_for_each(rng, [sg, &dist] (auto i) {
+ bool odd = dist(seastar::testing::local_random_engine)%2;
+ return rename_scheduling_group(sg, odd ? name1 : name2);
+ });
+ });
+ }).get();
+
+ std::set<sstring> label_vals = get_label_values(sstring("scheduler_shares"), sstring("group"));
+ // validate that only one of the names is eventually in the metrics
+ bool name1_found = label_vals.find(sstring(name1)) != label_vals.end();
+ bool name2_found = label_vals.find(sstring(name2)) != label_vals.end();
+ BOOST_REQUIRE((name1_found && !name2_found) || (name2_found && !name1_found));
+}
+
+SEASTAR_THREAD_TEST_CASE(test_renaming_io_priority_classes) {
+ // this seams a little bit out of place but the
+ // renaming functionality is primarily for statistics
+ // otherwise those classes could have just been reused
+ // without renaming them.
+ using namespace seastar;
+ static const char* name1 = "A";
+ static const char* name2 = "B";
+ seastar::io_priority_class pc = engine().register_one_priority_class("hello",100);
+ smp::invoke_on_all([pc] () {
+ // this is a trick to get all of the queues actually register their
+ // stats.
+ return engine().update_shares_for_class(pc,101);
+ }).get();
+
+ boost::integer_range<int> rng(0, 1000);
+ // repeatedly change the group name back and forth in
+ // decresing time intervals to see if it generate double
+ //registration statistics errors.
+ for (auto&& i : rng) {
+ const char* name = i%2 ? name1 : name2;
+ const char* prev_name = i%2 ? name2 : name1;
+ sleep(std::chrono::microseconds(100000/(i+1))).get();
+ rename_priority_class(pc, name).get();
+ std::set<sstring> label_vals = get_label_values(sstring("io_queue_shares"), sstring("class"));
+ // validate that the name that we *renamed to* is in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(name)) != label_vals.end());
+ // validate that the name that we *renamed from* is *not* in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(prev_name)) == label_vals.end());
+ }
+
+ smp::invoke_on_all([pc] () {
+ return do_with(std::uniform_int_distribution<int>(), boost::irange<int>(0, 1000),
+ [pc] (std::uniform_int_distribution<int>& dist, boost::integer_range<int>& rng) {
+ // flip a fair coin and rename to one of two options and rename to that
+ // scheduling group name, do it 1000 in parallel on all shards so there
+ // is a chance of collision.
+ return do_for_each(rng, [pc, &dist] (auto i) {
+ bool odd = dist(seastar::testing::local_random_engine)%2;
+ return rename_priority_class(pc, odd ? name1 : name2);
+ });
+ });
+ }).get();
+
+ std::set<sstring> label_vals = get_label_values(sstring("io_queue_shares"), sstring("class"));
+ // validate that only one of the names is eventually in the metrics
+ bool name1_found = label_vals.find(sstring(name1)) != label_vals.end();
+ bool name2_found = label_vals.find(sstring(name2)) != label_vals.end();
+ BOOST_REQUIRE((name1_found && !name2_found) || (name2_found && !name1_found));
+}
diff --git a/src/seastar/tests/unit/mkcert.gmk b/src/seastar/tests/unit/mkcert.gmk
new file mode 100644
index 000000000..ecf2d5dfe
--- /dev/null
+++ b/src/seastar/tests/unit/mkcert.gmk
@@ -0,0 +1,94 @@
+server = $(shell hostname)
+domain = $(shell dnsdomainname)
+name = $(server)
+
+country = SE
+state = Stockholm
+locality= $(state)
+org = $(domain)
+unit = $(domain)
+mail = mx
+common = $(server).$(domain)
+email = postmaster@$(domain)
+ckey = ca$(key).pem
+
+pubkey = $(name).pub
+prvkey = $(name).key
+width = 4096
+
+csr = $(name).csr
+crt = $(name).crt
+
+root = ca$(name).pem
+rootkey = ca$(name).key
+
+config = $(name).cfg
+days = 3650
+
+alg = RSA
+alg_opt = -pkeyopt rsa_keygen_bits:$(width)
+
+hosts =
+
+all : $(crt)
+
+clean :
+ @rm -f $(crt) $(csr) $(pubkey) $(prvkey)
+
+%.key :
+ @echo generating $@
+ openssl genpkey -out $@ -algorithm $(alg) $(alg_opt)
+
+%.pub : %.key
+ @echo generating $@
+ openssl pkey -in $< -out $@
+
+$(config) : $(MAKEFILE_LIST)
+ @echo generating $@
+ @( \
+ echo [ req ] ; \
+ echo default_bits = $(width) ; \
+ echo default_keyfile = $(prvkey) ; \
+ echo default_md = sha256 ; \
+ echo distinguished_name = req_distinguished_name ; \
+ echo req_extensions = v3_req ; \
+ echo prompt = no ; \
+ echo [ req_distinguished_name ] ; \
+ echo C = $(country) ; \
+ echo ST = $(state) ; \
+ echo L = $(locality) ; \
+ echo O = $(org) ; \
+ echo OU = $(unit) ; \
+ echo CN= $(common) ; \
+ echo emailAddress = $(email) ; \
+ echo [v3_ca] ; \
+ echo subjectKeyIdentifier=hash ; \
+ echo authorityKeyIdentifier=keyid:always,issuer:always ; \
+ echo basicConstraints = CA:true ; \
+ echo [v3_req] ; \
+ echo "# Extensions to add to a certificate request" ; \
+ echo basicConstraints = CA:FALSE ; \
+ echo keyUsage = nonRepudiation, digitalSignature, keyEncipherment ; \
+ $(if $(hosts), echo subjectAltName = @alt_names ;) \
+ $(if $(hosts), echo [alt_names] ;) \
+ $(if $(hosts), index=1; for host in $(hosts); \
+ do echo DNS.$$index = $$host.$(domain); \
+ index=$$(($$index + 1));done ;) \
+ ) > $@
+
+%.csr : %.key $(config)
+ @echo generating $@
+ openssl req -new -key $< -out $@ -config $(config)
+
+%.crt : %.csr $(root) $(rootkey)
+ @echo generating $@
+ openssl x509 -req -in $< -CA $(root) -CAkey $(rootkey) -CAcreateserial \
+ -out $@ -days $(days)
+
+%.pem : %.key $(config)
+ @echo generating $@
+ openssl req -x509 -new -nodes -key $< -days $(days) -config $(config) \
+ -out $@
+
+.PRECIOUS : %.pem %.key %.pub %.crt %.csr
+
diff --git a/src/seastar/tests/unit/mock_file.hh b/src/seastar/tests/unit/mock_file.hh
new file mode 100644
index 000000000..7b7f8eeed
--- /dev/null
+++ b/src/seastar/tests/unit/mock_file.hh
@@ -0,0 +1,113 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+#pragma once
+
+#include <boost/range/numeric.hpp>
+
+#include <seastar/testing/seastar_test.hh>
+#include <seastar/core/file.hh>
+
+namespace seastar {
+
+class mock_read_only_file final : public file_impl {
+ bool _closed = false;
+ uint64_t _total_file_size;
+ size_t _allowed_read_requests = 0;
+ std::function<void(size_t)> _verify_length;
+private:
+ size_t verify_read(uint64_t position, size_t length) {
+ BOOST_CHECK(!_closed);
+ BOOST_CHECK_LE(position, _total_file_size);
+ BOOST_CHECK_LE(position + length, _total_file_size);
+ if (position + length != _total_file_size) {
+ _verify_length(length);
+ }
+ BOOST_CHECK(_allowed_read_requests);
+ assert(_allowed_read_requests);
+ _allowed_read_requests--;
+ return length;
+ }
+public:
+ explicit mock_read_only_file(uint64_t file_size) noexcept
+ : _total_file_size(file_size)
+ , _verify_length([] (auto) { })
+ { }
+
+ void set_read_size_verifier(std::function<void(size_t)> fn) {
+ _verify_length = fn;
+ }
+ void set_expected_read_size(size_t expected) {
+ _verify_length = [expected] (auto length) {
+ BOOST_CHECK_EQUAL(length, expected);
+ };
+ }
+ void set_allowed_read_requests(size_t requests) {
+ _allowed_read_requests = requests;
+ }
+
+ virtual future<size_t> write_dma(uint64_t, const void*, size_t, const io_priority_class&) noexcept override {
+ return make_exception_future<size_t>(std::bad_function_call());
+ }
+ virtual future<size_t> write_dma(uint64_t, std::vector<iovec>, const io_priority_class&) noexcept override {
+ return make_exception_future<size_t>(std::bad_function_call());
+ }
+ virtual future<size_t> read_dma(uint64_t pos, void*, size_t len, const io_priority_class&) noexcept override {
+ return make_ready_future<size_t>(verify_read(pos, len));
+ }
+ virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class&) noexcept override {
+ auto length = boost::accumulate(iov | boost::adaptors::transformed([] (auto&& iov) { return iov.iov_len; }),
+ size_t(0), std::plus<size_t>());
+ return make_ready_future<size_t>(verify_read(pos, length));
+ }
+ virtual future<> flush() noexcept override {
+ return make_ready_future<>();
+ }
+ virtual future<struct stat> stat() noexcept override {
+ return make_exception_future<struct stat>(std::bad_function_call());
+ }
+ virtual future<> truncate(uint64_t) noexcept override {
+ return make_exception_future<>(std::bad_function_call());
+ }
+ virtual future<> discard(uint64_t offset, uint64_t length) noexcept override {
+ return make_exception_future<>(std::bad_function_call());
+ }
+ virtual future<> allocate(uint64_t position, uint64_t length) noexcept override {
+ return make_exception_future<>(std::bad_function_call());
+ }
+ virtual future<uint64_t> size() noexcept override {
+ return make_ready_future<uint64_t>(_total_file_size);
+ }
+ virtual future<> close() noexcept override {
+ BOOST_CHECK(!_closed);
+ _closed = true;
+ return make_ready_future<>();
+ }
+ virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)>) override {
+ throw std::bad_function_call();
+ }
+ virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class&) noexcept override {
+ auto length = verify_read(offset, range_size);
+ return make_ready_future<temporary_buffer<uint8_t>>(temporary_buffer<uint8_t>(length));
+ }
+};
+
+}
diff --git a/src/seastar/tests/unit/net_config_test.cc b/src/seastar/tests/unit/net_config_test.cc
new file mode 100644
index 000000000..ec26135fd
--- /dev/null
+++ b/src/seastar/tests/unit/net_config_test.cc
@@ -0,0 +1,127 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2017 Marek Waszkiewicz ( marek.waszkiewicz77@gmail.com )
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <seastar/net/config.hh>
+#include <boost/test/included/unit_test.hpp>
+#include <exception>
+#include <sstream>
+
+using namespace seastar::net;
+
+BOOST_AUTO_TEST_CASE(test_valid_config_with_pci_address) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0 } , eth1: {pci-address: 0000:06:00.1, dhcp: true } }";
+ auto device_configs = parse_config(ss);
+
+ // eth0 tests
+ BOOST_REQUIRE(device_configs.find("eth0") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").hw_cfg.pci_address, "0000:06:00.0");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.dhcp, false);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.ip, "192.168.100.10");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.gateway, "192.168.100.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.netmask, "255.255.255.0");
+
+ // eth1 tests
+ BOOST_REQUIRE(device_configs.find("eth1") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").hw_cfg.pci_address, "0000:06:00.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.dhcp, true);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.ip, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.gateway, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.netmask, "");
+}
+
+BOOST_AUTO_TEST_CASE(test_valid_config_with_port_index) {
+ std::stringstream ss;
+ ss << "{eth0: {port-index: 0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0 } , eth1: {port-index: 1, dhcp: true } }";
+ auto device_configs = parse_config(ss);
+
+ // eth0 tests
+ BOOST_REQUIRE(device_configs.find("eth0") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(*device_configs.at("eth0").hw_cfg.port_index, 0u);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.dhcp, false);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.ip, "192.168.100.10");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.gateway, "192.168.100.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.netmask, "255.255.255.0");
+
+ // eth1 tests
+ BOOST_REQUIRE(device_configs.find("eth1") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(*device_configs.at("eth1").hw_cfg.port_index, 1u);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.dhcp, true);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.ip, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.gateway, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.netmask, "");
+}
+
+BOOST_AUTO_TEST_CASE(test_valid_config_single_device) {
+ std::stringstream ss;
+ ss << "eth0: {pci-address: 0000:06:00.0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0 }";
+ auto device_configs = parse_config(ss);
+
+ // eth0 tests
+ BOOST_REQUIRE(device_configs.find("eth0") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").hw_cfg.pci_address, "0000:06:00.0");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.dhcp, false);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.ip, "192.168.100.10");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.gateway, "192.168.100.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.netmask, "255.255.255.0");
+}
+
+BOOST_AUTO_TEST_CASE(test_unsupported_key) {
+ std::stringstream ss;
+ ss << "{eth0: { some_not_supported_tag: xxx, pci-address: 0000:06:00.0, ip: 192.168.100.10, "
+ "gateway: 192.168.100.1, netmask: 255.255.255.0 } , eth1: {pci-address: 0000:06:00.1, "
+ "dhcp: true } }";
+
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
+
+BOOST_AUTO_TEST_CASE(test_bad_yaml_syntax_if_thrown) {
+ std::stringstream ss;
+ ss << "some bad: [ yaml syntax }";
+ BOOST_REQUIRE_THROW(parse_config(ss), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_pci_address_and_port_index_if_thrown) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, port-index: 0, ip: 192.168.100.10, gateway: "
+ "192.168.100.1, netmask: 255.255.255.0 } , eth1: {pci-address: 0000:06:00.1, dhcp: true} "
+ "}";
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
+
+BOOST_AUTO_TEST_CASE(test_dhcp_and_ip_if_thrown) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0, dhcp: true } , eth1: {pci-address: 0000:06:00.1, dhcp: true} }";
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
+
+BOOST_AUTO_TEST_CASE(test_ip_missing_if_thrown) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, gateway: 192.168.100.1, netmask: 255.255.255.0 } , "
+ "eth1: {pci-address: 0000:06:00.1, dhcp: true} }";
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
diff --git a/src/seastar/tests/unit/network_interface_test.cc b/src/seastar/tests/unit/network_interface_test.cc
new file mode 100644
index 000000000..08e90041d
--- /dev/null
+++ b/src/seastar/tests/unit/network_interface_test.cc
@@ -0,0 +1,88 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/net/ethernet.hh>
+#include <seastar/net/ip.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+static logger niflog("network_interface_test");
+
+SEASTAR_TEST_CASE(list_interfaces) {
+ // just verifying we have something. And can access all the stuff.
+ auto interfaces = engine().net().network_interfaces();
+ BOOST_REQUIRE_GT(interfaces.size(), 0);
+
+ for (auto& nif : interfaces) {
+ niflog.info("Iface: {}, index = {}, mtu = {}, loopback = {}, virtual = {}, up = {}",
+ nif.name(), nif.index(), nif.mtu(), nif.is_loopback(), nif.is_virtual(), nif.is_up()
+ );
+ if (nif.hardware_address().size() >= 6) {
+ niflog.info(" HW: {}", net::ethernet_address(nif.hardware_address().data()));
+ }
+ for (auto& addr : nif.addresses()) {
+ niflog.info(" Addr: {}", addr);
+ }
+ }
+
+ return make_ready_future();
+}
+
+SEASTAR_TEST_CASE(match_ipv6_scope) {
+ auto interfaces = engine().net().network_interfaces();
+
+ for (auto& nif : interfaces) {
+ if (nif.is_loopback()) {
+ continue;
+ }
+ auto i = std::find_if(nif.addresses().begin(), nif.addresses().end(), std::mem_fn(&net::inet_address::is_ipv6));
+ if (i == nif.addresses().end()) {
+ continue;
+ }
+
+ std::ostringstream ss;
+ ss << net::inet_address(i->as_ipv6_address()) << "%" << nif.name();
+ auto text = ss.str();
+
+ net::inet_address na(text);
+
+ BOOST_REQUIRE_EQUAL(na.as_ipv6_address(), i->as_ipv6_address());
+ // also verify that the inet_address itself matches
+ BOOST_REQUIRE_EQUAL(na, *i);
+ // and that inet_address _without_ scope matches.
+ BOOST_REQUIRE_EQUAL(net::inet_address(na.as_ipv6_address()), *i);
+ BOOST_REQUIRE_EQUAL(na.scope(), nif.index());
+ // and that they are not ipv4 addresses
+ BOOST_REQUIRE_THROW(i->as_ipv4_address(), std::invalid_argument);
+ BOOST_REQUIRE_THROW(na.as_ipv4_address(), std::invalid_argument);
+
+ niflog.info("Org: {}, Parsed: {}, Text: {}", *i, na, text);
+
+ }
+
+ return make_ready_future();
+}
diff --git a/src/seastar/tests/unit/noncopyable_function_test.cc b/src/seastar/tests/unit/noncopyable_function_test.cc
new file mode 100644
index 000000000..dc19d7525
--- /dev/null
+++ b/src/seastar/tests/unit/noncopyable_function_test.cc
@@ -0,0 +1,87 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/util/noncopyable_function.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(basic_tests) {
+ struct s {
+ int f1(int x) const { return x + 1; }
+ int f2(int x) { return x + 2; }
+ static int f3(int x) { return x + 3; }
+ int operator()(int x) const { return x + 4; }
+ };
+ s obj, obj2;
+ auto fn1 = noncopyable_function<int (const s*, int)>(&s::f1);
+ auto fn2 = noncopyable_function<int (s*, int)>(&s::f2);
+ auto fn3 = noncopyable_function<int (int)>(&s::f3);
+ auto fn4 = noncopyable_function<int (int)>(std::move(obj2));
+ BOOST_REQUIRE_EQUAL(fn1(&obj, 1), 2);
+ BOOST_REQUIRE_EQUAL(fn2(&obj, 1), 3);
+ BOOST_REQUIRE_EQUAL(fn3(1), 4);
+ BOOST_REQUIRE_EQUAL(fn4(1), 5);
+}
+
+template <size_t Extra>
+struct payload {
+ static unsigned live;
+ char extra[Extra];
+ std::unique_ptr<int> v;
+ payload(int x) : v(std::make_unique<int>(x)) { ++live; }
+ payload(payload&& x) noexcept : v(std::move(x.v)) { ++live; }
+ void operator=(payload&&) = delete;
+ ~payload() { --live; }
+ int operator()() const { return *v; }
+};
+
+template <size_t Extra>
+unsigned payload<Extra>::live;
+
+template <size_t Extra>
+void do_move_tests() {
+ using payload = ::payload<Extra>;
+ auto f1 = noncopyable_function<int ()>(payload(3));
+ BOOST_REQUIRE_EQUAL(payload::live, 1u);
+ BOOST_REQUIRE_EQUAL(f1(), 3);
+ auto f2 = noncopyable_function<int ()>();
+ BOOST_CHECK_THROW(f2(), std::bad_function_call);
+ f2 = std::move(f1);
+ BOOST_CHECK_THROW(f1(), std::bad_function_call);
+ BOOST_REQUIRE_EQUAL(f2(), 3);
+ BOOST_REQUIRE_EQUAL(payload::live, 1u);
+ f2 = {};
+ BOOST_REQUIRE_EQUAL(payload::live, 0u);
+ BOOST_CHECK_THROW(f2(), std::bad_function_call);
+}
+
+BOOST_AUTO_TEST_CASE(small_move_tests) {
+ do_move_tests<1>();
+}
+
+BOOST_AUTO_TEST_CASE(large_move_tests) {
+ do_move_tests<1000>();
+}
+
diff --git a/src/seastar/tests/unit/output_stream_test.cc b/src/seastar/tests/unit/output_stream_test.cc
new file mode 100644
index 000000000..cbacd38aa
--- /dev/null
+++ b/src/seastar/tests/unit/output_stream_test.cc
@@ -0,0 +1,159 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/vector-data-sink.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/later.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/net/packet.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <vector>
+
+using namespace seastar;
+using namespace net;
+
+static sstring to_sstring(const packet& p) {
+ sstring res = uninitialized_string(p.len());
+ auto i = res.begin();
+ for (auto& frag : p.fragments()) {
+ i = std::copy(frag.base, frag.base + frag.size, i);
+ }
+ return res;
+}
+
+struct stream_maker {
+ bool _trim = false;
+ size_t _size;
+
+ stream_maker size(size_t size) && {
+ _size = size;
+ return std::move(*this);
+ }
+
+ stream_maker trim(bool do_trim) && {
+ _trim = do_trim;
+ return std::move(*this);
+ }
+
+ lw_shared_ptr<output_stream<char>> operator()(data_sink sink) {
+ return make_lw_shared<output_stream<char>>(std::move(sink), _size, _trim);
+ }
+};
+
+template <typename T, typename StreamConstructor>
+future<> assert_split(StreamConstructor stream_maker, std::initializer_list<T> write_calls,
+ std::vector<std::string> expected_split) {
+ static int i = 0;
+ BOOST_TEST_MESSAGE("checking split: " << i++);
+ auto sh_write_calls = make_lw_shared<std::vector<T>>(std::move(write_calls));
+ auto sh_expected_splits = make_lw_shared<std::vector<std::string>>(std::move(expected_split));
+ auto v = make_shared<std::vector<packet>>();
+ auto out = stream_maker(data_sink(std::make_unique<vector_data_sink>(*v)));
+
+ return do_for_each(sh_write_calls->begin(), sh_write_calls->end(), [out, sh_write_calls] (auto&& chunk) {
+ return out->write(chunk);
+ }).then([out, v, sh_expected_splits] {
+ return out->close().then([out, v, sh_expected_splits] {
+ BOOST_REQUIRE_EQUAL(v->size(), sh_expected_splits->size());
+ int i = 0;
+ for (auto&& chunk : *sh_expected_splits) {
+ BOOST_REQUIRE(to_sstring((*v)[i]) == chunk);
+ i++;
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_splitting) {
+ auto ctor = stream_maker().trim(false).size(4);
+ return now()
+ .then([=] { return assert_split(ctor, {"1"}, {"1"}); })
+ .then([=] { return assert_split(ctor, {"12", "3"}, {"123"}); })
+ .then([=] { return assert_split(ctor, {"12", "34"}, {"1234"}); })
+ .then([=] { return assert_split(ctor, {"12", "345"}, {"1234", "5"}); })
+ .then([=] { return assert_split(ctor, {"1234"}, {"1234"}); })
+ .then([=] { return assert_split(ctor, {"12345"}, {"12345"}); })
+ .then([=] { return assert_split(ctor, {"1234567890"}, {"1234567890"}); })
+ .then([=] { return assert_split(ctor, {"1", "23456"}, {"1234", "56"}); })
+ .then([=] { return assert_split(ctor, {"123", "4567"}, {"1234", "567"}); })
+ .then([=] { return assert_split(ctor, {"123", "45678"}, {"1234", "5678"}); })
+ .then([=] { return assert_split(ctor, {"123", "4567890"}, {"1234", "567890"}); })
+ .then([=] { return assert_split(ctor, {"1234", "567"}, {"1234", "567"}); })
+
+ .then([] { return assert_split(stream_maker().trim(false).size(3), {"1", "234567", "89"}, {"123", "4567", "89"}); })
+ .then([] { return assert_split(stream_maker().trim(false).size(3), {"1", "2345", "67"}, {"123", "456", "7"}); })
+ ;
+}
+
+SEASTAR_TEST_CASE(test_splitting_with_trimming) {
+ auto ctor = stream_maker().trim(true).size(4);
+ return now()
+ .then([=] { return assert_split(ctor, {"1"}, {"1"}); })
+ .then([=] { return assert_split(ctor, {"12", "3"}, {"123"}); })
+ .then([=] { return assert_split(ctor, {"12", "3456789"}, {"1234", "5678", "9"}); })
+ .then([=] { return assert_split(ctor, {"12", "3456789", "12"}, {"1234", "5678", "912"}); })
+ .then([=] { return assert_split(ctor, {"123456789"}, {"1234", "5678", "9"}); })
+ .then([=] { return assert_split(ctor, {"12345678"}, {"1234", "5678"}); })
+ .then([=] { return assert_split(ctor, {"12345678", "9"}, {"1234", "5678", "9"}); })
+ .then([=] { return assert_split(ctor, {"1234", "567890"}, {"1234", "5678", "90"}); })
+ ;
+}
+
+SEASTAR_TEST_CASE(test_flush_on_empty_buffer_does_not_push_empty_packet_down_stream) {
+ auto v = make_shared<std::vector<packet>>();
+ auto out = make_shared<output_stream<char>>(
+ data_sink(std::make_unique<vector_data_sink>(*v)), 8);
+
+ return out->flush().then([v, out] {
+ BOOST_REQUIRE(v->empty());
+ return out->close();
+ }).finally([out]{});
+}
+
+SEASTAR_THREAD_TEST_CASE(test_simple_write) {
+ auto vec = std::vector<net::packet>{};
+ auto out = output_stream<char>(data_sink(std::make_unique<vector_data_sink>(vec)), 8);
+
+ auto value1 = sstring("te");
+ out.write(value1).get();
+
+
+ auto value2 = sstring("st");
+ out.write(value2).get();
+
+ auto value3 = sstring("abcdefgh1234");
+ out.write(value3).get();
+
+ out.close().get();
+
+ auto value = value1 + value2 + value3;
+ auto packets = net::packet{};
+ for (auto& p : vec) {
+ packets.append(std::move(p));
+ }
+ packets.linearize();
+ auto buf = packets.release();
+ BOOST_REQUIRE_EQUAL(buf.size(), 1);
+ BOOST_REQUIRE_EQUAL(sstring(buf.front().get(), buf.front().size()), value);
+}
diff --git a/src/seastar/tests/unit/packet_test.cc b/src/seastar/tests/unit/packet_test.cc
new file mode 100644
index 000000000..a0bdd291e
--- /dev/null
+++ b/src/seastar/tests/unit/packet_test.cc
@@ -0,0 +1,121 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/net/packet.hh>
+#include <array>
+
+using namespace seastar;
+using namespace net;
+
+BOOST_AUTO_TEST_CASE(test_many_fragments) {
+ std::vector<char> expected;
+
+ auto append = [&expected] (net::packet p, char c, size_t n) {
+ auto tmp = temporary_buffer<char>(n);
+ std::fill_n(tmp.get_write(), n, c);
+ std::fill_n(std::back_inserter(expected), n, c);
+ return net::packet(std::move(p), std::move(tmp));
+ };
+
+ net::packet p;
+ p = append(std::move(p), 'a', 5);
+ p = append(std::move(p), 'b', 31);
+ p = append(std::move(p), 'c', 65);
+ p = append(std::move(p), 'c', 4096);
+ p = append(std::move(p), 'd', 4096);
+
+ auto verify = [&expected] (const net::packet& p) {
+ BOOST_CHECK_EQUAL(p.len(), expected.size());
+ auto expected_it = expected.begin();
+ for (auto&& frag : p.fragments()) {
+ BOOST_CHECK_LE(frag.size, static_cast<size_t>(expected.end() - expected_it));
+ BOOST_CHECK(std::equal(frag.base, frag.base + frag.size, expected_it));
+ expected_it += frag.size;
+ }
+ };
+
+ auto trim_front = [&expected] (net::packet& p, size_t n) {
+ p.trim_front(n);
+ expected.erase(expected.begin(), expected.begin() + n);
+ };
+
+ verify(p);
+
+ trim_front(p, 1);
+ verify(p);
+
+ trim_front(p, 6);
+ verify(p);
+
+ trim_front(p, 29);
+ verify(p);
+
+ trim_front(p, 1024);
+ verify(p);
+
+ net::packet p2;
+ p2 = append(std::move(p2), 'z', 9);
+ p2 = append(std::move(p2), 'x', 7);
+
+ p.append(std::move(p2));
+ verify(p);
+}
+
+BOOST_AUTO_TEST_CASE(test_headers_are_contiguous) {
+ using tcp_header = std::array<char, 20>;
+ using ip_header = std::array<char, 20>;
+ char data[1000] = {};
+ fragment f{data, sizeof(data)};
+ packet p(f);
+ p.prepend_header<tcp_header>();
+ p.prepend_header<ip_header>();
+ BOOST_REQUIRE_EQUAL(p.nr_frags(), 2u);
+}
+
+BOOST_AUTO_TEST_CASE(test_headers_are_contiguous_even_with_small_fragment) {
+ using tcp_header = std::array<char, 20>;
+ using ip_header = std::array<char, 20>;
+ char data[100] = {};
+ fragment f{data, sizeof(data)};
+ packet p(f);
+ p.prepend_header<tcp_header>();
+ p.prepend_header<ip_header>();
+ BOOST_REQUIRE_EQUAL(p.nr_frags(), 2u);
+}
+
+BOOST_AUTO_TEST_CASE(test_headers_are_contiguous_even_with_many_fragments) {
+ using tcp_header = std::array<char, 20>;
+ using ip_header = std::array<char, 20>;
+ char data[100] = {};
+ fragment f{data, sizeof(data)};
+ packet p(f);
+ for (int i = 0; i < 7; ++i) {
+ p.append(packet(f));
+ }
+ p.prepend_header<tcp_header>();
+ p.prepend_header<ip_header>();
+ BOOST_REQUIRE_EQUAL(p.nr_frags(), 9u);
+}
+
diff --git a/src/seastar/tests/unit/program_options_test.cc b/src/seastar/tests/unit/program_options_test.cc
new file mode 100644
index 000000000..408752d5d
--- /dev/null
+++ b/src/seastar/tests/unit/program_options_test.cc
@@ -0,0 +1,63 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <seastar/util/program-options.hh>
+
+#include <boost/program_options.hpp>
+#include <boost/test/included/unit_test.hpp>
+
+#include <initializer_list>
+#include <vector>
+
+namespace bpo = boost::program_options;
+
+using namespace seastar;
+
+static bpo::variables_map parse(const bpo::options_description& desc, std::initializer_list<const char*> args) {
+ std::vector<const char*> raw_args{"program_options_test"};
+ for (const char* arg : args) {
+ raw_args.push_back(arg);
+ }
+
+ bpo::variables_map vars;
+ bpo::store(bpo::parse_command_line(raw_args.size(), raw_args.data(), desc), vars);
+ bpo::notify(vars);
+
+ return vars;
+}
+
+BOOST_AUTO_TEST_CASE(string_map) {
+ bpo::options_description desc;
+ desc.add_options()
+ ("ages", bpo::value<program_options::string_map>());
+
+ const auto vars = parse(desc, {"--ages", "joe=15:sally=20", "--ages", "phil=18:joe=11"});
+ const auto& ages = vars["ages"].as<program_options::string_map>();
+
+ // `string_map` values can be specified multiple times. The last association takes precedence.
+ BOOST_REQUIRE_EQUAL(ages.at("joe"), "11");
+ BOOST_REQUIRE_EQUAL(ages.at("phil"), "18");
+ BOOST_REQUIRE_EQUAL(ages.at("sally"), "20");
+
+ BOOST_REQUIRE_THROW(parse(desc, {"--ages", "tim:"}), bpo::invalid_option_value);
+}
diff --git a/src/seastar/tests/unit/queue_test.cc b/src/seastar/tests/unit/queue_test.cc
new file mode 100644
index 000000000..a8405e0ae
--- /dev/null
+++ b/src/seastar/tests/unit/queue_test.cc
@@ -0,0 +1,70 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2018 ScyllaDB
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/queue.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_queue_pop_after_abort) {
+ return async([] {
+ queue<int> q(1);
+ bool exception = false;
+ bool timer = false;
+ future<> done = make_ready_future();
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ done = sleep(1ms).then([&] {
+ timer = true;
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ });
+ try {
+ q.pop_eventually().get();
+ } catch(...) {
+ exception = !timer;
+ }
+ BOOST_REQUIRE(exception);
+ done.get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_queue_push_abort) {
+ return async([] {
+ queue<int> q(1);
+ bool exception = false;
+ bool timer = false;
+ future<> done = make_ready_future();
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ done = sleep(1ms).then([&] {
+ timer = true;
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ });
+ try {
+ q.push_eventually(1).get();
+ } catch(...) {
+ exception = !timer;
+ }
+ BOOST_REQUIRE(exception);
+ done.get();
+ });
+}
diff --git a/src/seastar/tests/unit/request_parser_test.cc b/src/seastar/tests/unit/request_parser_test.cc
new file mode 100644
index 000000000..e8d694561
--- /dev/null
+++ b/src/seastar/tests/unit/request_parser_test.cc
@@ -0,0 +1,74 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB.
+ */
+
+#include <seastar/core/ragel.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/temporary_buffer.hh>
+#include <seastar/http/request.hh>
+#include <seastar/http/request_parser.hh>
+#include <seastar/testing/test_case.hh>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(test_header_parsing) {
+ struct test_set {
+ sstring msg;
+ bool parsable;
+ sstring header_name = "";
+ sstring header_value = "";
+
+ temporary_buffer<char> buf() {
+ return temporary_buffer<char>(msg.c_str(), msg.size());
+ }
+ };
+
+ std::vector<test_set> tests = {
+ { "GET /test HTTP/1.1\r\nHost: test\r\n\r\n", true, "Host", "test" },
+ { "GET /hello HTTP/1.0\r\nHeader: Field\r\n\r\n", true, "Header", "Field" },
+ { "GET /hello HTTP/1.0\r\nHeader: \r\n\r\n", true, "Header", "" },
+ { "GET /hello HTTP/1.0\r\nHeader: f i e l d \r\n\r\n", true, "Header", "f i e l d" },
+ { "GET /hello HTTP/1.0\r\nHeader: fiel\r\n d\r\n\r\n", true, "Header", "fiel d" },
+ { "GET /hello HTTP/1.0\r\ntchars.^_`|123: printable!@#%^&*()obs_text\x80\x81\xff\r\n\r\n", true,
+ "tchars.^_`|123", "printable!@#%^&*()obs_text\x80\x81\xff" },
+ { "GET /hello HTTP/1.0\r\nHeader: Field\r\nHeader: Field2\r\n\r\n", true, "Header", "Field,Field2" },
+ { "GET /hello HTTP/1.0\r\n\r\n", true },
+ { "GET /hello HTTP/1.0\r\nHeader : Field\r\n\r\n", false },
+ { "GET /hello HTTP/1.0\r\nHeader Field\r\n\r\n", false },
+ { "GET /hello HTTP/1.0\r\nHeader@: Field\r\n\r\n", false },
+ { "GET /hello HTTP/1.0\r\nHeader: fiel\r\nd \r\n\r\n", false }
+ };
+
+ http_request_parser parser;
+ for (auto& tset : tests) {
+ parser.init();
+ BOOST_REQUIRE(parser(tset.buf()).get0().has_value());
+ BOOST_REQUIRE_NE(parser.failed(), tset.parsable);
+ if (tset.parsable) {
+ auto req = parser.get_parsed_request();
+ BOOST_REQUIRE_EQUAL(req->get_header(std::move(tset.header_name)), std::move(tset.header_value));
+ }
+ }
+ return make_ready_future<>();
+}
diff --git a/src/seastar/tests/unit/rpc_test.cc b/src/seastar/tests/unit/rpc_test.cc
new file mode 100644
index 000000000..0b729ada1
--- /dev/null
+++ b/src/seastar/tests/unit/rpc_test.cc
@@ -0,0 +1,1199 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#include "loopback_socket.hh"
+#include <seastar/rpc/rpc.hh>
+#include <seastar/rpc/rpc_types.hh>
+#include <seastar/rpc/lz4_compressor.hh>
+#include <seastar/rpc/lz4_fragmented_compressor.hh>
+#include <seastar/rpc/multi_algo_compressor_factory.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/distributed.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/defer.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+struct serializer {
+};
+
+template <typename T, typename Output>
+inline
+void write_arithmetic_type(Output& out, T v) {
+ static_assert(std::is_arithmetic<T>::value, "must be arithmetic type");
+ return out.write(reinterpret_cast<const char*>(&v), sizeof(T));
+}
+
+template <typename T, typename Input>
+inline
+T read_arithmetic_type(Input& in) {
+ static_assert(std::is_arithmetic<T>::value, "must be arithmetic type");
+ T v;
+ in.read(reinterpret_cast<char*>(&v), sizeof(T));
+ return v;
+}
+
+template <typename Output>
+inline void write(serializer, Output& output, int32_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, uint32_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, int64_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, uint64_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, double v) { return write_arithmetic_type(output, v); }
+template <typename Input>
+inline int32_t read(serializer, Input& input, rpc::type<int32_t>) { return read_arithmetic_type<int32_t>(input); }
+template <typename Input>
+inline uint32_t read(serializer, Input& input, rpc::type<uint32_t>) { return read_arithmetic_type<uint32_t>(input); }
+template <typename Input>
+inline uint64_t read(serializer, Input& input, rpc::type<uint64_t>) { return read_arithmetic_type<uint64_t>(input); }
+template <typename Input>
+inline uint64_t read(serializer, Input& input, rpc::type<int64_t>) { return read_arithmetic_type<int64_t>(input); }
+template <typename Input>
+inline double read(serializer, Input& input, rpc::type<double>) { return read_arithmetic_type<double>(input); }
+
+template <typename Output>
+inline void write(serializer, Output& out, const sstring& v) {
+ write_arithmetic_type(out, uint32_t(v.size()));
+ out.write(v.c_str(), v.size());
+}
+
+template <typename Input>
+inline sstring read(serializer, Input& in, rpc::type<sstring>) {
+ auto size = read_arithmetic_type<uint32_t>(in);
+ sstring ret = uninitialized_string(size);
+ in.read(ret.data(), size);
+ return ret;
+}
+
+using test_rpc_proto = rpc::protocol<serializer>;
+using make_socket_fn = std::function<seastar::socket ()>;
+
+struct rpc_loopback_error_injector : public loopback_error_injector {
+ int _x = 0;
+ bool server_rcv_error() override {
+ return _x++ >= 50;
+ }
+};
+
+class rpc_socket_impl : public ::net::socket_impl {
+ promise<connected_socket> _p;
+ bool _connect;
+ loopback_socket_impl _socket;
+ rpc_loopback_error_injector _error_injector;
+public:
+ rpc_socket_impl(loopback_connection_factory& factory, bool connect, bool inject_error)
+ : _connect(connect),
+ _socket(factory, inject_error ? &_error_injector : nullptr) {
+ }
+ virtual future<connected_socket> connect(socket_address sa, socket_address local, transport proto = transport::TCP) override {
+ return _connect ? _socket.connect(sa, local, proto) : _p.get_future();
+ }
+ virtual void set_reuseaddr(bool reuseaddr) override {}
+ virtual bool get_reuseaddr() const override { return false; };
+ virtual void shutdown() override {
+ if (_connect) {
+ _socket.shutdown();
+ } else {
+ _p.set_exception(std::make_exception_ptr(std::system_error(ECONNABORTED, std::system_category())));
+ }
+ }
+};
+
+struct rpc_test_config {
+ rpc::resource_limits resource_limits = {};
+ rpc::server_options server_options = {};
+ bool connect = true;
+ bool inject_error = false;
+};
+
+template<typename MsgType = int>
+class rpc_test_env {
+ struct rpc_test_service {
+ test_rpc_proto _proto;
+ test_rpc_proto::server _server;
+ std::vector<MsgType> _handlers;
+
+ rpc_test_service() = delete;
+ explicit rpc_test_service(const rpc_test_config& cfg, loopback_connection_factory& lcf)
+ : _proto(serializer())
+ , _server(_proto, cfg.server_options, lcf.get_server_socket(), cfg.resource_limits)
+ { }
+
+ test_rpc_proto& proto() {
+ return _proto;
+ }
+
+ test_rpc_proto::server& server() {
+ return _server;
+ }
+
+ future<> stop() {
+ return parallel_for_each(_handlers, [this] (auto t) {
+ return proto().unregister_handler(t);
+ }).finally([this] {
+ return server().stop();
+ });
+ }
+
+ template<typename Func>
+ auto register_handler(MsgType t, scheduling_group sg, Func func) {
+ _handlers.emplace_back(t);
+ return proto().register_handler(t, sg, std::move(func));
+ }
+
+ future<> unregister_handler(MsgType t) {
+ auto it = std::find(_handlers.begin(), _handlers.end(), t);
+ assert(it != _handlers.end());
+ _handlers.erase(it);
+ return proto().unregister_handler(t);
+ }
+ };
+
+ rpc_test_config _cfg;
+ loopback_connection_factory _lcf;
+ std::unique_ptr<sharded<rpc_test_service>> _service;
+
+public:
+ rpc_test_env() = delete;
+ explicit rpc_test_env(rpc_test_config cfg)
+ : _cfg(cfg), _service(std::make_unique<sharded<rpc_test_service>>())
+ {
+ }
+
+ using test_fn = std::function<future<> (rpc_test_env<MsgType>& env)>;
+ static future<> do_with(rpc_test_config cfg, test_fn&& func) {
+ return seastar::do_with(rpc_test_env(cfg), [func] (rpc_test_env<MsgType>& env) {
+ return env.start().then([&env, func] {
+ return func(env);
+ }).finally([&env] {
+ return env.stop();
+ });
+ });
+ }
+
+ using thread_test_fn = std::function<void (rpc_test_env<MsgType>& env)>;
+ static future<> do_with_thread(rpc_test_config cfg, thread_test_fn&& func) {
+ return do_with(std::move(cfg), [func] (rpc_test_env<MsgType>& env) {
+ return seastar::async([&env, func] {
+ func(env);
+ });
+ });
+ }
+
+ using thread_test_fn_with_client = std::function<void (rpc_test_env<MsgType>& env, test_rpc_proto::client& cl)>;
+ static future<> do_with_thread(rpc_test_config cfg, rpc::client_options co, thread_test_fn_with_client&& func) {
+ return do_with(std::move(cfg), [func, co = std::move(co)] (rpc_test_env<MsgType>& env) {
+ return seastar::async([&env, func, co = std::move(co)] {
+ test_rpc_proto::client cl(env.proto(), co, env.make_socket(), ipv4_addr());
+ auto stop = defer([&] { cl.stop().get(); });
+ func(env, cl);
+ });
+ });
+ }
+
+ static future<> do_with_thread(rpc_test_config cfg, thread_test_fn_with_client&& func) {
+ return do_with_thread(std::move(cfg), rpc::client_options(), std::move(func));
+ }
+
+ auto make_socket() {
+ return seastar::socket(std::make_unique<rpc_socket_impl>(_lcf, _cfg.connect, _cfg.inject_error));
+ };
+
+ test_rpc_proto& proto() {
+ return local_service().proto();
+ }
+
+ test_rpc_proto::server& server() {
+ return local_service().server();
+ }
+
+ template<typename Func>
+ future<> register_handler(MsgType t, scheduling_group sg, Func func) {
+ return _service->invoke_on_all([t, func = std::move(func), sg] (rpc_test_service& s) mutable {
+ s.register_handler(t, sg, std::move(func));
+ });
+ }
+
+ template<typename Func>
+ future<> register_handler(MsgType t, Func func) {
+ return register_handler(t, scheduling_group(), std::move(func));
+ }
+
+ future<> unregister_handler(MsgType t) {
+ return _service->invoke_on_all([t] (rpc_test_service& s) mutable {
+ return s.unregister_handler(t);
+ });
+ }
+
+private:
+ rpc_test_service& local_service() {
+ return _service->local();
+
+ }
+
+ future<> start() {
+ return _service->start(std::cref(_cfg), std::ref(_lcf));
+ }
+
+ future<> stop() {
+ return _service->stop().then([this] {
+ return _lcf.destroy_all_shards();
+ });
+ }
+};
+
+struct cfactory : rpc::compressor::factory {
+ mutable int use_compression = 0;
+ const sstring name;
+ cfactory(sstring name_ = "LZ4") : name(std::move(name_)) {}
+ const sstring& supported() const override {
+ return name;
+ }
+ class mylz4 : public rpc::lz4_compressor {
+ sstring _name;
+ public:
+ mylz4(const sstring& n) : _name(n) {}
+ sstring name() const override {
+ return _name;
+ }
+ };
+ std::unique_ptr<rpc::compressor> negotiate(sstring feature, bool is_server) const override {
+ if (feature == name) {
+ use_compression++;
+ return std::make_unique<mylz4>(name);
+ } else {
+ return nullptr;
+ }
+ }
+};
+
+SEASTAR_TEST_CASE(test_rpc_connect) {
+ std::vector<future<>> fs;
+
+ for (auto i = 0; i < 2; i++) {
+ for (auto j = 0; j < 4; j++) {
+ auto factory = std::make_unique<cfactory>();
+ rpc::server_options so;
+ rpc::client_options co;
+ if (i == 1) {
+ so.compressor_factory = factory.get();
+ }
+ if (j & 1) {
+ co.compressor_factory = factory.get();
+ }
+ co.send_timeout_data = j & 2;
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ auto f = rpc_test_env<>::do_with_thread(cfg, co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ auto result = sum(c1, 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }).handle_exception([] (auto ep) {
+ BOOST_FAIL("No exception expected");
+ }).finally([factory = std::move(factory), i, j = j & 1] {
+ if (i == 1 && j == 1) {
+ BOOST_REQUIRE_EQUAL(factory->use_compression, 2);
+ } else {
+ BOOST_REQUIRE_EQUAL(factory->use_compression, 0);
+ }
+ });
+ fs.emplace_back(std::move(f));
+ }
+ }
+ return when_all(fs.begin(), fs.end()).discard_result();
+}
+
+SEASTAR_TEST_CASE(test_rpc_connect_multi_compression_algo) {
+ auto factory1 = std::make_unique<cfactory>();
+ auto factory2 = std::make_unique<cfactory>("LZ4NEW");
+ rpc::server_options so;
+ rpc::client_options co;
+ static rpc::multi_algo_compressor_factory server({factory1.get(), factory2.get()});
+ static rpc::multi_algo_compressor_factory client({factory2.get(), factory1.get()});
+ so.compressor_factory = &server;
+ co.compressor_factory = &client;
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ return rpc_test_env<>::do_with_thread(cfg, co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ auto result = sum(c1, 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }).finally([factory1 = std::move(factory1), factory2 = std::move(factory2)] {
+ BOOST_REQUIRE_EQUAL(factory1->use_compression, 0);
+ BOOST_REQUIRE_EQUAL(factory2->use_compression, 2);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_connect_abort) {
+ rpc_test_config cfg;
+ cfg.connect = false;
+ return rpc_test_env<>::do_with_thread(cfg, [] (rpc_test_env<>& env) {
+ test_rpc_proto::client c1(env.proto(), {}, env.make_socket(), ipv4_addr());
+ env.register_handler(1, []() { return make_ready_future<>(); }).get();
+ auto f = env.proto().make_client<void ()>(1);
+ c1.stop().get0();
+ try {
+ f(c1).get0();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_cancel) {
+ using namespace std::chrono_literals;
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ bool rpc_executed = false;
+ int good = 0;
+ promise<> handler_called;
+ future<> f_handler_called = handler_called.get_future();
+ env.register_handler(1, [&rpc_executed, &handler_called] {
+ handler_called.set_value(); rpc_executed = true; return sleep(1ms);
+ }).get();
+ auto call = env.proto().make_client<void ()>(1);
+ rpc::cancellable cancel;
+ auto f = call(c1, cancel);
+ // cancel send side
+ cancel.cancel();
+ try {
+ f.get();
+ } catch(rpc::canceled_error&) {
+ good += !rpc_executed;
+ };
+ f = call(c1, cancel);
+ // cancel wait side
+ f_handler_called.then([&cancel] {
+ cancel.cancel();
+ }).get();
+ try {
+ f.get();
+ } catch(rpc::canceled_error&) {
+ good += 10*rpc_executed;
+ };
+ BOOST_REQUIRE_EQUAL(good, 11);
+ });
+}
+
+SEASTAR_TEST_CASE(test_message_to_big) {
+ rpc_test_config cfg;
+ cfg.resource_limits = {0, 1, 100};
+ return rpc_test_env<>::do_with_thread(cfg, [] (rpc_test_env<>& env, test_rpc_proto::client& c) {
+ bool good = true;
+ env.register_handler(1, [&] (sstring payload) mutable {
+ good = false;
+ }).get();
+ auto call = env.proto().make_client<void (sstring)>(1);
+ try {
+ call(c, uninitialized_string(101)).get();
+ good = false;
+ } catch(std::runtime_error& err) {
+ } catch(...) {
+ good = false;
+ }
+ BOOST_REQUIRE_EQUAL(good, true);
+ });
+}
+
+struct stream_test_result {
+ bool client_source_closed = false;
+ bool server_source_closed = false;
+ bool sink_exception = false;
+ bool sink_close_exception = false;
+ bool source_done_exception = false;
+ bool server_done_exception = false;
+ bool client_stop_exception = false;
+ int server_sum = 0;
+};
+
+future<stream_test_result> stream_test_func(rpc_test_env<>& env, bool stop_client, bool expect_connection_error = false) {
+ return seastar::async([&env, stop_client, expect_connection_error] {
+ stream_test_result r;
+ test_rpc_proto::client c(env.proto(), {}, env.make_socket(), ipv4_addr());
+ future<> server_done = make_ready_future();
+ env.register_handler(1, [&](int i, rpc::source<int> source) {
+ BOOST_REQUIRE_EQUAL(i, 666);
+ auto sink = source.make_sink<serializer, sstring>();
+ auto sink_loop = seastar::async([sink] () mutable {
+ for (auto i = 0; i < 100; i++) {
+ sink("seastar").get();
+ sleep(std::chrono::milliseconds(1)).get();
+ }
+ }).finally([sink] () mutable {
+ return sink.flush();
+ }).finally([sink] () mutable {
+ return sink.close();
+ }).finally([sink] {});
+
+ auto source_loop = seastar::async([source, &r] () mutable {
+ while (!r.server_source_closed) {
+ auto data = source().get0();
+ if (data) {
+ r.server_sum += std::get<0>(*data);
+ } else {
+ r.server_source_closed = true;
+ try {
+ // check that reading after eos does not crash
+ // and throws correct exception
+ source().get();
+ } catch (rpc::stream_closed& ex) {
+ // expected
+ } catch (...) {
+ BOOST_FAIL("wrong exception on reading from a stream after eos");
+ }
+ }
+ }
+ });
+ server_done = when_all_succeed(std::move(sink_loop), std::move(source_loop)).discard_result();
+ return sink;
+ }).get();
+ auto call = env.proto().make_client<rpc::source<sstring> (int, rpc::sink<int>)>(1);
+ auto x = [&] {
+ try {
+ return c.make_stream_sink<serializer, int>(env.make_socket()).get0();
+ } catch (...) {
+ c.stop().get();
+ throw;
+ }
+ };
+ auto sink = x();
+ auto source = call(c, 666, sink).get0();
+ auto source_done = seastar::async([&] {
+ while (!r.client_source_closed) {
+ auto data = source().get0();
+ if (data) {
+ BOOST_REQUIRE_EQUAL(std::get<0>(*data), "seastar");
+ } else {
+ r.client_source_closed = true;
+ }
+ }
+ });
+ auto check_exception = [] (auto f) {
+ try {
+ f.get();
+ } catch (...) {
+ return true;
+ }
+ return false;
+ };
+ future<> stop_client_future = make_ready_future();
+ // With a connection error sink() will eventually fail, but we
+ // cannot guarantee when.
+ int max = expect_connection_error ? std::numeric_limits<int>::max() : 101;
+ for (int i = 1; i < max; i++) {
+ if (stop_client && i == 50) {
+ // stop client while stream is in use
+ stop_client_future = c.stop();
+ }
+ sleep(std::chrono::milliseconds(1)).get();
+ r.sink_exception = check_exception(sink(i));
+ if (r.sink_exception) {
+ break;
+ }
+ }
+ r.sink_close_exception = check_exception(sink.close());
+ r.source_done_exception = check_exception(std::move(source_done));
+ r.server_done_exception = check_exception(std::move(server_done));
+ r.client_stop_exception = check_exception(!stop_client ? c.stop() : std::move(stop_client_future));
+ return r;
+ });
+}
+
+SEASTAR_TEST_CASE(test_stream_simple) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ return rpc_test_env<>::do_with(cfg, [] (rpc_test_env<>& env) {
+ return stream_test_func(env, false).then([] (stream_test_result r) {
+ BOOST_REQUIRE(r.client_source_closed);
+ BOOST_REQUIRE(r.server_source_closed);
+ BOOST_REQUIRE(r.server_sum == 5050);
+ BOOST_REQUIRE(!r.sink_exception);
+ BOOST_REQUIRE(!r.sink_close_exception);
+ BOOST_REQUIRE(!r.source_done_exception);
+ BOOST_REQUIRE(!r.server_done_exception);
+ BOOST_REQUIRE(!r.client_stop_exception);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_stream_stop_client) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ return rpc_test_env<>::do_with(cfg, [] (rpc_test_env<>& env) {
+ return stream_test_func(env, true).then([] (stream_test_result r) {
+ BOOST_REQUIRE(!r.client_source_closed);
+ BOOST_REQUIRE(!r.server_source_closed);
+ BOOST_REQUIRE(r.sink_exception);
+ BOOST_REQUIRE(r.sink_close_exception);
+ BOOST_REQUIRE(r.source_done_exception);
+ BOOST_REQUIRE(r.server_done_exception);
+ BOOST_REQUIRE(!r.client_stop_exception);
+ });
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_stream_connection_error) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ cfg.inject_error = true;
+ return rpc_test_env<>::do_with(cfg, [] (rpc_test_env<>& env) {
+ return stream_test_func(env, false, true).then([] (stream_test_result r) {
+ BOOST_REQUIRE(!r.client_source_closed);
+ BOOST_REQUIRE(!r.server_source_closed);
+ BOOST_REQUIRE(r.sink_exception);
+ BOOST_REQUIRE(r.sink_close_exception);
+ BOOST_REQUIRE(r.source_done_exception);
+ BOOST_REQUIRE(r.server_done_exception);
+ BOOST_REQUIRE(!r.client_stop_exception);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_scheduling) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ auto sg = create_scheduling_group("rpc", 100).get0();
+ env.register_handler(1, sg, [] () {
+ return make_ready_future<unsigned>(internal::scheduling_group_index(current_scheduling_group()));
+ }).get();
+ auto call_sg_id = env.proto().make_client<unsigned ()>(1);
+ auto id = call_sg_id(c1).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg));
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_rpc_scheduling_connection_based) {
+ auto sg1 = create_scheduling_group("sg1", 100).get0();
+ auto sg1_kill = defer([&] { destroy_scheduling_group(sg1).get(); });
+ auto sg2 = create_scheduling_group("sg2", 100).get0();
+ auto sg2_kill = defer([&] { destroy_scheduling_group(sg2).get(); });
+ rpc::resource_limits limits;
+ limits.isolate_connection = [sg1, sg2] (sstring cookie) {
+ auto sg = current_scheduling_group();
+ if (cookie == "sg1") {
+ sg = sg1;
+ } else if (cookie == "sg2") {
+ sg = sg2;
+ }
+ rpc::isolation_config cfg;
+ cfg.sched_group = sg;
+ return cfg;
+ };
+ rpc_test_config cfg;
+ cfg.resource_limits = limits;
+ rpc_test_env<>::do_with_thread(cfg, [sg1, sg2] (rpc_test_env<>& env) {
+ rpc::client_options co1;
+ co1.isolation_cookie = "sg1";
+ test_rpc_proto::client c1(env.proto(), co1, env.make_socket(), ipv4_addr());
+ rpc::client_options co2;
+ co2.isolation_cookie = "sg2";
+ test_rpc_proto::client c2(env.proto(), co2, env.make_socket(), ipv4_addr());
+ env.register_handler(1, [] {
+ return make_ready_future<unsigned>(internal::scheduling_group_index(current_scheduling_group()));
+ }).get();
+ auto call_sg_id = env.proto().make_client<unsigned ()>(1);
+ unsigned id;
+ id = call_sg_id(c1).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg1));
+ id = call_sg_id(c2).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg2));
+ c1.stop().get();
+ c2.stop().get();
+ }).get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_rpc_scheduling_connection_based_compatibility) {
+ auto sg1 = create_scheduling_group("sg1", 100).get0();
+ auto sg1_kill = defer([&] { destroy_scheduling_group(sg1).get(); });
+ auto sg2 = create_scheduling_group("sg2", 100).get0();
+ auto sg2_kill = defer([&] { destroy_scheduling_group(sg2).get(); });
+ rpc::resource_limits limits;
+ limits.isolate_connection = [sg1, sg2] (sstring cookie) {
+ auto sg = current_scheduling_group();
+ if (cookie == "sg1") {
+ sg = sg1;
+ } else if (cookie == "sg2") {
+ sg = sg2;
+ }
+ rpc::isolation_config cfg;
+ cfg.sched_group = sg;
+ return cfg;
+ };
+ rpc_test_config cfg;
+ cfg.resource_limits = limits;
+ rpc_test_env<>::do_with_thread(cfg, [sg1, sg2] (rpc_test_env<>& env) {
+ rpc::client_options co1;
+ co1.isolation_cookie = "sg1";
+ test_rpc_proto::client c1(env.proto(), co1, env.make_socket(), ipv4_addr());
+ rpc::client_options co2;
+ co2.isolation_cookie = "sg2";
+ test_rpc_proto::client c2(env.proto(), co2, env.make_socket(), ipv4_addr());
+ // An old client, that doesn't have an isolation cookie
+ rpc::client_options co3;
+ test_rpc_proto::client c3(env.proto(), co3, env.make_socket(), ipv4_addr());
+ // A server that uses sg1 if the client is old
+ env.register_handler(1, sg1, [] () {
+ return make_ready_future<unsigned>(internal::scheduling_group_index(current_scheduling_group()));
+ }).get();
+ auto call_sg_id = env.proto().make_client<unsigned ()>(1);
+ unsigned id;
+ id = call_sg_id(c1).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg1));
+ id = call_sg_id(c2).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg2));
+ id = call_sg_id(c3).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg1));
+ c1.stop().get();
+ c2.stop().get();
+ c3.stop().get();
+ }).get();
+}
+
+void test_compressor(std::function<std::unique_ptr<seastar::rpc::compressor>()> compressor_factory) {
+ using namespace seastar::rpc;
+
+ auto linearize = [&] (const auto& buffer) {
+ return seastar::visit(buffer.bufs,
+ [] (const temporary_buffer<char>& buf) {
+ return buf.clone();
+ },
+ [&] (const std::vector<temporary_buffer<char>>& bufs) {
+ auto buf = temporary_buffer<char>(buffer.size);
+ auto dst = buf.get_write();
+ for (auto& b : bufs) {
+ dst = std::copy_n(b.get(), b.size(), dst);
+ }
+ return buf;
+ }
+ );
+ };
+
+ auto split_buffer = [&] (temporary_buffer<char> b, size_t chunk_size) {
+ std::vector<temporary_buffer<char>> bufs;
+ auto src = b.get();
+ auto n = b.size();
+ while (n) {
+ auto this_chunk = std::min(chunk_size, n);
+ bufs.emplace_back(this_chunk);
+ std::copy_n(src, this_chunk, bufs.back().get_write());
+ src += this_chunk;
+ n -= this_chunk;
+ }
+ return bufs;
+ };
+
+ auto clone = [&] (const auto& buffer) {
+ auto c = std::decay_t<decltype(buffer)>();
+ c.size = buffer.size;
+ c.bufs = seastar::visit(buffer.bufs,
+ [] (const temporary_buffer<char>& buf) -> decltype(c.bufs) {
+ return buf.clone();
+ },
+ [] (const std::vector<temporary_buffer<char>>& bufs) -> decltype(c.bufs) {
+ std::vector<temporary_buffer<char>> c;
+ c.reserve(bufs.size());
+ for (auto& b : bufs) {
+ c.emplace_back(b.clone());
+ }
+ return c;
+ }
+ );
+ return c;
+ };
+
+ auto compressor = compressor_factory();
+
+ std::vector<std::tuple<sstring, size_t, snd_buf>> inputs;
+
+ auto& eng = testing::local_random_engine;
+ auto dist = std::uniform_int_distribution<char>();
+
+ auto snd = snd_buf(1);
+ *snd.front().get_write() = 'a';
+ inputs.emplace_back("one byte, no headroom", 0, std::move(snd));
+
+ snd = snd_buf(1);
+ *snd.front().get_write() = 'a';
+ inputs.emplace_back("one byte, 128k of headroom", 128 * 1024, std::move(snd));
+
+ auto gen_fill = [&](size_t s, sstring msg, std::optional<size_t> split = {}) {
+ auto buf = temporary_buffer<char>(s);
+ std::fill_n(buf.get_write(), s, 'a');
+
+ auto snd = snd_buf();
+ snd.size = s;
+ if (split) {
+ snd.bufs = split_buffer(buf.clone(), *split);
+ } else {
+ snd.bufs = buf.clone();
+ }
+ inputs.emplace_back(msg, 0, std::move(snd));
+ };
+
+ gen_fill(16 * 1024, "single 16 kB buffer of \'a\'");
+
+ auto gen_rand = [&](size_t s, sstring msg, std::optional<size_t> split = {}) {
+ auto buf = temporary_buffer<char>(s);
+ std::generate_n(buf.get_write(), s, [&] { return dist(eng); });
+
+ auto snd = snd_buf();
+ snd.size = s;
+ if (split) {
+ snd.bufs = split_buffer(buf.clone(), *split);
+ } else {
+ snd.bufs = buf.clone();
+ }
+ inputs.emplace_back(msg, 0, std::move(snd));
+ };
+
+ gen_rand(16 * 1024, "single 16 kB buffer of random");
+
+ auto gen_text = [&](size_t s, sstring msg, std::optional<size_t> split = {}) {
+ static const std::string_view text = "The quick brown fox wants bananas for his long term health but sneaks bacon behind his wife's back. ";
+
+ auto buf = temporary_buffer<char>(s);
+ size_t n = 0;
+ while (n < s) {
+ auto rem = std::min(s - n, text.size());
+ std::copy(text.data(), text.data() + rem, buf.get_write() + n);
+ n += rem;
+ }
+
+ auto snd = snd_buf();
+ snd.size = s;
+ if (split) {
+ snd.bufs = split_buffer(buf.clone(), *split);
+ } else {
+ snd.bufs = buf.clone();
+ }
+ inputs.emplace_back(msg, 0, std::move(snd));
+ };
+
+
+ for (auto s : { 1, 4, 8 }) {
+ for (auto ss : { 32, 64, 128, 48, 56, 246, 511 }) {
+ gen_fill(s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} kB - {}", s, ss, ss), ss * 1024 - ss);
+ gen_fill(s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} kB", s, ss), ss * 1024);
+ gen_rand(s * 1024 * 1024, format("{} MB buffer of random split into {} kB", s, ss), ss * 1024);
+
+ gen_fill(s * 1024 * 1024 + 1, format("{} MB + 1B buffer of \'a\' split into {} kB", s, ss), ss * 1024);
+ gen_rand(s * 1024 * 1024 + 1, format("{} MB + 1B buffer of random split into {} kB", s, ss), ss * 1024);
+ }
+
+ for (auto ss : { 128, 246, 511, 3567, 2*1024, 8*1024 }) {
+ gen_fill(s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} B", s, ss), ss);
+ gen_rand(s * 1024 * 1024, format("{} MB buffer of random split into {} B", s, ss), ss);
+ gen_text(s * 1024 * 1024, format("{} MB buffer of text split into {} B", s, ss), ss);
+ gen_fill(s * 1024 * 1024 - ss, format("{} MB - {}B buffer of \'a\' split into {} B", s, ss, ss), ss);
+ gen_rand(s * 1024 * 1024 - ss, format("{} MB - {}B buffer of random split into {} B", s, ss, ss), ss);
+ gen_text(s * 1024 * 1024 - ss, format("{} MB - {}B buffer of random split into {} B", s, ss, ss), ss);
+ }
+ }
+
+ for (auto s : { 64*1024 + 5670, 16*1024 + 3421, 32*1024 - 321 }) {
+ gen_fill(s, format("{} bytes buffer of \'a\'", s));
+ gen_rand(s, format("{} bytes buffer of random", s));
+ gen_text(s, format("{} bytes buffer of text", s));
+ }
+
+ std::vector<std::tuple<sstring, std::function<rcv_buf(snd_buf)>>> transforms {
+ { "identity", [] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = std::move(snd.bufs);
+ return rcv;
+ } },
+ { "linearized", [&linearize] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = linearize(snd);
+ return rcv;
+ } },
+ { "split 1 B", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 1);
+ return rcv;
+ } },
+ { "split 129 B", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 129);
+ return rcv;
+ } },
+ { "split 4 kB", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 4096);
+ return rcv;
+ } },
+ { "split 4 kB - 128", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 4096 - 128);
+ return rcv;
+ } },
+ };
+
+ auto sanity_check = [&] (const auto& buffer) {
+ auto actual_size = seastar::visit(buffer.bufs,
+ [] (const temporary_buffer<char>& buf) {
+ return buf.size();
+ },
+ [] (const std::vector<temporary_buffer<char>>& bufs) {
+ return boost::accumulate(bufs, size_t(0), [] (size_t sz, const temporary_buffer<char>& buf) {
+ return sz + buf.size();
+ });
+ }
+ );
+ BOOST_CHECK_EQUAL(actual_size, buffer.size);
+ };
+
+ for (auto& in : inputs) {
+ BOOST_TEST_MESSAGE("Input: " << std::get<0>(in));
+ auto headroom = std::get<1>(in);
+ auto compressed = compressor->compress(headroom, clone(std::get<2>(in)));
+ sanity_check(compressed);
+
+ // Remove headroom
+ BOOST_CHECK_GE(compressed.size, headroom);
+ compressed.size -= headroom;
+ seastar::visit(compressed.bufs,
+ [&] (temporary_buffer<char>& buf) {
+ BOOST_CHECK_GE(buf.size(), headroom);
+ buf.trim_front(headroom);
+ },
+ [&] (std::vector<temporary_buffer<char>>& bufs) {
+ while (headroom) {
+ BOOST_CHECK(!bufs.empty());
+ auto to_remove = std::min(bufs.front().size(), headroom);
+ bufs.front().trim_front(to_remove);
+ if (bufs.front().empty() && bufs.size() > 1) {
+ bufs.erase(bufs.begin());
+ }
+ headroom -= to_remove;
+ }
+ }
+ );
+
+ auto in_l = linearize(std::get<2>(in));
+
+ for (auto& t : transforms) {
+ BOOST_TEST_MESSAGE(" Transform: " << std::get<0>(t));
+ auto received = std::get<1>(t)(clone(compressed));
+
+ auto decompressed = compressor->decompress(std::move(received));
+ sanity_check(decompressed);
+
+ BOOST_CHECK_EQUAL(decompressed.size, std::get<2>(in).size);
+
+ auto out_l = linearize(decompressed);
+
+ BOOST_CHECK_EQUAL(in_l.size(), out_l.size());
+ BOOST_CHECK(in_l == out_l);
+ }
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_lz4_compressor) {
+ test_compressor([] { return std::make_unique<rpc::lz4_compressor>(); });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_lz4_fragmented_compressor) {
+ test_compressor([] { return std::make_unique<rpc::lz4_fragmented_compressor>(); });
+}
+
+// Test reproducing issue #671: If timeout is time_point::max(), translating
+// it to relative timeout in the sender and then back in the receiver, when
+// these calculations happen across a millisecond boundary, overflowed the
+// integer and mislead the receiver to think the requested timeout was
+// negative, and cause it drop its response, so the RPC call never completed.
+SEASTAR_TEST_CASE(test_max_absolute_timeout) {
+ // The typical failure of this test is a hang. So we use semaphore to
+ // stop the test either when it succeeds, or after a long enough hang.
+ auto success = make_lw_shared<bool>(false);
+ auto done = make_lw_shared<semaphore>(0);
+ auto abrt = make_lw_shared<abort_source>();
+ (void) seastar::sleep_abortable(std::chrono::seconds(3), *abrt).then([done, success] {
+ done->signal(1);
+ }).handle_exception([] (std::exception_ptr) {});
+ rpc::client_options co;
+ co.send_timeout_data = 1;
+ (void)rpc_test_env<>::do_with_thread(rpc_test_config(), co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ // The bug only reproduces if the calculation done on the sender
+ // and receiver sides, happened across a millisecond boundary.
+ // We can't control when it happens, so we just need to loop many
+ // times, at least many milliseconds, to increase the probability
+ // that we catch the bug. Experimentally, if we loop for 200ms, we
+ // catch the bug in #671 virtually every time.
+ auto until = seastar::lowres_clock::now() + std::chrono::milliseconds(200);
+ while (seastar::lowres_clock::now() <= until) {
+ auto result = sum(c1, rpc::rpc_clock_type::time_point::max(), 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }
+ }).then([success, done, abrt] {
+ *success = true;
+ abrt->request_abort();
+ done->signal();
+ });
+ return done->wait().then([done, success] {
+ BOOST_REQUIRE(*success);
+ });
+}
+
+// Similar to the above test: Test that a relative timeout duration::max()
+// also works, and again doesn't cause the timeout wrapping around to the
+// past and causing dropped responses.
+SEASTAR_TEST_CASE(test_max_relative_timeout) {
+ // The typical failure of this test is a hang. So we use semaphore to
+ // stop the test either when it succeeds, or after a long enough hang.
+ auto success = make_lw_shared<bool>(false);
+ auto done = make_lw_shared<semaphore>(0);
+ auto abrt = make_lw_shared<abort_source>();
+ (void) seastar::sleep_abortable(std::chrono::seconds(3), *abrt).then([done, success] {
+ done->signal(1);
+ }).handle_exception([] (std::exception_ptr) {});
+ rpc::client_options co;
+ co.send_timeout_data = 1;
+ (void)rpc_test_env<>::do_with_thread(rpc_test_config(), co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ // The following call used to always hang, when max()+now()
+ // overflowed and appeared to be a negative timeout.
+ auto result = sum(c1, rpc::rpc_clock_type::duration::max(), 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }).then([success, done, abrt] {
+ *success = true;
+ abrt->request_abort();
+ done->signal();
+ });
+ return done->wait().then([done, success] {
+ BOOST_REQUIRE(*success);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_tuple) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [] () {
+ return make_ready_future<rpc::tuple<int, long>>(rpc::tuple<int, long>(1, 0x7'0000'0000L));
+ }).get();
+ auto f1 = env.proto().make_client<rpc::tuple<int, long> ()>(1);
+ auto result = f1(c1).get0();
+ BOOST_REQUIRE_EQUAL(std::get<0>(result), 1);
+ BOOST_REQUIRE_EQUAL(std::get<1>(result), 0x7'0000'0000L);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_nonvariadic_client_variadic_server) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ // Server is variadic
+ env.register_handler(1, [] () {
+ return make_ready_future<rpc::tuple<int, long>>(rpc::tuple(1, 0x7'0000'0000L));
+ }).get();
+ // Client is non-variadic
+ auto f1 = env.proto().make_client<future<rpc::tuple<int, long>> ()>(1);
+ auto result = f1(c1).get0();
+ BOOST_REQUIRE_EQUAL(std::get<0>(result), 1);
+ BOOST_REQUIRE_EQUAL(std::get<1>(result), 0x7'0000'0000L);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_variadic_client_nonvariadic_server) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ // Server is nonvariadic
+ env.register_handler(1, [] () {
+ return make_ready_future<rpc::tuple<int, long>>(rpc::tuple<int, long>(1, 0x7'0000'0000L));
+ }).get();
+ // Client is variadic
+ auto f1 = env.proto().make_client<future<rpc::tuple<int, long>> ()>(1);
+ auto result = f1(c1).get0();
+ BOOST_REQUIRE_EQUAL(std::get<0>(result), 1);
+ BOOST_REQUIRE_EQUAL(std::get<1>(result), 0x7'0000'0000L);
+ });
+}
+
+SEASTAR_TEST_CASE(test_handler_registration) {
+ rpc_test_config cfg;
+ cfg.connect = false;
+ return rpc_test_env<>::do_with_thread(cfg, [] (rpc_test_env<>& env) {
+ auto& proto = env.proto();
+
+ // new proto must be empty
+ BOOST_REQUIRE(!proto.has_handlers());
+
+ // non-existing handler should not be found
+ BOOST_REQUIRE(!proto.has_handler(1));
+
+ // unregistered non-existing handler should return ready future
+ auto fut = proto.unregister_handler(1);
+ BOOST_REQUIRE(fut.available() && !fut.failed());
+ fut.get();
+
+ // existing handler should be found
+ auto handler = [] () { return make_ready_future<>(); };
+ proto.register_handler(1, handler);
+ BOOST_REQUIRE(proto.has_handler(1));
+
+ // cannot register handler if already registered
+ BOOST_REQUIRE_THROW(proto.register_handler(1, handler), std::runtime_error);
+
+ // unregistered handler should not be found
+ proto.unregister_handler(1).get();
+ BOOST_REQUIRE(!proto.has_handler(1));
+
+ // re-registering a handler should succeed
+ proto.register_handler(1, handler);
+ BOOST_REQUIRE(proto.has_handler(1));
+
+ // proto with handlers must not be empty
+ BOOST_REQUIRE(proto.has_handlers());
+ });
+}
+
+SEASTAR_TEST_CASE(test_unregister_handler) {
+ using namespace std::chrono_literals;
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ promise<> handler_called;
+ future<> f_handler_called = handler_called.get_future();
+ bool rpc_executed = false;
+ bool rpc_completed = false;
+
+ auto reset_state = [&f_handler_called, &rpc_executed, &rpc_completed] {
+ if (f_handler_called.available()) {
+ f_handler_called.get();
+ }
+ rpc_executed = false;
+ rpc_completed = false;
+ };
+
+ auto get_handler = [&handler_called, &rpc_executed, &rpc_completed] {
+ return [&handler_called, &rpc_executed, &rpc_completed] {
+ handler_called.set_value();
+ rpc_executed = true;
+ return sleep(1ms).then([&rpc_completed] {
+ rpc_completed = true;
+ });
+ };
+ };
+
+ // handler should not run if unregistered before being called
+ env.register_handler(1, get_handler()).get();
+ env.unregister_handler(1).get();
+ BOOST_REQUIRE(!f_handler_called.available());
+ BOOST_REQUIRE(!rpc_executed);
+ BOOST_REQUIRE(!rpc_completed);
+
+ // verify normal execution path
+ env.register_handler(1, get_handler()).get();
+ auto call = env.proto().make_client<void ()>(1);
+ call(c1).get();
+ BOOST_REQUIRE(f_handler_called.available());
+ BOOST_REQUIRE(rpc_executed);
+ BOOST_REQUIRE(rpc_completed);
+ reset_state();
+
+ // call should fail after handler is unregistered
+ env.unregister_handler(1).get();
+ try {
+ call(c1).get();
+ BOOST_REQUIRE(false);
+ } catch (rpc::unknown_verb_error&) {
+ // expected
+ } catch (...) {
+ std::cerr << "call failed in an unexpected way: " << std::current_exception() << std::endl;
+ BOOST_REQUIRE(false);
+ }
+ BOOST_REQUIRE(!f_handler_called.available());
+ BOOST_REQUIRE(!rpc_executed);
+ BOOST_REQUIRE(!rpc_completed);
+
+ // unregistered is allowed while call is in flight
+ auto delayed_unregister = [&env] {
+ return sleep(500us).then([&env] {
+ return env.unregister_handler(1);
+ });
+ };
+
+ env.register_handler(1, get_handler()).get();
+ try {
+ when_all_succeed(call(c1), delayed_unregister()).get();
+ reset_state();
+ } catch (rpc::unknown_verb_error&) {
+ // expected
+ } catch (...) {
+ std::cerr << "call failed in an unexpected way: " << std::current_exception() << std::endl;
+ BOOST_REQUIRE(false);
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_loggers) {
+ static seastar::logger log("dummy");
+ log.set_level(log_level::debug);
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ socket_address dummy_addr;
+ auto& proto = env.proto();
+ auto& logger = proto.get_logger();
+ logger(dummy_addr, "Hello0");
+ logger(dummy_addr, log_level::debug, "Hello1");
+ proto.set_logger(&log);
+ logger(dummy_addr, "Hello2");
+ logger(dummy_addr, log_level::debug, "Hello3");
+ // We *want* to test the deprecated API, don't spam warnings about it.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ proto.set_logger([] (const sstring& str) {
+ log.info("Test: {}", str);
+ });
+#pragma GCC diagnostic pop
+ logger(dummy_addr, "Hello4");
+ logger(dummy_addr, log_level::debug, "Hello5");
+ proto.set_logger(nullptr);
+ logger(dummy_addr, "Hello6");
+ logger(dummy_addr, log_level::debug, "Hello7");
+ });
+}
+
+static_assert(std::is_same_v<decltype(rpc::tuple(1U, 1L)), rpc::tuple<unsigned, long>>, "rpc::tuple deduction guid not working");
diff --git a/src/seastar/tests/unit/scheduling_group_test.cc b/src/seastar/tests/unit/scheduling_group_test.cc
new file mode 100644
index 000000000..5f30d5a97
--- /dev/null
+++ b/src/seastar/tests/unit/scheduling_group_test.cc
@@ -0,0 +1,251 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+#include <algorithm>
+#include <vector>
+#include <chrono>
+
+#include <seastar/core/thread.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/execution_stage.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/scheduling_specific.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/with_scheduling_group.hh>
+#include <seastar/util/later.hh>
+
+using namespace std::chrono_literals;
+
+using namespace seastar;
+
+/**
+ * Test setting primitive and object as a value after all groups are created
+ */
+SEASTAR_THREAD_TEST_CASE(sg_specific_values_define_after_sg_create) {
+ using ivec = std::vector<int>;
+ const int num_scheduling_groups = 4;
+ std::vector<scheduling_group> sgs;
+ for (int i = 0; i < num_scheduling_groups; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+
+ const auto destroy_scheduling_groups = defer([&sgs] () {
+ for (scheduling_group sg : sgs) {
+ destroy_scheduling_group(sg).get();
+ }
+ });
+ scheduling_group_key_config key1_conf = make_scheduling_group_key_config<int>();
+ scheduling_group_key key1 = scheduling_group_key_create(key1_conf).get0();
+
+ scheduling_group_key_config key2_conf = make_scheduling_group_key_config<ivec>();
+ scheduling_group_key key2 = scheduling_group_key_create(key2_conf).get0();
+
+ smp::invoke_on_all([key1, key2, &sgs] () {
+ int factor = this_shard_id() + 1;
+ for (int i=0; i < num_scheduling_groups; i++) {
+ sgs[i].get_specific<int>(key1) = (i + 1) * factor;
+ sgs[i].get_specific<ivec>(key2).push_back((i + 1) * factor);
+ }
+
+ for (int i=0; i < num_scheduling_groups; i++) {
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<int>(key1) = (i + 1) * factor, (i + 1) * factor);
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<ivec>(key2)[0], (i + 1) * factor);
+ }
+
+ }).get();
+
+ smp::invoke_on_all([key1, key2] () {
+ return reduce_scheduling_group_specific<int>(std::plus<int>(), int(0), key1).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ }). then([key2] {
+ auto ivec_to_int = [] (ivec& v) {
+ return v.size() ? v[0] : 0;
+ };
+
+ return map_reduce_scheduling_group_specific<ivec>(ivec_to_int, std::plus<int>(), int(0), key2).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ });
+
+ });
+ }).get();
+
+
+}
+
+/**
+ * Test setting primitive and object as a value before all groups are created
+ */
+SEASTAR_THREAD_TEST_CASE(sg_specific_values_define_before_sg_create) {
+ using ivec = std::vector<int>;
+ const int num_scheduling_groups = 4;
+ std::vector<scheduling_group> sgs;
+ const auto destroy_scheduling_groups = defer([&sgs] () {
+ for (scheduling_group sg : sgs) {
+ destroy_scheduling_group(sg).get();
+ }
+ });
+ scheduling_group_key_config key1_conf = make_scheduling_group_key_config<int>();
+ scheduling_group_key key1 = scheduling_group_key_create(key1_conf).get0();
+
+ scheduling_group_key_config key2_conf = make_scheduling_group_key_config<ivec>();
+ scheduling_group_key key2 = scheduling_group_key_create(key2_conf).get0();
+
+ for (int i = 0; i < num_scheduling_groups; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+
+ smp::invoke_on_all([key1, key2, &sgs] () {
+ int factor = this_shard_id() + 1;
+ for (int i=0; i < num_scheduling_groups; i++) {
+ sgs[i].get_specific<int>(key1) = (i + 1) * factor;
+ sgs[i].get_specific<ivec>(key2).push_back((i + 1) * factor);
+ }
+
+ for (int i=0; i < num_scheduling_groups; i++) {
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<int>(key1) = (i + 1) * factor, (i + 1) * factor);
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<ivec>(key2)[0], (i + 1) * factor);
+ }
+
+ }).get();
+
+ smp::invoke_on_all([key1, key2] () {
+ return reduce_scheduling_group_specific<int>(std::plus<int>(), int(0), key1).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ }). then([key2] {
+ auto ivec_to_int = [] (ivec& v) {
+ return v.size() ? v[0] : 0;
+ };
+
+ return map_reduce_scheduling_group_specific<ivec>(ivec_to_int, std::plus<int>(), int(0), key2).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ });
+
+ });
+ }).get();
+
+}
+
+/**
+ * Test setting primitive and an object as a value before some groups are created
+ * and after some of the groups are created.
+ */
+SEASTAR_THREAD_TEST_CASE(sg_specific_values_define_before_and_after_sg_create) {
+ using ivec = std::vector<int>;
+ const int num_scheduling_groups = 4;
+ std::vector<scheduling_group> sgs;
+ const auto destroy_scheduling_groups = defer([&sgs] () {
+ for (scheduling_group sg : sgs) {
+ destroy_scheduling_group(sg).get();
+ }
+ });
+
+ for (int i = 0; i < num_scheduling_groups/2; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+ scheduling_group_key_config key1_conf = make_scheduling_group_key_config<int>();
+ scheduling_group_key key1 = scheduling_group_key_create(key1_conf).get0();
+
+ scheduling_group_key_config key2_conf = make_scheduling_group_key_config<ivec>();
+ scheduling_group_key key2 = scheduling_group_key_create(key2_conf).get0();
+
+ for (int i = num_scheduling_groups/2; i < num_scheduling_groups; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+
+ smp::invoke_on_all([key1, key2, &sgs] () {
+ int factor = this_shard_id() + 1;
+ for (int i=0; i < num_scheduling_groups; i++) {
+ sgs[i].get_specific<int>(key1) = (i + 1) * factor;
+ sgs[i].get_specific<ivec>(key2).push_back((i + 1) * factor);
+ }
+
+ for (int i=0; i < num_scheduling_groups; i++) {
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<int>(key1) = (i + 1) * factor, (i + 1) * factor);
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<ivec>(key2)[0], (i + 1) * factor);
+ }
+
+ }).get();
+
+ smp::invoke_on_all([key1, key2] () {
+ return reduce_scheduling_group_specific<int>(std::plus<int>(), int(0), key1).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ }). then([key2] {
+ auto ivec_to_int = [] (ivec& v) {
+ return v.size() ? v[0] : 0;
+ };
+
+ return map_reduce_scheduling_group_specific<ivec>(ivec_to_int, std::plus<int>(), int(0), key2).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ });
+
+ });
+ }).get();
+}
+
+/*
+ * Test that current scheduling group is inherited by seastar::async()
+ */
+SEASTAR_THREAD_TEST_CASE(sg_scheduling_group_inheritance_in_seastar_async_test) {
+ scheduling_group sg = create_scheduling_group("sg0", 100).get0();
+ thread_attributes attr = {};
+ attr.sched_group = sg;
+ seastar::async(attr, [attr] {
+ BOOST_REQUIRE_EQUAL(internal::scheduling_group_index(current_scheduling_group()),
+ internal::scheduling_group_index(*(attr.sched_group)));
+
+ seastar::async([attr] {
+ BOOST_REQUIRE_EQUAL(internal::scheduling_group_index(current_scheduling_group()),
+ internal::scheduling_group_index(*(attr.sched_group)));
+
+ smp::invoke_on_all([sched_group_idx = internal::scheduling_group_index(*(attr.sched_group))] () {
+ BOOST_REQUIRE_EQUAL(internal::scheduling_group_index(current_scheduling_group()), sched_group_idx);
+ }).get();
+ }).get();
+ }).get();
+}
+
+
+SEASTAR_THREAD_TEST_CASE(later_preserves_sg) {
+ scheduling_group sg = create_scheduling_group("sg", 100).get0();
+ auto cleanup = defer([&] { destroy_scheduling_group(sg).get(); });
+ with_scheduling_group(sg, [&] {
+ return later().then([&] {
+ BOOST_REQUIRE_EQUAL(
+ internal::scheduling_group_index(current_scheduling_group()),
+ internal::scheduling_group_index(sg));
+ });
+ }).get();
+}
diff --git a/src/seastar/tests/unit/semaphore_test.cc b/src/seastar/tests/unit/semaphore_test.cc
new file mode 100644
index 000000000..1f164ed00
--- /dev/null
+++ b/src/seastar/tests/unit/semaphore_test.cc
@@ -0,0 +1,310 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/map_reduce.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/shared_mutex.hh>
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_semaphore_consume) {
+ semaphore sem(0);
+ sem.consume(1);
+ BOOST_REQUIRE_EQUAL(sem.current(), 0u);
+ BOOST_REQUIRE_EQUAL(sem.waiters(), 0u);
+
+ BOOST_REQUIRE_EQUAL(sem.try_wait(0), false);
+ auto fut = sem.wait(1);
+ BOOST_REQUIRE_EQUAL(fut.available(), false);
+ BOOST_REQUIRE_EQUAL(sem.waiters(), 1u);
+ sem.signal(2);
+ BOOST_REQUIRE_EQUAL(sem.waiters(), 0u);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_semaphore_1) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ (void)x.first.wait().then([&x] {
+ x.second++;
+ });
+ x.first.signal();
+ return sleep(10ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 1);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_2) {
+ auto sem = std::make_optional<semaphore>(0);
+ int x = 0;
+ auto fut = sem->wait().then([&x] {
+ x++;
+ });
+ sleep(10ms).get();
+ BOOST_REQUIRE_EQUAL(x, 0);
+ sem = std::nullopt;
+ BOOST_CHECK_THROW(fut.get(), broken_promise);
+}
+
+SEASTAR_TEST_CASE(test_semaphore_timeout_1) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ (void)x.first.wait(100ms).then([&x] {
+ x.second++;
+ });
+ (void)sleep(3ms).then([&x] {
+ x.first.signal();
+ });
+ return sleep(200ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 1);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_timeout_2) {
+ auto sem = semaphore(0);
+ int x = 0;
+ auto fut1 = sem.wait(3ms).then([&x] {
+ x++;
+ });
+ bool signaled = false;
+ auto fut2 = sleep(100ms).then([&sem, &signaled] {
+ signaled = true;
+ sem.signal();
+ });
+ sleep(200ms).get();
+ fut2.get();
+ BOOST_REQUIRE_EQUAL(signaled, true);
+ BOOST_CHECK_THROW(fut1.get(), semaphore_timed_out);
+ BOOST_REQUIRE_EQUAL(x, 0);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_mix_1) {
+ auto sem = semaphore(0);
+ int x = 0;
+ auto fut1 = sem.wait(30ms).then([&x] {
+ x++;
+ });
+ auto fut2 = sem.wait().then([&x] {
+ x += 10;
+ });
+ auto fut3 = sleep(100ms).then([&sem] {
+ sem.signal();
+ });
+ sleep(200ms).get();
+ fut3.get();
+ fut2.get();
+ BOOST_CHECK_THROW(fut1.get(), semaphore_timed_out);
+ BOOST_REQUIRE_EQUAL(x, 10);
+}
+
+SEASTAR_TEST_CASE(test_broken_semaphore) {
+ auto sem = make_lw_shared<semaphore>(0);
+ struct oops {};
+ auto check_result = [sem] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("expecting exception");
+ } catch (oops& x) {
+ // ok
+ return make_ready_future<>();
+ } catch (...) {
+ BOOST_FAIL("wrong exception seen");
+ }
+ BOOST_FAIL("unreachable");
+ return make_ready_future<>();
+ };
+ auto ret = sem->wait().then_wrapped(check_result);
+ sem->broken(oops());
+ return sem->wait().then_wrapped(check_result).then([ret = std::move(ret)] () mutable {
+ return std::move(ret);
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_exclusive) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ return parallel_for_each(boost::irange(0, 10), [&sm, &counter] (int idx) {
+ return with_lock(sm, [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_shared) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ auto running_in_parallel = [&sm, &counter] (int instance) {
+ return with_shared(sm, [&counter] {
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ bool was_parallel = counter != 0;
+ --counter;
+ return was_parallel;
+ });
+ });
+ };
+ return map_reduce(boost::irange(0, 100), running_in_parallel, false, std::bit_or<bool>()).then([&counter] (bool result) {
+ BOOST_REQUIRE_EQUAL(result, true);
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_mixed) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ auto running_in_parallel = [&sm, &counter] (int instance) {
+ return with_shared(sm, [&counter] {
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ bool was_parallel = counter != 0;
+ --counter;
+ return was_parallel;
+ });
+ });
+ };
+ auto running_alone = [&sm, &counter] (int instance) {
+ return with_lock(sm, [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ return true;
+ });
+ });
+ };
+ auto run = [running_in_parallel, running_alone] (int instance) {
+ if (instance % 9 == 0) {
+ return running_alone(instance);
+ } else {
+ return running_in_parallel(instance);
+ }
+ };
+ return map_reduce(boost::irange(0, 100), run, false, std::bit_or<bool>()).then([&counter] (bool result) {
+ BOOST_REQUIRE_EQUAL(result, true);
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_with_semaphore) {
+ return do_with(semaphore(1), 0, [] (semaphore& sem, int& counter) {
+ return with_semaphore(sem, 1, [&counter] {
+ ++counter;
+ }).then([&counter, &sem] () {
+ return with_semaphore(sem, 1, [&counter] {
+ ++counter;
+ throw 123;
+ }).then_wrapped([&counter] (auto&& fut) {
+ BOOST_REQUIRE_EQUAL(counter, 2);
+ BOOST_REQUIRE(fut.failed());
+ fut.ignore_ready_future();
+ });
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_units_splitting) {
+ auto sm = semaphore(2);
+ auto units = get_units(sm, 2, 1min).get0();
+ {
+ BOOST_REQUIRE_EQUAL(units.count(), 2);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ auto split = units.split(1);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ }
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ units.~semaphore_units();
+ units = get_units(sm, 2, 1min).get0();
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ BOOST_REQUIRE_THROW(units.split(10), std::invalid_argument);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_units_return) {
+ auto sm = semaphore(3);
+ auto units = get_units(sm, 3, 1min).get0();
+ BOOST_REQUIRE_EQUAL(units.count(), 3);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ BOOST_REQUIRE_EQUAL(units.return_units(1), 2);
+ BOOST_REQUIRE_EQUAL(units.count(), 2);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ units.~semaphore_units();
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 3);
+
+ units = get_units(sm, 2, 1min).get0();
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ BOOST_REQUIRE_THROW(units.return_units(10), std::invalid_argument);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ units.return_all();
+ BOOST_REQUIRE_EQUAL(units.count(), 0);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 3);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_named_semaphore_error) {
+ auto sem = make_lw_shared<named_semaphore>(0, named_semaphore_exception_factory{"name_of_the_semaphore"});
+ auto check_result = [sem] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("Expecting an exception");
+ } catch (broken_named_semaphore& ex) {
+ BOOST_REQUIRE_NE(std::string(ex.what()).find("name_of_the_semaphore"), std::string::npos);
+ } catch (...) {
+ BOOST_FAIL("Expected an instance of broken_named_semaphore with proper semaphore name");
+ }
+ return make_ready_future<>();
+ };
+ auto ret = sem->wait().then_wrapped(check_result);
+ sem->broken();
+ sem->wait().then_wrapped(check_result).then([ret = std::move(ret)] () mutable {
+ return std::move(ret);
+ }).get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_named_semaphore_timeout) {
+ auto sem = make_lw_shared<named_semaphore>(0, named_semaphore_exception_factory{"name_of_the_semaphore"});
+
+ auto f = sem->wait(named_semaphore::clock::now() + 1ms, 1);
+ try {
+ f.get();
+ BOOST_FAIL("Expecting an exception");
+ } catch (named_semaphore_timed_out& ex) {
+ BOOST_REQUIRE_NE(std::string(ex.what()).find("name_of_the_semaphore"), std::string::npos);
+ } catch (...) {
+ BOOST_FAIL("Expected an instance of named_semaphore_timed_out with proper semaphore name");
+ }
+}
diff --git a/src/seastar/tests/unit/sharded_test.cc b/src/seastar/tests/unit/sharded_test.cc
new file mode 100644
index 000000000..f243d7fc0
--- /dev/null
+++ b/src/seastar/tests/unit/sharded_test.cc
@@ -0,0 +1,110 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 Scylladb, Ltd.
+ */
+
+#include <seastar/testing/thread_test_case.hh>
+
+#include <seastar/core/sharded.hh>
+
+using namespace seastar;
+
+namespace {
+class invoke_on_during_stop final : public peering_sharded_service<invoke_on_during_stop> {
+ bool flag = false;
+
+public:
+ future<> stop() {
+ return container().invoke_on(0, [] (invoke_on_during_stop& instance) {
+ instance.flag = true;
+ });
+ }
+
+ ~invoke_on_during_stop() {
+ if (this_shard_id() == 0) {
+ assert(flag);
+ }
+ }
+};
+}
+
+SEASTAR_THREAD_TEST_CASE(invoke_on_during_stop_test) {
+ sharded<invoke_on_during_stop> s;
+ s.start().get();
+ s.stop().get();
+}
+
+class mydata {
+public:
+ int x = 1;
+ future<> stop() {
+ return make_ready_future<>();
+ }
+};
+
+SEASTAR_THREAD_TEST_CASE(invoke_map_returns_non_future_value) {
+ seastar::sharded<mydata> s;
+ s.start().get();
+ s.map([] (mydata& m) {
+ return m.x;
+ }).then([] (std::vector<int> results) {
+ for (auto& x : results) {
+ assert(x == 1);
+ }
+ }).get();
+ s.stop().get();
+};
+
+SEASTAR_THREAD_TEST_CASE(invoke_map_returns_future_value) {
+ seastar::sharded<mydata> s;
+ s.start().get();
+ s.map([] (mydata& m) {
+ return make_ready_future<int>(m.x);
+ }).then([] (std::vector<int> results) {
+ for (auto& x : results) {
+ assert(x == 1);
+ }
+ }).get();
+ s.stop().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(invoke_map_returns_future_value_from_thread) {
+ seastar::sharded<mydata> s;
+ s.start().get();
+ s.map([] (mydata& m) {
+ return seastar::async([&m] {
+ return m.x;
+ });
+ }).then([] (std::vector<int> results) {
+ for (auto& x : results) {
+ assert(x == 1);
+ }
+ }).get();
+ s.stop().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(failed_sharded_start_doesnt_hang) {
+ class fail_to_start {
+ public:
+ fail_to_start() { throw 0; }
+ };
+
+ seastar::sharded<fail_to_start> s;
+ s.start().then_wrapped([] (auto&& fut) { fut.ignore_ready_future(); }).get();
+}
diff --git a/src/seastar/tests/unit/shared_ptr_test.cc b/src/seastar/tests/unit/shared_ptr_test.cc
new file mode 100644
index 000000000..3ee4b6589
--- /dev/null
+++ b/src/seastar/tests/unit/shared_ptr_test.cc
@@ -0,0 +1,204 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright 2015 Cloudius Systems
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <set>
+#include <unordered_map>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/shared_ptr.hh>
+
+using namespace seastar;
+
+struct expected_exception : public std::exception {};
+
+struct A {
+ static bool destroyed;
+ A() {
+ destroyed = false;
+ }
+ virtual ~A() {
+ destroyed = true;
+ }
+};
+
+struct A_esft : public A, public enable_lw_shared_from_this<A_esft> {
+};
+
+struct B {
+ virtual void x() {}
+};
+
+bool A::destroyed = false;
+
+BOOST_AUTO_TEST_CASE(explot_dynamic_cast_use_after_free_problem) {
+ shared_ptr<A> p = ::make_shared<A>();
+ {
+ auto p2 = dynamic_pointer_cast<B>(p);
+ BOOST_ASSERT(!p2);
+ }
+ BOOST_ASSERT(!A::destroyed);
+}
+
+class C : public enable_shared_from_this<C> {
+public:
+ shared_ptr<C> dup() { return shared_from_this(); }
+ shared_ptr<const C> get() const { return shared_from_this(); }
+};
+
+BOOST_AUTO_TEST_CASE(test_const_ptr) {
+ shared_ptr<C> a = make_shared<C>();
+ shared_ptr<const C> ca = a;
+ BOOST_REQUIRE(ca == a);
+ shared_ptr<const C> cca = ca->get();
+ BOOST_REQUIRE(cca == ca);
+}
+
+struct D {};
+
+BOOST_AUTO_TEST_CASE(test_lw_const_ptr_1) {
+ auto pd1 = make_lw_shared<const D>(D());
+ auto pd2 = make_lw_shared(D());
+ lw_shared_ptr<const D> pd3 = pd2;
+ BOOST_REQUIRE(pd2 == pd3);
+}
+
+struct E : enable_lw_shared_from_this<E> {};
+
+BOOST_AUTO_TEST_CASE(test_lw_const_ptr_2) {
+ auto pe1 = make_lw_shared<const E>();
+ auto pe2 = make_lw_shared<E>();
+ lw_shared_ptr<const E> pe3 = pe2;
+ BOOST_REQUIRE(pe2 == pe3);
+}
+
+struct F : enable_lw_shared_from_this<F> {
+ auto const_method() const {
+ return shared_from_this();
+ }
+};
+
+BOOST_AUTO_TEST_CASE(test_shared_from_this_called_on_const_object) {
+ auto ptr = make_lw_shared<F>();
+ ptr->const_method();
+}
+
+BOOST_AUTO_TEST_CASE(test_exception_thrown_from_constructor_is_propagated) {
+ struct X {
+ X() {
+ throw expected_exception();
+ }
+ };
+ try {
+ auto ptr = make_lw_shared<X>();
+ BOOST_FAIL("Constructor should have thrown");
+ } catch (const expected_exception& e) {
+ BOOST_TEST_MESSAGE("Expected exception caught");
+ }
+ try {
+ auto ptr = ::make_shared<X>();
+ BOOST_FAIL("Constructor should have thrown");
+ } catch (const expected_exception& e) {
+ BOOST_TEST_MESSAGE("Expected exception caught");
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_indirect_functors) {
+ {
+ std::multiset<shared_ptr<sstring>, indirect_less<shared_ptr<sstring>>> a_set;
+
+ a_set.insert(make_shared<sstring>("k3"));
+ a_set.insert(make_shared<sstring>("k1"));
+ a_set.insert(make_shared<sstring>("k2"));
+ a_set.insert(make_shared<sstring>("k4"));
+ a_set.insert(make_shared<sstring>("k0"));
+
+
+ auto i = a_set.begin();
+ BOOST_REQUIRE_EQUAL(sstring("k0"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k1"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k2"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k3"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k4"), *(*i++));
+ }
+
+ {
+ std::unordered_map<shared_ptr<sstring>, bool,
+ indirect_hash<shared_ptr<sstring>>, indirect_equal_to<shared_ptr<sstring>>> a_map;
+
+ a_map.emplace(make_shared<sstring>("k3"), true);
+ a_map.emplace(make_shared<sstring>("k1"), true);
+ a_map.emplace(make_shared<sstring>("k2"), true);
+ a_map.emplace(make_shared<sstring>("k4"), true);
+ a_map.emplace(make_shared<sstring>("k0"), true);
+
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k0")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k1")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k2")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k3")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k4")));
+ BOOST_REQUIRE(!a_map.count(make_shared<sstring>("k5")));
+ }
+}
+
+template<typename T>
+void do_test_release() {
+ auto ptr = make_lw_shared<T>();
+ BOOST_REQUIRE(!T::destroyed);
+
+ auto ptr2 = ptr;
+
+ BOOST_REQUIRE(!ptr.release());
+ BOOST_REQUIRE(!ptr);
+ BOOST_REQUIRE(ptr2.use_count() == 1);
+
+ auto uptr2 = ptr2.release();
+ BOOST_REQUIRE(uptr2);
+ BOOST_REQUIRE(!ptr2);
+ ptr2 = {};
+
+ BOOST_REQUIRE(!T::destroyed);
+ uptr2 = {};
+
+ BOOST_REQUIRE(T::destroyed);
+
+ // Check destroying via disposer
+ auto ptr3 = make_lw_shared<T>();
+ auto uptr3 = ptr3.release();
+ BOOST_REQUIRE(uptr3);
+ BOOST_REQUIRE(!T::destroyed);
+
+ auto raw_ptr3 = uptr3.release();
+ lw_shared_ptr<T>::dispose(raw_ptr3);
+ BOOST_REQUIRE(T::destroyed);
+}
+
+BOOST_AUTO_TEST_CASE(test_release) {
+ do_test_release<A>();
+ do_test_release<A_esft>();
+}
+
+BOOST_AUTO_TEST_CASE(test_const_release) {
+ do_test_release<const A>();
+ do_test_release<const A_esft>();
+}
diff --git a/src/seastar/tests/unit/signal_test.cc b/src/seastar/tests/unit/signal_test.cc
new file mode 100755
index 000000000..080de068f
--- /dev/null
+++ b/src/seastar/tests/unit/signal_test.cc
@@ -0,0 +1,48 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+
+using namespace seastar;
+
+extern "C" {
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+}
+
+SEASTAR_TEST_CASE(test_sighup) {
+ return do_with(make_lw_shared<promise<>>(), false, [](auto const& p, bool& signaled) {
+ engine().handle_signal(SIGHUP, [p, &signaled] {
+ signaled = true;
+ p->set_value();
+ });
+
+ kill(getpid(), SIGHUP);
+
+ return p->get_future().then([&] {
+ BOOST_REQUIRE_EQUAL(signaled, true);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/simple_stream_test.cc b/src/seastar/tests/unit/simple_stream_test.cc
new file mode 100644
index 000000000..32e0bf2d4
--- /dev/null
+++ b/src/seastar/tests/unit/simple_stream_test.cc
@@ -0,0 +1,99 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 ScyllaDB Ltd.
+ */
+
+#define BOOST_TEST_MODULE simple_stream
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/simple-stream.hh>
+
+using namespace seastar;
+
+template<typename Input, typename Output>
+static void write_read_test(Input in, Output out)
+{
+ auto aa = std::vector<char>(4, 'a');
+ auto bb = std::vector<char>(3, 'b');
+ auto cc = std::vector<char>(2, 'c');
+
+ out.write(aa.data(), aa.size());
+ out.fill('b', 3);
+
+ BOOST_CHECK_THROW(out.fill(' ', 3), std::out_of_range);
+ BOOST_CHECK_THROW(out.write(" ", 3), std::out_of_range);
+
+ out.write(cc.data(), cc.size());
+
+ BOOST_CHECK_THROW(out.fill(' ', 1), std::out_of_range);
+ BOOST_CHECK_THROW(out.write(" ", 1), std::out_of_range);
+
+ auto actual_aa = std::vector<char>(4);
+ in.read(actual_aa.data(), actual_aa.size());
+ BOOST_CHECK_EQUAL(aa, actual_aa);
+
+ auto actual_bb = std::vector<char>(3);
+ in.read(actual_bb.data(), actual_bb.size());
+ BOOST_CHECK_EQUAL(bb, actual_bb);
+
+ actual_aa.resize(1024);
+ BOOST_CHECK_THROW(in.read(actual_aa.data(), actual_aa.size()), std::out_of_range);
+
+ auto actual_cc = std::vector<char>(2);
+ in.read(actual_cc.data(), actual_cc.size());
+ BOOST_CHECK_EQUAL(cc, actual_cc);
+
+ BOOST_CHECK_THROW(in.read(actual_aa.data(), 1), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_CASE(simple_write_read_test) {
+ auto buf = temporary_buffer<char>(9);
+
+ write_read_test(simple_memory_input_stream(buf.get(), buf.size()),
+ simple_memory_output_stream(buf.get_write(), buf.size()));
+
+ std::fill_n(buf.get_write(), buf.size(), 'x');
+
+ auto out = simple_memory_output_stream(buf.get_write(), buf.size());
+ write_read_test(out.to_input_stream(), out);
+}
+
+BOOST_AUTO_TEST_CASE(fragmented_write_read_test) {
+ static constexpr size_t total_size = 9;
+
+ auto bufs = std::vector<temporary_buffer<char>>();
+ using iterator_type = std::vector<temporary_buffer<char>>::iterator;
+
+ auto test = [&] {
+ write_read_test(fragmented_memory_input_stream<iterator_type>(bufs.begin(), total_size),
+ fragmented_memory_output_stream<iterator_type>(bufs.begin(), total_size));
+
+ auto out = fragmented_memory_output_stream<iterator_type>(bufs.begin(), total_size);
+ write_read_test(out.to_input_stream(), out);
+ };
+
+ bufs.emplace_back(total_size);
+ test();
+
+ bufs.clear();
+ for (auto i = 0u; i < total_size; i++) {
+ bufs.emplace_back(1);
+ }
+ test();
+}
diff --git a/src/seastar/tests/unit/slab_test.cc b/src/seastar/tests/unit/slab_test.cc
new file mode 100644
index 000000000..7588dd79d
--- /dev/null
+++ b/src/seastar/tests/unit/slab_test.cc
@@ -0,0 +1,127 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ *
+ * To compile: g++ -std=c++14 slab_test.cc
+ */
+
+#include <iostream>
+#include <assert.h>
+#include <seastar/core/slab.hh>
+
+using namespace seastar;
+
+namespace bi = boost::intrusive;
+
+static constexpr size_t max_object_size = 1024*1024;
+
+class item : public slab_item_base {
+public:
+ bi::list_member_hook<> _cache_link;
+ uint32_t _slab_page_index;
+
+ item(uint32_t slab_page_index) : _slab_page_index(slab_page_index) {}
+
+ const uint32_t get_slab_page_index() {
+ return _slab_page_index;
+ }
+ const bool is_unlocked() {
+ return true;
+ }
+};
+
+template<typename Item>
+static void free_vector(slab_allocator<Item>& slab, std::vector<item *>& items) {
+ for (auto item : items) {
+ slab.free(item);
+ }
+}
+
+static void test_allocation_1(const double growth_factor, const unsigned slab_limit_size) {
+ slab_allocator<item> slab(growth_factor, slab_limit_size, max_object_size);
+ size_t size = max_object_size;
+
+ slab.print_slab_classes();
+
+ std::vector<item *> items;
+
+ assert(slab_limit_size % size == 0);
+ for (auto i = 0u; i < (slab_limit_size / size); i++) {
+ auto item = slab.create(size);
+ items.push_back(item);
+ }
+ assert(slab.create(size) == nullptr);
+
+ free_vector<item>(slab, items);
+ std::cout << __FUNCTION__ << " done!\n";
+}
+
+static void test_allocation_2(const double growth_factor, const unsigned slab_limit_size) {
+ slab_allocator<item> slab(growth_factor, slab_limit_size, max_object_size);
+ size_t size = 1024;
+
+ std::vector<item *> items;
+
+ auto allocations = 0u;
+ for (;;) {
+ auto item = slab.create(size);
+ if (!item) {
+ break;
+ }
+ items.push_back(item);
+ allocations++;
+ }
+
+ auto class_size = slab.class_size(size);
+ auto per_slab_page = max_object_size / class_size;
+ auto available_slab_pages = slab_limit_size / max_object_size;
+ assert(allocations == (per_slab_page * available_slab_pages));
+
+ free_vector<item>(slab, items);
+ std::cout << __FUNCTION__ << " done!\n";
+}
+
+static void test_allocation_with_lru(const double growth_factor, const unsigned slab_limit_size) {
+ bi::list<item, bi::member_hook<item, bi::list_member_hook<>, &item::_cache_link>> _cache;
+ unsigned evictions = 0;
+
+ slab_allocator<item> slab(growth_factor, slab_limit_size, max_object_size,
+ [&](item& item_ref) { _cache.erase(_cache.iterator_to(item_ref)); evictions++; });
+ size_t size = max_object_size;
+
+ auto max = slab_limit_size / max_object_size;
+ for (auto i = 0u; i < max * 1000; i++) {
+ auto item = slab.create(size);
+ assert(item != nullptr);
+ _cache.push_front(*item);
+ }
+ assert(evictions == max * 999);
+
+ _cache.clear();
+
+ std::cout << __FUNCTION__ << " done!\n";
+}
+
+int main(int ac, char** av) {
+ test_allocation_1(1.25, 5*1024*1024);
+ test_allocation_2(1.07, 5*1024*1024); // 1.07 is the growth factor used by facebook.
+ test_allocation_with_lru(1.25, 5*1024*1024);
+
+ return 0;
+}
diff --git a/src/seastar/tests/unit/smp_test.cc b/src/seastar/tests/unit/smp_test.cc
new file mode 100644
index 000000000..67967674d
--- /dev/null
+++ b/src/seastar/tests/unit/smp_test.cc
@@ -0,0 +1,81 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/print.hh>
+
+using namespace seastar;
+
+future<bool> test_smp_call() {
+ return smp::submit_to(1, [] {
+ return make_ready_future<int>(3);
+ }).then([] (int ret) {
+ return make_ready_future<bool>(ret == 3);
+ });
+}
+
+struct nasty_exception {};
+
+future<bool> test_smp_exception() {
+ fmt::print("1\n");
+ return smp::submit_to(1, [] {
+ fmt::print("2\n");
+ auto x = make_exception_future<int>(nasty_exception());
+ fmt::print("3\n");
+ return x;
+ }).then_wrapped([] (future<int> result) {
+ fmt::print("4\n");
+ try {
+ result.get();
+ return make_ready_future<bool>(false); // expected an exception
+ } catch (nasty_exception&) {
+ // all is well
+ return make_ready_future<bool>(true);
+ } catch (...) {
+ // incorrect exception type
+ return make_ready_future<bool>(false);
+ }
+ });
+}
+
+int tests, fails;
+
+future<>
+report(sstring msg, future<bool>&& result) {
+ return std::move(result).then([msg] (bool result) {
+ fmt::print("{}: {}\n", (result ? "PASS" : "FAIL"), msg);
+ tests += 1;
+ fails += !result;
+ });
+}
+
+int main(int ac, char** av) {
+ return app_template().run_deprecated(ac, av, [] {
+ return report("smp call", test_smp_call()).then([] {
+ return report("smp exception", test_smp_exception());
+ }).then([] {
+ fmt::print("\n{:d} tests / {:d} failures\n", tests, fails);
+ engine().exit(fails ? 1 : 0);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/socket_test.cc b/src/seastar/tests/unit/socket_test.cc
new file mode 100644
index 000000000..b4640bbfc
--- /dev/null
+++ b/src/seastar/tests/unit/socket_test.cc
@@ -0,0 +1,82 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Elazar Leibovich
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/memory.hh>
+#include <seastar/util/std-compat.hh>
+
+#include <seastar/net/posix-stack.hh>
+
+using namespace seastar;
+
+future<> handle_connection(connected_socket s) {
+ auto in = s.input();
+ auto out = s.output();
+ return do_with(std::move(in), std::move(out), [](auto& in, auto& out) {
+ return do_until([&in]() { return in.eof(); },
+ [&in, &out] {
+ return in.read().then([&out](auto buf) {
+ return out.write(std::move(buf)).then([&out]() { return out.close(); });
+ });
+ });
+ });
+}
+
+future<> echo_server_loop() {
+ return do_with(
+ server_socket(listen(make_ipv4_address({1234}), listen_options{.reuse_address = true})), [](auto& listener) {
+ // Connect asynchronously in background.
+ (void)connect(make_ipv4_address({"127.0.0.1", 1234})).then([](connected_socket&& socket) {
+ socket.shutdown_output();
+ });
+ return listener.accept().then(
+ [](accept_result ar) {
+ connected_socket s = std::move(ar.connection);
+ return handle_connection(std::move(s));
+ }).then([l = std::move(listener)]() mutable { return l.abort_accept(); });
+ });
+}
+
+class my_malloc_allocator : public std::pmr::memory_resource {
+public:
+ int allocs;
+ int frees;
+ void* do_allocate(std::size_t bytes, std::size_t alignment) override { allocs++; return malloc(bytes); }
+ void do_deallocate(void *ptr, std::size_t bytes, std::size_t alignment) override { frees++; return free(ptr); }
+ virtual bool do_is_equal(const std::pmr::memory_resource& __other) const noexcept override { abort(); }
+};
+
+my_malloc_allocator malloc_allocator;
+std::pmr::polymorphic_allocator<char> allocator{&malloc_allocator};
+
+int main(int ac, char** av) {
+ register_network_stack("posix", boost::program_options::options_description(),
+ [](boost::program_options::variables_map ops) {
+ return smp::main_thread() ? net::posix_network_stack::create(ops, &allocator)
+ : net::posix_ap_network_stack::create(ops);
+ }, true);
+ return app_template().run_deprecated(ac, av, [] {
+ return echo_server_loop().finally([](){ engine().exit((malloc_allocator.allocs == malloc_allocator.frees) ? 0 : 1); });
+ });
+}
diff --git a/src/seastar/tests/unit/sstring_test.cc b/src/seastar/tests/unit/sstring_test.cc
new file mode 100644
index 000000000..c93632683
--- /dev/null
+++ b/src/seastar/tests/unit/sstring_test.cc
@@ -0,0 +1,186 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2014 Cloudius Systems
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/sstring.hh>
+#include <list>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(test_make_sstring) {
+ std::string_view foo = "foo";
+ std::string bar = "bar";
+ sstring zed = "zed";
+ const char* baz = "baz";
+ BOOST_REQUIRE_EQUAL(make_sstring(foo, bar, zed, baz, "bah"), sstring("foobarzedbazbah"));
+}
+
+BOOST_AUTO_TEST_CASE(test_construction) {
+ BOOST_REQUIRE_EQUAL(sstring(std::string_view("abc")), sstring("abc"));
+}
+
+BOOST_AUTO_TEST_CASE(test_equality) {
+ BOOST_REQUIRE_EQUAL(sstring("aaa"), sstring("aaa"));
+}
+
+BOOST_AUTO_TEST_CASE(test_to_sstring) {
+ BOOST_REQUIRE_EQUAL(to_sstring(1234567), sstring("1234567"));
+}
+
+BOOST_AUTO_TEST_CASE(test_add_literal_to_sstring) {
+ BOOST_REQUIRE_EQUAL("x" + sstring("y"), sstring("xy"));
+}
+
+BOOST_AUTO_TEST_CASE(test_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find('b'), 1u);
+ BOOST_REQUIRE_EQUAL(sstring("babcde").find('b',1), 2u);
+}
+
+BOOST_AUTO_TEST_CASE(test_not_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find('x'), sstring::npos);
+}
+
+BOOST_AUTO_TEST_CASE(test_str_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find("bc"), 1u);
+ BOOST_REQUIRE_EQUAL(sstring("abcbcde").find("bc", 2), 3u);
+}
+
+BOOST_AUTO_TEST_CASE(test_str_not_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find("x"), sstring::npos);
+}
+
+BOOST_AUTO_TEST_CASE(test_substr_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").substr(1,2), "bc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(1,2), "bc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(1,3), "bc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(0, 2), "ab");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(3, 2), "");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(1), "bc");
+}
+
+BOOST_AUTO_TEST_CASE(test_substr_eor_sstring) {
+ BOOST_REQUIRE_THROW(sstring("abcde").substr(6,1), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_CASE(test_at_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").at(1), 'b');
+ BOOST_REQUIRE_THROW(sstring("abcde").at(6), std::out_of_range);
+ sstring s("abcde");
+ s.at(1) = 'd';
+ BOOST_REQUIRE_EQUAL(s, "adcde");
+}
+
+BOOST_AUTO_TEST_CASE(test_find_last_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a'), 4u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',5), 4u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',4), 4u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',3), 2u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('x'), sstring::npos);
+ BOOST_REQUIRE_EQUAL(sstring("").find_last_of('a'), sstring::npos);
+}
+
+
+BOOST_AUTO_TEST_CASE(test_append) {
+ BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 3), "aba123");
+ BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 4), "aba1234");
+ BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 0), "aba");
+}
+
+BOOST_AUTO_TEST_CASE(test_replace) {
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(1,1, "xyz", 1), "axc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(3,2, "xyz", 2), "abcxy");
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(2,2, "xyz", 2), "abxy");
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(0,2, "", 0), "c");
+ BOOST_REQUIRE_THROW(sstring("abc").replace(4,1, "xyz", 1), std::out_of_range);
+ const char* s = "xyz";
+ sstring str("abcdef");
+ BOOST_REQUIRE_EQUAL(str.replace(str.begin() + 1 , str.begin() + 3, s + 1, s + 3), "ayzdef");
+ BOOST_REQUIRE_THROW(sstring("abc").replace(4,1, "xyz", 1), std::out_of_range);
+
+}
+
+BOOST_AUTO_TEST_CASE(test_insert) {
+ sstring str("abc");
+ const char* s = "xyz";
+ str.insert(str.begin() +1, s + 1, s + 2);
+ BOOST_REQUIRE_EQUAL(str, "aybc");
+ str = "abc";
+ BOOST_REQUIRE_THROW(str.insert(str.begin() + 5, s + 1, s + 2), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_CASE(test_erase) {
+ sstring str("abcdef");
+ auto i = str.erase(str.begin() + 1, str.begin() + 3);
+ BOOST_REQUIRE_EQUAL(*i, 'd');
+ BOOST_REQUIRE_EQUAL(str, "adef");
+}
+
+BOOST_AUTO_TEST_CASE(test_ctor_iterator) {
+ std::list<char> data{{'a', 'b', 'c'}};
+ sstring s(data.begin(), data.end());
+ BOOST_REQUIRE_EQUAL(s, "abc");
+}
+
+BOOST_AUTO_TEST_CASE(test_nul_termination) {
+ using stype = basic_sstring<char, uint32_t, 15, true>;
+
+ for (int size = 1; size <= 32; size *= 2) {
+ auto s1 = uninitialized_string<stype>(size - 1);
+ BOOST_REQUIRE_EQUAL(s1.c_str()[size - 1], '\0');
+ auto s2 = uninitialized_string<stype>(size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[size], '\0');
+
+ s1 = stype("01234567890123456789012345678901", size - 1);
+ BOOST_REQUIRE_EQUAL(s1.c_str()[size - 1], '\0');
+ s2 = stype("01234567890123456789012345678901", size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[size], '\0');
+
+ s1 = stype(size - 1, ' ');
+ BOOST_REQUIRE_EQUAL(s1.c_str()[size - 1], '\0');
+ s2 = stype(size, ' ');
+ BOOST_REQUIRE_EQUAL(s2.c_str()[size], '\0');
+
+ s2 = s1;
+ BOOST_REQUIRE_EQUAL(s2.c_str()[s1.size()], '\0');
+ s2.resize(s1.size());
+ BOOST_REQUIRE_EQUAL(s2.c_str()[s1.size()], '\0');
+ BOOST_REQUIRE_EQUAL(s1, s2);
+
+ auto new_size = size / 2;
+ s2 = s1;
+ s2.resize(new_size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[new_size], '\0');
+ BOOST_REQUIRE(!strncmp(s1.c_str(), s2.c_str(), new_size));
+
+ new_size = size * 2;
+ s2 = s1;
+ s2.resize(new_size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[new_size], '\0');
+ BOOST_REQUIRE(!strncmp(s1.c_str(), s2.c_str(), std::min(s1.size(), s2.size())));
+
+ new_size = size * 2;
+ s2 = s1 + s1;
+ BOOST_REQUIRE_EQUAL(s2.c_str()[s2.size()], '\0');
+ BOOST_REQUIRE(!strncmp(s1.c_str(), s2.c_str(), std::min(s1.size(), s2.size())));
+ }
+}
diff --git a/src/seastar/tests/unit/stall_detector_test.cc b/src/seastar/tests/unit/stall_detector_test.cc
new file mode 100644
index 000000000..f9e4ba7a0
--- /dev/null
+++ b/src/seastar/tests/unit/stall_detector_test.cc
@@ -0,0 +1,108 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 ScyllaDB Ltd.
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread_cputime_clock.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/later.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <../../src/core/stall_detector.hh>
+#include <atomic>
+#include <chrono>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+class temporary_stall_detector_settings {
+ std::chrono::milliseconds _old_threshold;
+ std::function<void ()> _old_report;
+public:
+ temporary_stall_detector_settings(std::chrono::duration<double> threshold, std::function<void ()> report)
+ : _old_threshold(engine().get_blocked_reactor_notify_ms())
+ , _old_report(engine().get_stall_detector_report_function()) {
+ engine().update_blocked_reactor_notify_ms(std::chrono::duration_cast<std::chrono::milliseconds>(threshold));
+ engine().set_stall_detector_report_function(std::move(report));
+ }
+ ~temporary_stall_detector_settings() {
+ engine().update_blocked_reactor_notify_ms(_old_threshold);
+ engine().set_stall_detector_report_function(std::move(_old_report));
+ }
+};
+
+void spin(std::chrono::duration<double> how_much) {
+ auto end = internal::cpu_stall_detector::clock_type::now() + how_much;
+ while (internal::cpu_stall_detector::clock_type::now() < end) {
+ // spin!
+ }
+}
+
+void spin_some_cooperatively(std::chrono::duration<double> how_much) {
+ auto end = std::chrono::steady_clock::now() + how_much;
+ while (std::chrono::steady_clock::now() < end) {
+ spin(200us);
+ if (need_preempt()) {
+ thread::yield();
+ }
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(normal_case) {
+ std::atomic<unsigned> reports{};
+ temporary_stall_detector_settings tsds(10ms, [&] { ++reports; });
+ spin_some_cooperatively(1s);
+ BOOST_REQUIRE_EQUAL(reports, 0);
+}
+
+SEASTAR_THREAD_TEST_CASE(simple_stalls) {
+ std::atomic<unsigned> reports{};
+ temporary_stall_detector_settings tsds(10ms, [&] { ++reports; });
+ unsigned nr = 10;
+ for (unsigned i = 0; i < nr; ++i) {
+ spin_some_cooperatively(100ms);
+ spin(20ms);
+ }
+ spin_some_cooperatively(100ms);
+
+ // blocked-reactor-reports-per-minute defaults to 5, so we don't
+ // get all 10 reports.
+ BOOST_REQUIRE_EQUAL(reports, 5);
+}
+
+SEASTAR_THREAD_TEST_CASE(no_poll_no_stall) {
+ std::atomic<unsigned> reports{};
+ temporary_stall_detector_settings tsds(10ms, [&] { ++reports; });
+ spin_some_cooperatively(1ms); // need to yield so that stall detector change from above take effect
+ static constexpr unsigned tasks = 2000;
+ promise<> p;
+ auto f = p.get_future();
+ parallel_for_each(boost::irange(0u, tasks), [&p] (unsigned int i) {
+ (void)later().then([i, &p] {
+ spin(500us);
+ if (i == tasks - 1) {
+ p.set_value();
+ }
+ });
+ return make_ready_future<>();
+ }).get();
+ f.get();
+ BOOST_REQUIRE_EQUAL(reports, 0);
+}
diff --git a/src/seastar/tests/unit/thread_context_switch_test.cc b/src/seastar/tests/unit/thread_context_switch_test.cc
new file mode 100644
index 000000000..28cefd4e9
--- /dev/null
+++ b/src/seastar/tests/unit/thread_context_switch_test.cc
@@ -0,0 +1,96 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/distributed.hh>
+#include <seastar/core/sleep.hh>
+#include <fmt/printf.h>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+class context_switch_tester {
+ uint64_t _switches{0};
+ semaphore _s1{0};
+ semaphore _s2{0};
+ bool _done1{false};
+ bool _done2{false};
+ thread _t1{[this] { main1(); }};
+ thread _t2{[this] { main2(); }};
+private:
+ void main1() {
+ while (!_done1) {
+ _s1.wait().get();
+ ++_switches;
+ _s2.signal();
+ }
+ _done2 = true;
+ }
+ void main2() {
+ while (!_done2) {
+ _s2.wait().get();
+ ++_switches;
+ _s1.signal();
+ }
+ }
+public:
+ void begin_measurement() {
+ _s1.signal();
+ }
+ future<uint64_t> measure() {
+ _done1 = true;
+ return _t1.join().then([this] {
+ return _t2.join();
+ }).then([this] {
+ return _switches;
+ });
+ }
+ future<> stop() {
+ return make_ready_future<>();
+ }
+};
+
+int main(int ac, char** av) {
+ static const auto test_time = 5s;
+ return app_template().run_deprecated(ac, av, [] {
+ auto dcstp = std::make_unique<distributed<context_switch_tester>>();
+ auto& dcst = *dcstp;
+ return dcst.start().then([&dcst] {
+ return dcst.invoke_on_all(&context_switch_tester::begin_measurement);
+ }).then([] {
+ return sleep(test_time);
+ }).then([&dcst] {
+ return dcst.map_reduce0(std::mem_fn(&context_switch_tester::measure), uint64_t(), std::plus<uint64_t>());
+ }).then([] (uint64_t switches) {
+ switches /= smp::count;
+ fmt::print("context switch time: {:5.1f} ns\n",
+ double(std::chrono::duration_cast<std::chrono::nanoseconds>(test_time).count()) / switches);
+ }).then([&dcst] {
+ return dcst.stop();
+ }).then([dcstp = std::move(dcstp)] {
+ engine_exit(0);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/thread_test.cc b/src/seastar/tests/unit/thread_test.cc
new file mode 100644
index 000000000..c1190b9cb
--- /dev/null
+++ b/src/seastar/tests/unit/thread_test.cc
@@ -0,0 +1,264 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/sleep.hh>
+#include <sys/mman.h>
+#include <sys/signal.h>
+
+#include <valgrind/valgrind.h>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_thread_1) {
+ return do_with(sstring(), [] (sstring& x) {
+ auto t1 = new thread([&x] {
+ x = "abc";
+ });
+ return t1->join().then([&x, t1] {
+ BOOST_REQUIRE_EQUAL(x, "abc");
+ delete t1;
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_2) {
+ struct tmp {
+ std::vector<thread> threads;
+ semaphore sem1{0};
+ semaphore sem2{0};
+ int counter = 0;
+ void thread_fn() {
+ sem1.wait(1).get();
+ ++counter;
+ sem2.signal(1);
+ }
+ };
+ return do_with(tmp(), [] (tmp& x) {
+ auto n = 10;
+ for (int i = 0; i < n; ++i) {
+ x.threads.emplace_back(std::bind(&tmp::thread_fn, &x));
+ }
+ BOOST_REQUIRE_EQUAL(x.counter, 0);
+ x.sem1.signal(n);
+ return x.sem2.wait(n).then([&x, n] {
+ BOOST_REQUIRE_EQUAL(x.counter, n);
+ return parallel_for_each(x.threads.begin(), x.threads.end(), std::mem_fn(&thread::join));
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_async) {
+ sstring x = "x";
+ sstring y = "y";
+ auto concat = [] (sstring x, sstring y) {
+ sleep(10ms).get();
+ return x + y;
+ };
+ return async(concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_async_immed) {
+ return async([] { return 3; }).then([] (int three) {
+ BOOST_REQUIRE_EQUAL(three, 3);
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_async_nested) {
+ return async([] {
+ return async([] {
+ return 3;
+ }).get0();
+ }).then([] (int three) {
+ BOOST_REQUIRE_EQUAL(three, 3);
+ });
+}
+
+void compute(float& result, bool& done, uint64_t& ctr) {
+ while (!done) {
+ for (int n = 0; n < 10000; ++n) {
+ result += 1 / (result + 1);
+ ++ctr;
+ }
+ thread::yield();
+ }
+}
+
+#if defined(SEASTAR_ASAN_ENABLED) && defined(SEASTAR_HAVE_ASAN_FIBER_SUPPORT)
+volatile int force_write;
+volatile void* shut_up_gcc;
+
+[[gnu::noinline]]
+void throw_exception() {
+ volatile char buf[1024];
+ shut_up_gcc = &buf;
+ for (int i = 0; i < 1024; i++) {
+ buf[i] = force_write;
+ }
+ throw 1;
+}
+
+[[gnu::noinline]]
+void use_stack() {
+ volatile char buf[2 * 1024];
+ shut_up_gcc = &buf;
+ for (int i = 0; i < 2 * 1024; i++) {
+ buf[i] = force_write;
+ }
+}
+
+SEASTAR_TEST_CASE(test_asan_false_positive) {
+ return async([] {
+ try {
+ throw_exception();
+ } catch (...) {
+ use_stack();
+ }
+ });
+}
+#endif
+
+SEASTAR_THREAD_TEST_CASE_EXPECTED_FAILURES(abc, 2) {
+ BOOST_TEST(false);
+ BOOST_TEST(false);
+}
+
+SEASTAR_TEST_CASE(test_thread_custom_stack_size) {
+ sstring x = "x";
+ sstring y = "y";
+ auto concat = [] (sstring x, sstring y) {
+ sleep(10ms).get();
+ return x + y;
+ };
+ thread_attributes attr;
+ attr.stack_size = 16384;
+ return async(attr, concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ });
+}
+
+// The test case uses x86_64 specific signal handler info. The test
+// fails with detect_stack_use_after_return=1. We could put it behind
+// a command line option and fork/exec to run it after removing
+// detect_stack_use_after_return=1 from the environment.
+#if defined(SEASTAR_THREAD_STACK_GUARDS) && defined(__x86_64__) && !defined(SEASTAR_ASAN_ENABLED)
+struct test_thread_custom_stack_size_failure : public seastar::testing::seastar_test {
+ const char* get_test_file() override { return __FILE__; }
+ const char* get_name() override { return "test_thread_custom_stack_size_failure"; }
+ int get_expected_failures() override { return 0; } \
+ seastar::future<> run_test_case() override;
+};
+
+static test_thread_custom_stack_size_failure test_thread_custom_stack_size_failure_instance;
+static thread_local volatile bool stack_guard_bypassed = false;
+
+static int get_mprotect_flags(void* ctx) {
+ int flags;
+ ucontext_t* context = reinterpret_cast<ucontext_t*>(ctx);
+ if (context->uc_mcontext.gregs[REG_ERR] & 0x2) {
+ flags = PROT_READ | PROT_WRITE;
+ } else {
+ flags = PROT_READ;
+ }
+ return flags;
+}
+
+static void* pagealign(void* ptr, size_t page_size) {
+ static const int pageshift = ffs(page_size) - 1;
+ return reinterpret_cast<void*>(((reinterpret_cast<intptr_t>((ptr)) >> pageshift) << pageshift));
+}
+
+static thread_local struct sigaction default_old_sigsegv_handler;
+
+static void bypass_stack_guard(int sig, siginfo_t* si, void* ctx) {
+ assert(sig == SIGSEGV);
+ int flags = get_mprotect_flags(ctx);
+ stack_guard_bypassed = (flags & PROT_WRITE);
+ if (!stack_guard_bypassed) {
+ return;
+ }
+ size_t page_size = getpagesize();
+ auto mp_result = mprotect(pagealign(si->si_addr, page_size), page_size, PROT_READ | PROT_WRITE);
+ assert(mp_result == 0);
+}
+
+// This test will fail with a regular stack size, because we only probe
+// around 10KiB of data, and the stack guard resides after 128'th KiB.
+seastar::future<> test_thread_custom_stack_size_failure::run_test_case() {
+ if (RUNNING_ON_VALGRIND) {
+ return make_ready_future<>();
+ }
+
+ sstring x = "x";
+ sstring y = "y";
+
+ // Catch segmentation fault once:
+ struct sigaction sa{};
+ sa.sa_sigaction = &bypass_stack_guard;
+ sa.sa_flags = SA_SIGINFO;
+ auto ret = sigaction(SIGSEGV, &sa, &default_old_sigsegv_handler);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+
+ auto concat = [] (sstring x, sstring y) {
+ sleep(10ms).get();
+ // Probe the stack by writing to it in intervals of 1024,
+ // until we hit a write fault. In order not to ruin anything,
+ // the "write" uses data it just read from the address.
+ volatile char* mem = reinterpret_cast<volatile char*>(&x);
+ for (int i = 0; i < 20; ++i) {
+ mem[i*-1024] = char(mem[i*-1024]);
+ if (stack_guard_bypassed) {
+ break;
+ }
+ }
+ return x + y;
+ };
+ thread_attributes attr;
+ attr.stack_size = 16384;
+ return async(attr, concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ BOOST_REQUIRE(stack_guard_bypassed);
+ auto ret = sigaction(SIGSEGV, &default_old_sigsegv_handler, nullptr);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+ }).then([concat, x, y] {
+ // The same function with a default stack will not trigger
+ // a segfault, because its stack is much bigger than 10KiB
+ return async(concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ });
+ });
+}
+#endif // SEASTAR_THREAD_STACK_GUARDS && __x86_64__
diff --git a/src/seastar/tests/unit/timer_test.cc b/src/seastar/tests/unit/timer_test.cc
new file mode 100644
index 000000000..ca698b7eb
--- /dev/null
+++ b/src/seastar/tests/unit/timer_test.cc
@@ -0,0 +1,141 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/timer.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+#include <chrono>
+#include <iostream>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+#define BUG() do { \
+ std::cerr << "ERROR @ " << __FILE__ << ":" << __LINE__ << std::endl; \
+ throw std::runtime_error("test failed"); \
+ } while (0)
+
+#define OK() do { \
+ std::cerr << "OK @ " << __FILE__ << ":" << __LINE__ << std::endl; \
+ } while (0)
+
+template <typename Clock>
+struct timer_test {
+ timer<Clock> t1;
+ timer<Clock> t2;
+ timer<Clock> t3;
+ timer<Clock> t4;
+ timer<Clock> t5;
+ promise<> pr1;
+ promise<> pr2;
+
+ future<> run() {
+ t1.set_callback([this] {
+ OK();
+ fmt::print(" 500ms timer expired\n");
+ if (!t4.cancel()) {
+ BUG();
+ }
+ if (!t5.cancel()) {
+ BUG();
+ }
+ t5.arm(1100ms);
+ });
+ t2.set_callback([] { OK(); fmt::print(" 900ms timer expired\n"); });
+ t3.set_callback([] { OK(); fmt::print("1000ms timer expired\n"); });
+ t4.set_callback([] { OK(); fmt::print(" BAD cancelled timer expired\n"); });
+ t5.set_callback([this] { OK(); fmt::print("1600ms rearmed timer expired\n"); pr1.set_value(); });
+
+ t1.arm(500ms);
+ t2.arm(900ms);
+ t3.arm(1000ms);
+ t4.arm(700ms);
+ t5.arm(800ms);
+
+ return pr1.get_future().then([this] { return test_timer_cancelling(); }).then([this] {
+ return test_timer_with_scheduling_groups();
+ });
+ }
+
+ future<> test_timer_cancelling() {
+ timer<Clock>& t1 = *new timer<Clock>();
+ t1.set_callback([] { BUG(); });
+ t1.arm(100ms);
+ t1.cancel();
+
+ t1.arm(100ms);
+ t1.cancel();
+
+ t1.set_callback([this] { OK(); pr2.set_value(); });
+ t1.arm(100ms);
+ return pr2.get_future().then([&t1] { delete &t1; });
+ }
+
+ future<> test_timer_with_scheduling_groups() {
+ return async([] {
+ auto sg1 = create_scheduling_group("sg1", 100).get0();
+ auto sg2 = create_scheduling_group("sg2", 100).get0();
+ thread_attributes t1attr;
+ t1attr.sched_group = sg1;
+ auto expirations = 0;
+ async(t1attr, [&] {
+ auto make_callback_checking_sg = [&] (scheduling_group sg_to_check) {
+ return [sg_to_check, &expirations] {
+ ++expirations;
+ if (current_scheduling_group() != sg_to_check) {
+ BUG();
+ }
+ };
+ };
+ timer<Clock> t1(make_callback_checking_sg(sg1));
+ t1.arm(10ms);
+ timer<Clock> t2(sg2, make_callback_checking_sg(sg2));
+ t2.arm(10ms);
+ sleep(500ms).get();
+ if (expirations != 2) {
+ BUG();
+ }
+ OK();
+ }).get();
+ destroy_scheduling_group(sg1).get();
+ destroy_scheduling_group(sg2).get();
+ });
+ }
+};
+
+int main(int ac, char** av) {
+ app_template app;
+ timer_test<steady_clock_type> t1;
+ timer_test<lowres_clock> t2;
+ return app.run_deprecated(ac, av, [&t1, &t2] {
+ fmt::print("=== Start High res clock test\n");
+ return t1.run().then([&t2] {
+ fmt::print("=== Start Low res clock test\n");
+ return t2.run();
+ }).then([] {
+ fmt::print("Done\n");
+ engine().exit(0);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/tls-ca-bundle.pem b/src/seastar/tests/unit/tls-ca-bundle.pem
new file mode 100644
index 000000000..d56c7e67d
--- /dev/null
+++ b/src/seastar/tests/unit/tls-ca-bundle.pem
@@ -0,0 +1,4195 @@
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2
+MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
+ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk
+hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym
+1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW
+OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb
+2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko
+O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU
+AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF
+Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb
+LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir
+oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C
+MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
+sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2
+MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
+ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC
+206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci
+KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2
+JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9
+BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e
+Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B
+PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67
+Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq
+Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ
+o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3
++L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj
+YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj
+FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn
+xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2
+LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc
+obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8
+CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe
+IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA
+DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F
+AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX
+Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb
+AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl
+Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw
+RY8mkaKO/qk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
+ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
+MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
+LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
+KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
+RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
+WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
+Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
+AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
+eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
+zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
+/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
+VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
+bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
+b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
+iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
+r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
+04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
+GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
+3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
+lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
+NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
+cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
+2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
+JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
+Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
+n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
+PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
+dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
+MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
+MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
+A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
+b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
+cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
+VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
+ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
+uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
+9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
+hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
+pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
+MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
+MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
+dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
+cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
+DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
+yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
+L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
+EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
+7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
+QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
+qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
+NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
+LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
+TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
+LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
+I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
+nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
+NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
+dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
+WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
+v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
+UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
+IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
+W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
+lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
+AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
+c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
+MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
+emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
+DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
+FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
+UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
+YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
+MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
+pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
+13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
+AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
+U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
+F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
+oJ2daZH9
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB
+VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp
+bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R
+dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw
+MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy
+dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52
+ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM
+EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj
+lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ
+znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH
+2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1
+k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs
+2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD
+VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
+AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG
+KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+
+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R
+FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
+mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE
+DNuxUCAKGkq6ahq97BvIxYSazQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
+AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
+CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
+BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
+VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
+qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
+HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
+G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
+lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
+IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
+0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
+k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
+4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
+m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
+cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
+uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
+KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
+ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
+AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
+VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
+CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
+cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
+QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
+7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
+cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
+QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
+czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
+aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
+aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
+DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
+BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
+D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
+JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
+AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
+vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
+tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
+7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
+h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
+d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
+pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE
+AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x
+CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW
+MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF
+RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
+AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7
+09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7
+XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P
+Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK
+t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb
+X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28
+MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU
+fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI
+2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH
+K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae
+ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP
+BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ
+MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw
+RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm
+fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3
+gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe
+I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i
+5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi
+ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn
+MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ
+o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6
+zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN
+GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt
+r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK
+Z05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
+BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
+MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
+SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
+ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
+UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
+4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
+KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
+gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
+rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
+51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
+be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
+KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
+v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
+fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
+jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
+ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
+e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
+jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
+WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
+SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
+pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
+X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
+fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
+K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
+ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
+LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
+LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
+MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
+VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
+CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
+tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
+dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
+PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
+BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
+ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
+7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
+43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
+pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
+WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
+MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
+ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
+BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
+6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
+GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
+dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
+1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
+62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
+BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
+MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
+cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
+b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
+IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
+iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
+4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
+XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
+MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
+EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
+BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
+xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
+87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
+2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
+WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
+0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
+A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
+pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
+ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
+aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
+hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
+hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
+P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
+iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
+xqE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
+Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
+ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
+MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
+yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
+VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
+nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
+XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
+vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
+Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
+N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
+nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
+YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
+kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
+QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
+6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
+yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
+QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
+tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
+QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
+Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
+olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
+x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
+dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
+A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
+cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
+qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
+JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
+s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
+HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
+70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
+V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
+qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
+5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
+C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
+OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
+FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
+KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
+8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
+MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
+0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
+u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
+u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
+YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
+GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
+RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
+KeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
+VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
+cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
+BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
+VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
+0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
+ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
+A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
+aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
+flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc
+MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp
+b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT
+AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs
+aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H
+j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K
+f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55
+IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw
+FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht
+QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm
+/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ
+k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ
+MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC
+seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ
+hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+
+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U
+DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj
+B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
+AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
+EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
+FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
+REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
+Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
+VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
+SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
+4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
+cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
+eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
+HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
+A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
+DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
+vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
+DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
+maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
+lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
+KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
+MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
+VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
+ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
+AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
+661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
+am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
+ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
+PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
+3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
+SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
+3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
+ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
+StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
+Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
+jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
+DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
+ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
+VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
+mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
+IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
+mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
+XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
+dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
+jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
+DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
+9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
+jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
+Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
+ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
+R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0
+ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX
+l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB
+HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B
+5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3
+WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP
+gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+
+DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu
+BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs
+h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk
+LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
+6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
+L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
+1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
+MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
+QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
+arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
+Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
+FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
+P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
+9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
+uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
+9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
+OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
+KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
+DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
+H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
+I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
+5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
+3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
+Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg
+isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z
+NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI
++MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R
+hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+
+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP
+Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s
+EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2
+mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC
+e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow
+dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
+ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
+N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
+tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
+0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
+/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
+KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
+zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
+O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
+34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
+K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
+Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
+QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
+IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
+HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
+O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
+033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
+dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
+kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
+3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
+u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
+4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET
+MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE
+AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw
+CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg
+YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE
+Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX
+mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD
+XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW
+S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp
+FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD
+AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu
+ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z
+ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv
+Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw
+DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6
+yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq
+EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB
+EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN
+PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy
+MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk
+D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o
+OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A
+fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe
+IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n
+oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK
+/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj
+rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD
+3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE
+7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC
+yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd
+qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI
+hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
+xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA
+SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo
+HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB
+emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC
+AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb
+7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x
+DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk
+F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF
+a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT
+Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
+MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
+NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
+PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
+x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
+QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
+yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
+QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
+H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
+QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
+i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
+nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
+rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
+hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
+GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
+lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
+TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
+nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
+gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
+G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
+zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
+L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD
+TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2
+MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF
+Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh
+IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6
+dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO
+V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC
+GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN
+v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB
+AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB
+Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO
+76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK
+OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH
+ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi
+yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL
+buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj
+2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
+UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
+2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
+Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
+nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
+/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
+PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
+QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
+SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
+IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
+zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
+BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
+ZQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
+IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
+MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
+ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
+T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
+FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
+cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
+BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
+fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
+GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
+6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
+pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
+9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
+/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
+Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
+qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
+SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
+u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
+Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
+crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
+FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
+/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
+wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
+4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
+2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
+FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
+CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
+boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
+jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
+S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
+QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
+0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
+NVOFBkpdn627G190
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
+MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
+ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
+b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
+MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
+ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
+IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
+AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
+unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
+BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
+7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
+0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
+roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
+A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
+aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
+26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
+BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
+EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
+BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
+AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
+p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
+1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
+XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
+eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
+tGWaIZDgqtCYvDi1czyL+Nw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
+MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
+ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
+YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
+MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
+NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
+A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
+A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
+Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
+QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
+eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
+B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
+z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
+AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
+ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
+TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
+MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
+VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
+VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
+AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
+bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
+ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
+VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
+ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
+AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
+BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
+DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
+BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
+QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
+gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
+zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
+130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
+JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
+ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
+AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
+AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
+9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
+bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
+fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
+HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
+t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET
+MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk
+BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4
+Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl
+cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0
+aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY
+F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N
+8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe
+rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K
+/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu
+7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC
+28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6
+lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E
+nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB
+0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09
+5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj
+WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN
+jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s
+ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM
+OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q
+619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn
+2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj
+o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v
+nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG
+5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq
+pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb
+dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0
+BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
+PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
+cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
+MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
+IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
+ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
+VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
+kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
+EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
+H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
+HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
+DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
+QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
+Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
+AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
+yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
+FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
+ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
+kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
+l7+ijrRU
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
+MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
+QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
+MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
+QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
+jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
+ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
+ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
+Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
+AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
+HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
+uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
+TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
+xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
+CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
+O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
+6GAqm4VKQPNriiTsBhYscw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
+MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
+ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
+cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
+WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
+Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
+IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
+UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
+TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
+BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
+kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
+AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
+HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
+sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
+I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
+J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
+VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
+IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
+MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
+dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
+EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
+MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
+28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
+VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
+DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
+5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
+ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
+Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
+UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
+Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
+hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
+HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
+YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
+L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
+ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
+IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
+HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
+DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
+PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
+5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
+glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
+FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
+pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
+xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
+tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
+jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
+fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
+d0jQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
+Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g
+Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0
+aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa
+Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg
+SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo
+aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp
+ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z
+7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//
+DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx
+zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8
+hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs
+4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u
+gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY
+NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
+FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3
+j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG
+52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB
+echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
+ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI
+zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy
+wy39FCqQmbkHzJ8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw
+PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu
+MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx
+GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL
+MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf
+HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh
+gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW
+v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue
+Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr
+9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt
+6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7
+MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl
+Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58
+ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq
+hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p
+iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC
+dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL
+kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL
+hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
+YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
+GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
+BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
+3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
+YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
+rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
+ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
+oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
+QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
+b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
+AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
+GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
+G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
+l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
+smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
+ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
+fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
+BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
+cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
+HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
+CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
+3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
+6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
+HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
+EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
+Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
+Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
+DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
+5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
+gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
+aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
+izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
+aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
+MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
+VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
+fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
+TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
+fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
+1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
+kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
+A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
+ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
+dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
+Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
+HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
+jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
+xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
+dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
+A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
+bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
+ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
+b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
+7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
+J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
+HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
+t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
+FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
+XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
+MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
+hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
+MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
+A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
+Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
+XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
+omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
+A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
+ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
+HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
+UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
+tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
+ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
+lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
+/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
+A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
+A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
+dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
+MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
+cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
+L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
+BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
+acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
+zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
+PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
+Johw1+qRzT65ysCQblrGXnRl11z+o+I=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
+NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
+BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
+ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
+3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
+qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
+p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
+HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
+ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
+HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
+Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
+c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
+RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
+dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
+Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
+3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
+CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
+xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
+KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
+ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
+MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
+VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
+FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
+ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
+gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
+fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
+ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
+ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
+MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
+c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
+dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
+aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
+hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
+QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
+h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
+rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
+9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
+PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
+Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
+rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
+OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
+xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
+7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
+aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
+SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
+ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
+AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
+R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
+JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
+Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
+MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
+IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
+IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
+RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
+U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
+IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
+ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
+QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
+rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
+NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
+QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
+txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
+BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
+AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
+tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
+IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
+6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
+Cm26OWMohpLzGITY+9HPBVZkVw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
+JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
+mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
+VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
+AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
+AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
+pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
+dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
+fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
+NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
+H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
+n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
+biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
+EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
+bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
+YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
+AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
+BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
+QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
+0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
+lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
+B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
+ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
+IhNzbM8m9Yop5w==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
+RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
+Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
+RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
+AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
+JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
+6pZjamVFkpUBtA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
+5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
+1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
+NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
+Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
+8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
+pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
+MrY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
+Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
+EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
+IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
+fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
+Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
+BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
+AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
+oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
+sycX
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
+RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
+ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
+xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
+ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
+DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
+jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
+CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
+EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
+fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
+uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
+chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
+9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
+ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
+SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
+fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
+sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
+cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
+0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
+4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
+r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
+/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
+gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1
+MQswCQYDVQQGEwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxp
+Z2kgQS5TLjE8MDoGA1UEAxMzZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZp
+a2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3MDEwNDExMzI0OFoXDTE3MDEwNDEx
+MzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0cm9uaWsgQmlsZ2kg
+R3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9uaWsg
+U2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdU
+MZTe1RK6UxYC6lhj71vY8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlT
+L/jDj/6z/P2douNffb7tC+Bg62nsM+3YjfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H
+5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAIJjjcJRFHLfO6IxClv7wC
+90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk9Ok0oSy1
+c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoE
+VtstxNulMA0GCSqGSIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLP
+qk/CaOv/gKlR6D1id4k9CnU58W5dF4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S
+/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwqD2fK/A+JYZ1lpTzlvBNbCNvj
+/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4Vwpm+Vganf2X
+KWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
+fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV
+BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC
+aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV
+BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1
+Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz
+MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+
+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp
+em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
+ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY
+B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH
+D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF
+Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo
+q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D
+k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH
+fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut
+dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM
+ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8
+zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
+rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX
+U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6
+Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5
+XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF
+Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR
+HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY
+GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c
+77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3
++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK
+vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6
+FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl
+yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P
+AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD
+y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d
+NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV
+BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt
+ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4
+MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg
+SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl
+a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h
+4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk
+tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s
+tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL
+dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4
+c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um
+TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z
++kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O
+Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW
+OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW
+fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2
+l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw
+FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+
+8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI
+6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO
+TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME
+wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY
+Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn
+xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q
+DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q
+Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t
+hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4
+7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7
+QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB
+8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy
+dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1
+YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3
+dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh
+IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD
+LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG
+EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g
+KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD
+ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu
+bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg
+ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R
+85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm
+4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV
+HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd
+QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t
+lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB
+o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4
+opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo
+dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW
+ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN
+AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y
+/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k
+SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy
+Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS
+Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
+nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
+MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
+czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
+CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
+MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
+ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
+b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
+euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
+bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
+WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
+MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
+1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
+zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
+BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
+BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
+v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
+E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
+iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
+GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
+MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
+LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
+YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
+A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
+K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
+sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
+MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
+XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
+HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
+4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
+j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
+U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
+u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
+bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
+fF6adulZkMV8gzURZVE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
+NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
+BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
+Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
+4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
+KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
+rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
+94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
+sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
+gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
+kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
+vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
+O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
+AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
+9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
+eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
+0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
+UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
+dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
+MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
+dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
+BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
+cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
+AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
+MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
+aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
+ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
+IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
+MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
+A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
+1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
+ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
+MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
+dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
+c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
+UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
+58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
+o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
+MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
+aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
+A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
+Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
+8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
+R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
+9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
+fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
+iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
+1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
+MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
+ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
+uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
+Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
+tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
+PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
+hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
+5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
+IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
+R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
+PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
+Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
+TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
+5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
+S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
+2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
+EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
+EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
+/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
+A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
+abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
+I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
+4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
+R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
+MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
+AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
+ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
+7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
+kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
+mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
+KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
+6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
+4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
+oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
+UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
+AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
+MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
+KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
+MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
+NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
+BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
+MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
+So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
+tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
+CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
+qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
+rD6ogRLQy7rQkgu2npaqBA+K
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
+mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
+MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
+eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
+BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
+BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
+hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
+5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
+JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
+DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
+huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
+AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
+zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
+kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
+SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
+spki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
+IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
+VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
+cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
+QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
+F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
+c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
+mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
+VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
+teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
+f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
+Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
+nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
+/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
+MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
+9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
+IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
+ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
+uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
+Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
+QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
+koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
+ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
+DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
+bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
+VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
+c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
+WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
+FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
+XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
+se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
+KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
+IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
+y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
+hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
+QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
+Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
+HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
+KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
+L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
+Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
+ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
+T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
+GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
+1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
+OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
+6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
+QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
+FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
+uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
+kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
+ewv4n4Q=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
+8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
+hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
+KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
+515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
+xwy8p2Fp8fc74SrL+SvzZpA3
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
+MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
+aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
+jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
+xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
+1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
+snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
+U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
+9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
+AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
+yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
+38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
+AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
+DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
+HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
+MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
+v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
+eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
+tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
+C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
+zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
+mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
+V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
+bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
+3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
+J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
+291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
+ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
+AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
+MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
+RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
+KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
+QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
+XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
+LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
+RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
+jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
+mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
+Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
+WD9f
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
+MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
+cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
+A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
+BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
+KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
+G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
+zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
+ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
+HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
+Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
+yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
+beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
+6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
+zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
+BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
+ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
+ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
+cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
+YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
+CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
+KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
+hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
+UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
+X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
+fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
+a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
+Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
+SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
+AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
+M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
+v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
+MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
+YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
+MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
+ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
+MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
+ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
+PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
+wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
+EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
+avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
+sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
+/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
+IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
+OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
+TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
+dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
+ReYNnyicsbkqWletNw+vHX/bvZ8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
+EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
+ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
+NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
+EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
+AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
+E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
+/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
+DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
+GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
+tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
+WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
+9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
+gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
+2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
+4uJEvlz36hz1
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
+RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
+dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
+YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
+NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
+EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
+cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
+dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
+fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
+bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
+75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
+FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
+HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
+5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
+b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
+A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
+6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
+dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
+Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
+l7WdmplNsDz4SgCbZN2fOUvRJ9e4
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
+FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
+Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
+A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
+b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
+jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
+PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
+ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
+nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
+q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
+MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
+mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
+7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
+oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
+EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
+fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
+AmvZWg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
+AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
+TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
+9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
+MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
+BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
+MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
+LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
+s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
+xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
+u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
+F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
+Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
+PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
+HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
+NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
+AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
+L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
+YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
+NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
+0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
+MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
+ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
+VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
+b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
+scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
+xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
+LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
+uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
+yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
+rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
+BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
+hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
+QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
+Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
+QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
+BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
+A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
+laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
+awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
+JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
+LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
+VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
+LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
+UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
+QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
+QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN
+AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp
+dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw
+MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw
+CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ
+MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB
+SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz
+ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH
+LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP
+PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL
+2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w
+ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC
+MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk
+AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0
+AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz
+AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz
+AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f
+BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY
+P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi
+CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g
+kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95
+HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS
+na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q
+qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z
+TbvGRNs2yyqcjg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw
+cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy
+b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z
+ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4
+NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN
+TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p
+Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u
+uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+
+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA
+vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770
+Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx
+62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB
+AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw
+LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP
+BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB
+AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov
+MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5
+ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT
+AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh
+ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo
+AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa
+AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln
+bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p
+Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP
+PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv
+Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB
+EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu
+w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj
+cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV
+HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI
+VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS
+BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS
+b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS
+8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds
+ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl
+7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR
+hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/
+MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
+VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
+ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
+CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
+OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
+FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
+Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
+kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
+cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
+fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
+N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
+xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
+Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
+SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
+mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
+ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
+2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
+HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
+EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
+MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
+cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
+dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
+pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
+b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
+IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
+lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
+AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
+VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
+ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
+BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
+AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
+U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
+bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
+uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
+XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV
+MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe
+TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0
+dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB
+KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0
+N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC
+dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu
+MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL
+b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD
+zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi
+3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8
+WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY
+Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi
+NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC
+ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4
+QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0
+YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz
+aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
+IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm
+ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg
+ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs
+amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv
+IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3
+Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6
+ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1
+YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg
+dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs
+b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G
+CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO
+xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP
+0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ
+QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk
+f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK
+8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
+MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
+dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
+UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
+ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
+c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
+OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
+mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
+BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
+qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
+gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
+bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
+dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
+6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
+h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
+/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
+pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
+ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
+aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
+ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
+NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
+A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
+VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
+SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
+VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
+w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
+mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
+4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
+4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
+EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
+SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
+ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
+vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
+Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
+/L7fCg0=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1
+dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s
+YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz
+dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0
+aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh
+IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ
+KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw
+MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy
+b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx
+KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG
+A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u
+aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9
+7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74
+BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G
+ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9
+JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0
+PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2
+0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
+0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/
+6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m
+v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7
+K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev
+bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw
+MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w
+MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD
+gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0
+b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh
+bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0
+cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp
+ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg
+ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq
+hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD
+AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w
+MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag
+RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t
+UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl
+cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
+Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG
+AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN
+AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS
+1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB
+3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv
+Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh
+HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm
+pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz
+sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE
+qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb
+mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9
+opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H
+YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
+TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
+MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
+IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
+dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
+li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
+rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
+WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
+F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
+xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
+Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
+dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
+ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
+IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
+c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
+ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
+KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
+KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
+y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
+dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
+VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
+MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
+fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
+7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
+cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
+mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
+xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
+SnQ2+Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
+MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
+wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
+rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
+68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
+4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
+UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
+abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
+3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
+KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
+hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
+Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
+zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
+ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
+MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
+cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
+qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
+YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
+b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
+8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
+NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
+ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
+q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
+nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
+GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
+Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
+WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
+rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
+ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
+Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
+PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
+/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
+oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
+yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
+EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
+A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
+MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
+BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
+g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
+fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
+WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
+B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
+hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
+TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
+mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
+ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
+4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
+8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
+MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
+qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
+n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
+c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
+o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
+IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
+IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
+8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
+vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
+7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
+cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
+ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
+AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
+roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
+W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
+lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
+csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
+dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
+KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
+HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
+WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
+V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
+4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
+H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
+8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
+vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
+mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
+btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
+T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
+WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
+c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
+4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
+VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
+CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
+aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
+dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
+czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
+A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
+TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
+Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
+7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
+d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
+4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
+t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
+DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
+k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
+zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
+Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
+mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
+4SVhM7JZG+Ju1zdXtg2pEto=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
+MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
+/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
+FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
+U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
+ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
+FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
+A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
+eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
+sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
+VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
+A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
+ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
+KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
+FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
+oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
+u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
+0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
+3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
+8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
+DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
+PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
+ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6
+MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
+dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX
+BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy
+MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp
+eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg
+/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl
+wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh
+AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2
+PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu
+AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR
+MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc
+HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/
+Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+
+f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO
+rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch
+6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3
+7CAFYd4=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF
+UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ
+R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN
+MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G
+A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw
+JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+
+WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj
+SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl
+u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy
+A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk
+Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7
+MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr
+aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC
+IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A
+cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA
+YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA
+bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA
+bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
+aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA
+aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA
+ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA
+YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA
+ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA
+LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6
+Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y
+eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw
+CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G
+A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu
+Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn
+lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt
+b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg
+9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF
+ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC
+IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
+MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
+A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
+MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
+Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
+QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
+i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
+h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
+MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
+UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
+8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
+h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
+AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
+KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
+X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
+QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
+pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
+QSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
+MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
+cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
+Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
+0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
+wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
+7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
+8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
+BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
+JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
+NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
+6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
+3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
+D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
+CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
+MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
+Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
+iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
+/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
+jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
+HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
+sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
+gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
+KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
+AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
+URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
+H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
+I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
+iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
+MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
+IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
+bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
+RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
+zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
+bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
+MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
+VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
+OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
+tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
+q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
+EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
+VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
+DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
+dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
+YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
+OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
+zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
+VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
+hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
+ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
+awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
+OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
+coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
+okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
+t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
+1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
+SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
+MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
+dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
+WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
+VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
+9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
+DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
+Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
+QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
+xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
+A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
+kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
+Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
+Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
+JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
+RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
+MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
+MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
+BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
+Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
+5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
+3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
+vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
+8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
+DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
+MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
+zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
+3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
+FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
+Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
+ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO
+TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy
+MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk
+ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn
+ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71
+9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO
+hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U
+tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o
+BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh
+SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww
+OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv
+cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA
+7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k
+/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm
+eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6
+u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy
+7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
+iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
+TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
+DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
+ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
+b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
+qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
+uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
+Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
+pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
+5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
+UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
+GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
+5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
+6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
+eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
+B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
+BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
+L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
+SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
+CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
+5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
+IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
+gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
+vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
+bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
+N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
+Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
+ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
+ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
+MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
+aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
+Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
+nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
+HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
+Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
+dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
+HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
+CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
+sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
+4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
+8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
+mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
+ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
+VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
+ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
+dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
+OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
+8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
+Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
+hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
+6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
+DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
+AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
+bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
+ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
+qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
+0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
+sSi6
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
+MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
+U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
+cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
+pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
+OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
+Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
+Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
+HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
+Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
+Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
+26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
+AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
+ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
+LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
+BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
+dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
+cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
+YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
+dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
+bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
+YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
+TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
+9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
+jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
+FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
+ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
+ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
+EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
+L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
+O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
+um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
+NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9
+MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
+U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
+cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
+pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
+OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
+Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
+Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
+HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
+Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
+Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
+26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
+AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC
+ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w
+ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk
+aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0
+YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg
+c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93
+d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG
+CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF
+wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS
+Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst
+0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc
+pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl
+CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF
+P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK
+1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm
+KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ
+8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm
+fyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1
+OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG
+A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ
+JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD
+vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo
+D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/
+Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW
+RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK
+HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN
+nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM
+0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i
+UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9
+Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg
+TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL
+BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX
+UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl
+6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK
+9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ
+HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI
+wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY
+XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l
+IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo
+hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr
+so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
+biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
+MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
+d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
+76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
+bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
+6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
+emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
+MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
+MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
+MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
+FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
+aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
+gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
+qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
+lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
+8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
+45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
+UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
+O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
+bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
+GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
+77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
+hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
+92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
+Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
+ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
+Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
+BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
+IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
+RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
+U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
+Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
+YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
+nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
+6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
+eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
+c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
+MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
+HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
+jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
+5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
+rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
+wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
+AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
+WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
+xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
+2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
+IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
+aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
+em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
+dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
+OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
+tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk
+MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
+Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT
+AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
+Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9
+m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih
+FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/
+TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F
+EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco
+kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu
+HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF
+vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo
+19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC
+L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW
+bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX
+JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
+FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc
+K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf
+ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik
+Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB
+sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e
+3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR
+ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip
+mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH
+b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf
+rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms
+hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y
+zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6
+MBr1mmz0DlP5OlvRHA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk
+MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
+Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT
+AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
+Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr
+jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r
+0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f
+2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP
+ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF
+y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA
+tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL
+6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0
+uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL
+acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh
+k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q
+VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
+FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
+BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh
+b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R
+fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv
+/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI
+REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx
+srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv
+aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT
+woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n
+Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W
+t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N
+8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2
+9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5
+wSsSnqaeG8XmDtkx2Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw
+ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp
+dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290
+IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD
+VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy
+dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg
+MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx
+UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD
+1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH
+oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR
+HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/
+5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv
+idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL
+OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC
+NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f
+46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB
+UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth
+7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G
+A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
+MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB
+bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x
+XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T
+PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0
+Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70
+WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL
+Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm
+7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S
+nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN
+vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB
+WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI
+fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb
+I+2ksx0WckNLIOFZfsLorSa/ovc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
+AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
+FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
+1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
+jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
+wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
+WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
+NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
+uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
+IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
+g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
+BSeOE6Fuwg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
+8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
+RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
+hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
+ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
+EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
+A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
+WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
+1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
+6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
+91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
+TpPDpFQUWw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf
+tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg
+uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J
+XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK
+8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99
+5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3
+kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS
+GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt
+ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8
+au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV
+hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI
+dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW
+Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q
+Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2
+1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq
+ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1
+Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX
+XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN
+irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8
+TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6
+g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB
+95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj
+S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
+BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1
+c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx
+MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg
+R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD
+VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR
+JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T
+fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu
+jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z
+wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ
+fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD
+VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G
+CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1
+7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn
+8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs
+ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/
+2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg
+MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
+dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz
+MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy
+dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD
+VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg
+xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu
+xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7
+XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k
+heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J
+YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C
+urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1
+JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51
+b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV
+9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7
+kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh
+fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
+B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA
+aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS
+RGQDJereW26fyfJOrN3H
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS
+S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
+SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx
+OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry
+b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC
+VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE
+sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F
+ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY
+KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG
++7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG
+HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P
+IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M
+733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk
+Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW
+AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
+aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5
+mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa
+XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ
+qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS
+S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
+SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3
+WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv
+bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU
+UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw
+bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe
+LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef
+J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh
+R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ
+Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX
+JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p
+zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S
+Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
+KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq
+ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
+Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz
+gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH
+uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS
+y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
+EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
+VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
+NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
+B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
+10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
+0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
+MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
+zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
+46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
+yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
+laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
+oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
+BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
+qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
+4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
+1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
+H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
+RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
+15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
+6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
+nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
+wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
+aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
+KwbQBM0=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
+MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
+V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
+WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
+LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
+AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
+K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
+RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
+rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
+3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
+hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
+MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
+XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
+lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
+aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
+YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
+MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
+PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
+IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
+gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
+yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
+F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
+jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
+ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
+VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
+YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
+EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
+Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
+DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
+MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
+UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
+qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
+ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
+JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
+hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
+EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
+nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
+udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
+ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
+LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
+pYYsfPQS
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
+NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
+b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
+VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
+VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
+7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
+Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
+/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
+81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
+dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
+Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
+sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
+pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
+slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
+arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
+VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
+9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
+dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
+0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
+TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
+Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
+Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
+OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
+vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
+t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
+HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
+SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF
+MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL
+ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx
+MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc
+MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+
+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH
+iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj
+vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA
+0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB
+OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/
+BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E
+FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01
+GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW
+zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4
+1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE
+f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F
+jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN
+ZetX2fNXlrtIzYE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
+MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
+bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
+VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
+YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
+dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
+Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
+GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
+aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
+QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
+xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
+aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
+IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
+gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
+O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
+fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
+lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
+AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
+NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
+wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
+7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
+gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
+oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
+yZyQ2uypQjyttgI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
+eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
+JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
+VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
+I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
+o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
+A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
+zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
+RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
+MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
+3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
+tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
+Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
+VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
+79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
+c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
+Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
+c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
+UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
+Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
+Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
+VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
+ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
+8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
+iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
+Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
+XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
+qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
+VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
+L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
+jjxDah2nGN59PRbxYvnKkKj9
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
+kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
+IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
+VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
+dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
+E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
+D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
+4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
+lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
+bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
+o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
+MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
+LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
+BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
+AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
+j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
+KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
+2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
+mfnGV/TJVTl4uix5yaaIK/QI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
+MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
+d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
+cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
+0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
+M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
+MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
+oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
+DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
+oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
+dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
+bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
+BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
+CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
+CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
+3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
+KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
+U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
+SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
+biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
+GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
+fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
+aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
+aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
+kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
+4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
+FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
+nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
+t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
+SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
+BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
+rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
+NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
+BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
+MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
+p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
+5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
+WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
+4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
+hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
+vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
+ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
+IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
+IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
+bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
+9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
+H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
+LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
+/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
+rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
+WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
+exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
+sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
+seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
+4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
+lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
+7M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
+N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
+KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
+kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
+CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
+Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
+imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
+2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
+DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
+F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
+TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
+GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
+U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
+NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
+ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
+ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
+CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
+g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
+2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
+bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
+MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
+cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
+bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
+CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
+dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
+cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
+2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
+lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
+ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
+299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
+vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
+dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
+AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
+AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
+zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
+LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
+7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
+++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
+398znM/jra6O1I7mT1GvFpLgXPYHDw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx
+IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs
+cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v
+dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0
+MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl
+bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD
+DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r
+WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU
+Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs
+HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj
+z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf
+SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl
+AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG
+KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P
+AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j
+BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC
+VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX
+ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB
+ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd
+/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB
+A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn
+k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9
+iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv
+2G0xffX8oRAHh84vWdw+WNs=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV
+MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV
+BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw
+MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX
+b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN
+rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U
+fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc
+f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2
+ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M
+x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR
+aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch
+zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar
+uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K
+mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA
+Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv
+HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H
+EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1
+LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ
+MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e
+JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN
+g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp
+dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab
+R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ
+PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce
+xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+
+J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl
+OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT
+ee5Ehr7XHuQe+w==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG
+MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV
+BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw
+MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl
+ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r
+D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1
+9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf
+v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk
+UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L
+NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb
++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V
+qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K
+yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G
+AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK
+J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC
+AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
+BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4
+WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6
+yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj
+/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6
+jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2
+ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX
+X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n
+FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D
+u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l
+O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le
+ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1
+2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
+gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
+MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
+UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
+NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
+dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
+dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
+38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
+KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
+DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
+qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
+JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
+PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
+BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
+jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
+eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
+vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
+IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
+i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
+O+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
+AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
+QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
+MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
+0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
+UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
+RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
+OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
+JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
+AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
+BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
+LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
+MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
+44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
+Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
+i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
+9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
+IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
+SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
+SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
+ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
+DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
+TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
+fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
+sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
+WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
+nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
+dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
+NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
+AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
+MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
+uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
+PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
+JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
+gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
+j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
+5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
+o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
+/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
+Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
+W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
+hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
+NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
+LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
+A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
+W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
+3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
+6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
+Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
+NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
+r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
+DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
+YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
+/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
+LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
+jVaMaA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
+IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
+BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
+MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
+YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
+dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
+BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
+papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
+DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
+KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
+XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
+rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
+BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
+Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
+LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
+MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
+ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
+gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
+YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
+b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
+9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
+zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
+OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
+HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
+2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
+oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
+KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
+m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
+MdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
diff --git a/src/seastar/tests/unit/tls_test.cc b/src/seastar/tests/unit/tls_test.cc
new file mode 100644
index 000000000..838c4062a
--- /dev/null
+++ b/src/seastar/tests/unit/tls_test.cc
@@ -0,0 +1,845 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <iostream>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/core/temporary_buffer.hh>
+#include <seastar/core/iostream.hh>
+#include <seastar/util/std-compat.hh>
+#include <seastar/net/tls.hh>
+#include <seastar/net/dns.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+
+#include <boost/dll.hpp>
+
+#include "loopback_socket.hh"
+#include "tmpdir.hh"
+
+#include <gnutls/gnutls.h>
+
+#if 0
+
+static void enable_gnutls_logging() {
+ gnutls_global_set_log_level(99);
+ gnutls_global_set_log_function([](int lv, const char * msg) {
+ std::cerr << "GNUTLS (" << lv << ") " << msg << std::endl;
+ });
+}
+#endif
+
+static const auto cert_location = boost::dll::program_location().parent_path();
+
+static std::string certfile(const std::string& file) {
+ return (cert_location / file).string();
+}
+
+using namespace seastar;
+
+static future<> connect_to_ssl_addr(::shared_ptr<tls::certificate_credentials> certs, socket_address addr) {
+ return tls::connect(certs, addr, "www.google.com").then([](connected_socket s) {
+ return do_with(std::move(s), [](connected_socket& s) {
+ return do_with(s.output(), [&s](auto& os) {
+ static const sstring msg("GET / HTTP/1.0\r\n\r\n");
+ auto f = os.write(msg);
+ return f.then([&s, &os]() mutable {
+ auto f = os.flush();
+ return f.then([&s]() mutable {
+ return do_with(s.input(), [](auto& in) {
+ auto f = in.read();
+ return f.then([](temporary_buffer<char> buf) {
+ // std::cout << buf.get() << std::endl;
+
+ // Avoid passing a nullptr as an argument of strncmp().
+ // If the temporary_buffer is empty (e.g. due to the underlying TCP connection
+ // being reset) passing the buf.get() (which would be a nullptr) to strncmp()
+ // causes a runtime error which masks the actual issue.
+ if (buf) {
+ BOOST_CHECK(strncmp(buf.get(), "HTTP/", 5) == 0);
+ }
+ BOOST_CHECK(buf.size() > 8);
+ });
+ });
+ });
+ }).finally([&os] {
+ return os.close();
+ });
+ });
+ });
+ });
+}
+
+static future<> connect_to_ssl_google(::shared_ptr<tls::certificate_credentials> certs) {
+ static socket_address google;
+
+ if (google.is_unspecified()) {
+ return net::dns::resolve_name("www.google.com", net::inet_address::family::INET).then([certs](net::inet_address addr) {
+ google = socket_address(addr, 443);
+ return connect_to_ssl_google(certs);
+ });
+ }
+ return connect_to_ssl_addr(std::move(certs), google);
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client) {
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ return certs->set_x509_trust_file(certfile("tls-ca-bundle.pem"), tls::x509_crt_format::PEM).then([certs]() {
+ return connect_to_ssl_google(certs);
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_system_trust) {
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ return certs->set_system_trust().then([certs]() {
+ return connect_to_ssl_google(certs);
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ return connect_to_ssl_google(b.build_certificate_credentials());
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust_multiple) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ auto creds = b.build_certificate_credentials();
+
+ return parallel_for_each(boost::irange(0, 20), [creds](auto i) { return connect_to_ssl_google(creds); });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_priority_strings) {
+ static std::vector<sstring> prios( { "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
+ "NORMAL:+ARCFOUR-128", // means normal ciphers plus ARCFOUR-128.
+ "SECURE128:-VERS-SSL3.0:+COMP-DEFLATE", // means that only secure ciphers are enabled, SSL3.0 is disabled, and libz compression enabled.
+ "NONE:+VERS-TLS-ALL:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1",
+ "SECURE256:+SECURE128",
+ "NORMAL:%COMPAT",
+ "NORMAL:-MD5",
+ "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
+ "NORMAL:+ARCFOUR-128",
+ "SECURE128:-VERS-TLS1.0:+COMP-DEFLATE",
+ "SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2"
+ });
+ return do_for_each(prios, [](const sstring & prio) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ b.set_priority_string(prio);
+ return connect_to_ssl_google(b.build_certificate_credentials());
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_priority_strings_fail) {
+ static std::vector<sstring> prios( { "NONE",
+ "NONE:+CURVE-SECP256R1"
+ });
+ return do_for_each(prios, [](const sstring & prio) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ b.set_priority_string(prio);
+ try {
+ return connect_to_ssl_google(b.build_certificate_credentials()).then([] {
+ BOOST_FAIL("Expected exception");
+ }).handle_exception([](auto ep) {
+ // ok.
+ });
+ } catch (...) {
+ // also ok
+ }
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_failed_connect) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ return connect_to_ssl_addr(b.build_certificate_credentials(), ipv4_addr()).handle_exception([](auto) {});
+}
+
+SEASTAR_TEST_CASE(test_non_tls) {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = server_socket(seastar::listen(addr, opts));
+
+ auto c = server.accept();
+
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+
+ auto f = connect_to_ssl_addr(b.build_certificate_credentials(), addr);
+
+
+ return c.then([f = std::move(f)](accept_result ar) mutable {
+ ::connected_socket s = std::move(ar.connection);
+ std::cerr << "Established connection" << std::endl;
+ auto sp = std::make_unique<::connected_socket>(std::move(s));
+ timer<> t([s = std::ref(*sp)] {
+ std::cerr << "Killing server side" << std::endl;
+ s.get() = ::connected_socket();
+ });
+ t.arm(timer<>::clock::now() + std::chrono::seconds(5));
+ return std::move(f).finally([t = std::move(t), sp = std::move(sp)] {});
+ }).handle_exception([server = std::move(server)](auto ep) {
+ std::cerr << "Got expected exception" << std::endl;
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_before_handshake) {
+ auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
+ return certs->set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).then([certs] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = server_socket(tls::listen(certs, addr, opts));
+ auto c = server.accept();
+ BOOST_CHECK(!c.available()); // should not be finished
+
+ server.abort_accept();
+
+ return c.then([](auto) { BOOST_FAIL("Should not reach"); }).handle_exception([](auto) {
+ // ok
+ }).finally([server = std::move(server)] {});
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_after_handshake) {
+ return async([] {
+ auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
+ certs->set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
+
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = tls::listen(certs, addr, opts);
+ auto sa = server.accept();
+
+ tls::credentials_builder b;
+ b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+
+ auto c = tls::connect(b.build_certificate_credentials(), addr).get0();
+ server.abort_accept(); // should not affect the socket we got.
+
+ auto s = sa.get0();
+ auto out = c.output();
+ auto in = s.connection.input();
+
+ out.write("apa").get();
+ auto f = out.flush();
+ auto buf = in.read().get0();
+ f.get();
+ BOOST_CHECK(sstring(buf.begin(), buf.end()) == "apa");
+
+ out.close().get();
+ in.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_on_server_before_handshake) {
+ return async([] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = server_socket(seastar::listen(addr, opts));
+ auto sa = server.accept();
+
+ tls::credentials_builder b;
+ b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+
+ auto creds = b.build_certificate_credentials();
+ auto f = tls::connect(creds, addr);
+
+ server.abort_accept();
+ try {
+ sa.get();
+ } catch (...) {
+ }
+ server = {};
+
+ try {
+ // the connect as such should succeed, but the handshare following it
+ // should not.
+ auto c = f.get0();
+ auto out = c.output();
+ out.write("apa").get();
+ out.flush().get();
+ out.close().get();
+
+ BOOST_FAIL("Expected exception");
+ } catch (...) {
+ // ok
+ }
+ });
+}
+
+
+struct streams {
+ ::connected_socket s;
+ input_stream<char> in;
+ output_stream<char> out;
+
+ // note: using custom output_stream, because we don't want polled flush
+ streams(::connected_socket cs) : s(std::move(cs)), in(s.input()), out(s.output().detach(), 8192)
+ {}
+};
+
+static const sstring message = "hej lilla fisk du kan dansa fint";
+
+class echoserver {
+ ::server_socket _socket;
+ ::shared_ptr<tls::server_credentials> _certs;
+ seastar::gate _gate;
+ bool _stopped = false;
+ size_t _size;
+ std::exception_ptr _ex;
+public:
+ echoserver(size_t message_size, bool use_dh_params = true)
+ : _certs(
+ use_dh_params
+ ? ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>())
+ : ::make_shared<tls::server_credentials>()
+ )
+ , _size(message_size)
+ {}
+
+ future<> listen(socket_address addr, sstring crtfile, sstring keyfile, tls::client_auth ca = tls::client_auth::NONE, sstring trust = {}) {
+ _certs->set_client_auth(ca);
+ auto f = _certs->set_x509_key_file(crtfile, keyfile, tls::x509_crt_format::PEM);
+ if (!trust.empty()) {
+ f = f.then([this, trust = std::move(trust)] {
+ return _certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
+ });
+ }
+ return f.then([this, addr] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+
+ _socket = tls::listen(_certs, addr, opts);
+
+ (void)try_with_gate(_gate, [this] {
+ return _socket.accept().then([this](accept_result ar) {
+ ::connected_socket s = std::move(ar.connection);
+ auto strms = ::make_lw_shared<streams>(std::move(s));
+ return repeat([strms, this]() {
+ return strms->in.read_exactly(_size).then([strms](temporary_buffer<char> buf) {
+ if (buf.empty()) {
+ return make_ready_future<stop_iteration>(stop_iteration::yes);
+ }
+ sstring tmp(buf.begin(), buf.end());
+ return strms->out.write(tmp).then([strms]() {
+ return strms->out.flush();
+ }).then([] {
+ return make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ });
+ }).finally([strms]{
+ return strms->out.close();
+ }).finally([strms]{});
+ }).handle_exception([this](auto ep) {
+ if (_stopped) {
+ return make_ready_future<>();
+ }
+ _ex = ep;
+ return make_ready_future<>();
+ });
+ }).handle_exception_type([] (const gate_closed_exception&) {/* ignore */});
+ return make_ready_future<>();
+ });
+ }
+
+ future<> stop() {
+ _stopped = true;
+ _socket.abort_accept();
+ return _gate.close().handle_exception([this] (std::exception_ptr ignored) {
+ if (_ex) {
+ std::rethrow_exception(_ex);
+ }
+ });
+ }
+};
+
+static future<> run_echo_test(sstring message,
+ int loops,
+ sstring trust,
+ sstring name,
+ sstring crt = certfile("test.crt"),
+ sstring key = certfile("test.key"),
+ tls::client_auth ca = tls::client_auth::NONE,
+ sstring client_crt = {},
+ sstring client_key = {},
+ bool do_read = true,
+ bool use_dh_params = true,
+ tls::dn_callback distinguished_name_callback = {}
+)
+{
+ static const auto port = 4711;
+
+ auto msg = ::make_shared<sstring>(std::move(message));
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ auto server = ::make_shared<seastar::sharded<echoserver>>();
+ auto addr = ::make_ipv4_address( {0x7f000001, port});
+
+ assert(do_read || loops == 1);
+
+ future<> f = make_ready_future();
+
+ if (!client_crt.empty() && !client_key.empty()) {
+ f = certs->set_x509_key_file(client_crt, client_key, tls::x509_crt_format::PEM);
+ if (distinguished_name_callback) {
+ certs->set_dn_verification_callback(std::move(distinguished_name_callback));
+ }
+ }
+
+ return f.then([=] {
+ return certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
+ }).then([=] {
+ return server->start(msg->size(), use_dh_params).then([=]() {
+ sstring server_trust;
+ if (ca != tls::client_auth::NONE) {
+ server_trust = trust;
+ }
+ return server->invoke_on_all(&echoserver::listen, addr, crt, key, ca, server_trust);
+ }).then([=] {
+ return tls::connect(certs, addr, name).then([loops, msg, do_read](::connected_socket s) {
+ auto strms = ::make_lw_shared<streams>(std::move(s));
+ auto range = boost::irange(0, loops);
+ return do_for_each(range, [strms, msg](auto) {
+ auto f = strms->out.write(*msg);
+ return f.then([strms, msg]() {
+ return strms->out.flush().then([strms, msg] {
+ return strms->in.read_exactly(msg->size()).then([msg](temporary_buffer<char> buf) {
+ if (buf.empty()) {
+ throw std::runtime_error("Unexpected EOF");
+ }
+ sstring tmp(buf.begin(), buf.end());
+ BOOST_CHECK(*msg == tmp);
+ });
+ });
+ });
+ }).then_wrapped([strms, do_read] (future<> f1) {
+ // Always call close()
+ return (do_read ? strms->out.close() : make_ready_future<>()).then_wrapped([strms, f1 = std::move(f1)] (future<> f2) mutable {
+ // Verification errors will be reported by the call to output_stream::close(),
+ // which waits for the flush to actually happen. They can also be reported by the
+ // input_stream::read_exactly() call. We want to keep only one and avoid nested exception mess.
+ if (f1.failed()) {
+ (void)f2.handle_exception([] (std::exception_ptr ignored) { });
+ return std::move(f1);
+ }
+ (void)f1.handle_exception([] (std::exception_ptr ignored) { });
+ return f2;
+ }).finally([strms] { });
+ });
+ });
+ }).finally([server] {
+ return server->stop().finally([server]{});
+ });
+ });
+}
+
+/*
+ * Certificates:
+ *
+ * make -f tests/unit/mkcert.gmk domain=scylladb.org server=test
+ *
+ * -> test.crt
+ * test.csr
+ * catest.pem
+ * catest.key
+ *
+ * catest == snakeoil root authority for these self-signed certs
+ *
+ */
+SEASTAR_TEST_CASE(test_simple_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org");
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_again) {
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org");
+}
+
+#if GNUTLS_VERSION_NUMBER >= 0x030600
+// Test #769 - do not set dh_params in server certs - let gnutls negotiate.
+SEASTAR_TEST_CASE(test_simple_server_default_dhparams) {
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org",
+ certfile("test.crt"), certfile("test.key"), tls::client_auth::NONE,
+ {}, {}, true, /* use_dh_params */ false
+ );
+}
+#endif
+
+SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail) {
+ // Load a real trust authority here, which out certs are _not_ signed with.
+ return run_echo_test(message, 1, certfile("tls-ca-bundle.pem"), {}).then([] {
+ BOOST_FAIL("Should have gotten validation error");
+ }).handle_exception([](auto ep) {
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error&) {
+ // ok.
+ } catch (...) {
+ BOOST_FAIL("Unexpected exception");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail_name) {
+ // Use trust store with our signer, but wrong host name
+ return run_echo_test(message, 1, certfile("tls-ca-bundle.pem"), "nils.holgersson.gov").then([] {
+ BOOST_FAIL("Should have gotten validation error");
+ }).handle_exception([](auto ep) {
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error&) {
+ // ok.
+ } catch (...) {
+ BOOST_FAIL("Unexpected exception");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_large_message_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ sstring msg = uninitialized_string(512 * 1024);
+ for (size_t i = 0; i < msg.size(); ++i) {
+ msg[i] = '0' + char(i % 30);
+ }
+ return run_echo_test(std::move(msg), 20, certfile("catest.pem"), "test.scylladb.org");
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_fail_client_auth) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ // Server will require certificate auth. We supply none, so should fail connection
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE).then([] {
+ BOOST_FAIL("Expected exception");
+ }).handle_exception([](auto ep) {
+ // ok.
+ });
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ // Server will require certificate auth. We supply one, so should succeed with connection
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"));
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth_with_dn_callback) {
+ // In addition to the above test, the certificate's subject and issuer
+ // Distinguished Names (DNs) will be checked for the occurrence of a specific
+ // substring (in this case, the test.scylladb.org url)
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"), true, true, [](tls::session_type t, sstring subject, sstring issuer) {
+ BOOST_REQUIRE(t == tls::session_type::CLIENT);
+ BOOST_REQUIRE(subject.find("test.scylladb.org") != sstring::npos);
+ BOOST_REQUIRE(issuer.find("test.scylladb.org") != sstring::npos);
+ });
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth_dn_callback_fails) {
+ // Test throwing an exception from within the Distinguished Names callback
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"), true, true, [](tls::session_type, sstring, sstring) {
+ throw tls::verification_error("to test throwing from within the callback");
+ }).then([] {
+ BOOST_FAIL("Should have gotten a verification_error exception");
+ }).handle_exception([](auto) {
+ // ok.
+ });
+}
+
+SEASTAR_TEST_CASE(test_many_large_message_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ sstring msg = uninitialized_string(4 * 1024 * 1024);
+ for (size_t i = 0; i < msg.size(); ++i) {
+ msg[i] = '0' + char(i % 30);
+ }
+ // Sending a huge-ish message a and immediately closing the session (see params)
+ // provokes case where tls::vec_push entered race and asserted on broken IO state
+ // machine.
+ auto range = boost::irange(0, 20);
+ return do_for_each(range, [msg = std::move(msg)](auto) {
+ return run_echo_test(std::move(msg), 1, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::NONE, {}, {}, false);
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_close_timout) {
+ tls::credentials_builder b;
+
+ b.set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
+ b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+ b.set_dh_level();
+ b.set_system_trust().get();
+
+ auto creds = b.build_certificate_credentials();
+ auto serv = b.build_server_credentials();
+
+ semaphore sem(0);
+
+ class my_loopback_connected_socket_impl : public loopback_connected_socket_impl {
+ public:
+ semaphore& _sem;
+ bool _close = false;
+
+ my_loopback_connected_socket_impl(semaphore& s, lw_shared_ptr<loopback_buffer> tx, lw_shared_ptr<loopback_buffer> rx)
+ : loopback_connected_socket_impl(tx, rx)
+ , _sem(s)
+ {}
+ ~my_loopback_connected_socket_impl() {
+ _sem.signal();
+ }
+ class my_sink_impl : public data_sink_impl {
+ public:
+ data_sink _sink;
+ my_loopback_connected_socket_impl& _impl;
+ promise<> _p;
+ my_sink_impl(data_sink sink, my_loopback_connected_socket_impl& impl)
+ : _sink(std::move(sink))
+ , _impl(impl)
+ {}
+ future<> flush() override {
+ return _sink.flush();
+ }
+ using data_sink_impl::put;
+ future<> put(net::packet p) override {
+ if (std::exchange(_impl._close, false)) {
+ return _p.get_future().then([this, p = std::move(p)]() mutable {
+ return put(std::move(p));
+ });
+ }
+ return _sink.put(std::move(p));
+ }
+ future<> close() override {
+ _p.set_value();
+ return make_ready_future<>();
+ }
+ };
+ data_sink sink() override {
+ return data_sink(std::make_unique<my_sink_impl>(loopback_connected_socket_impl::sink(), *this));
+ }
+ };
+
+ auto constexpr iterations = 500;
+
+ for (int i = 0; i < iterations; ++i) {
+ auto b1 = ::make_lw_shared<loopback_buffer>(nullptr, loopback_buffer::type::SERVER_TX);
+ auto b2 = ::make_lw_shared<loopback_buffer>(nullptr, loopback_buffer::type::CLIENT_TX);
+ auto ssi = std::make_unique<my_loopback_connected_socket_impl>(sem, b1, b2);
+ auto csi = std::make_unique<my_loopback_connected_socket_impl>(sem, b2, b1);
+
+ auto& ssir = *ssi;
+ auto& csir = *csi;
+
+ auto ss = tls::wrap_server(serv, connected_socket(std::move(ssi))).get0();
+ auto cs = tls::wrap_client(creds, connected_socket(std::move(csi))).get0();
+
+ auto os = cs.output().detach();
+ auto is = ss.input();
+
+ auto f1 = os.put(temporary_buffer<char>(10));
+ auto f2 = is.read();
+ f1.get();
+ f2.get();
+
+ // block further writes
+ ssir._close = true;
+ csir._close = true;
+ }
+
+ sem.wait(2 * iterations).get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_reload_certificates) {
+ tmpdir tmp;
+
+ namespace fs = std::filesystem;
+
+ // copy the wrong certs. We don't trust these
+ // blocking calls, but this is a test and seastar does not have a copy
+ // util and I am lazy...
+ fs::copy_file(certfile("other.crt"), tmp.path() / "test.crt");
+ fs::copy_file(certfile("other.key"), tmp.path() / "test.key");
+
+ auto cert = (tmp.path() / "test.crt").native();
+ auto key = (tmp.path() / "test.key").native();
+ std::unordered_set<sstring> changed;
+ promise<> p;
+
+ tls::credentials_builder b;
+ b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
+ b.set_dh_level();
+
+ auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) {
+ if (ep) {
+ return;
+ }
+ changed.insert(files.begin(), files.end());
+ if (changed.count(cert) && changed.count(key)) {
+ p.set_value();
+ }
+ }).get0();
+
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = tls::listen(certs, addr, opts);
+
+ tls::credentials_builder b2;
+ b2.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+
+ {
+ auto sa = server.accept();
+ auto c = tls::connect(b2.build_certificate_credentials(), addr).get0();
+ auto s = sa.get0();
+ auto in = s.connection.input();
+
+ output_stream<char> out(c.output().detach(), 4096);
+
+ try {
+ out.write("apa").get();
+ auto f = out.flush();
+ auto f2 = in.read();
+
+ try {
+ f.get();
+ BOOST_FAIL("should not reach");
+ } catch (tls::verification_error&) {
+ // ok
+ }
+ try {
+ out.close().get();
+ } catch (...) {
+ }
+
+ try {
+ f2.get();
+ BOOST_FAIL("should not reach");
+ } catch (...) {
+ // ok
+ }
+ try {
+ in.close().get();
+ } catch (...) {
+ }
+ } catch (tls::verification_error&) {
+ // ok
+ }
+ }
+
+ // copy the right (trusted) certs over the old ones.
+ fs::copy_file(certfile("test.crt"), tmp.path() / "test0.crt");
+ fs::copy_file(certfile("test.key"), tmp.path() / "test0.key");
+
+ rename_file((tmp.path() / "test0.crt").native(), (tmp.path() / "test.crt").native()).get();
+ rename_file((tmp.path() / "test0.key").native(), (tmp.path() / "test.key").native()).get();
+
+ p.get_future().get();
+
+ // now it should work
+ {
+ auto sa = server.accept();
+ auto c = tls::connect(b2.build_certificate_credentials(), addr).get0();
+ auto s = sa.get0();
+ auto in = s.connection.input();
+
+ output_stream<char> out(c.output().detach(), 4096);
+
+ out.write("apa").get();
+ auto f = out.flush();
+ auto buf = in.read().get0();
+ f.get();
+ out.close().get();
+ in.read().get(); // ignore - just want eof
+ in.close().get();
+
+ BOOST_CHECK_EQUAL(sstring(buf.begin(), buf.end()), "apa");
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_reload_broken_certificates) {
+ tmpdir tmp;
+
+ namespace fs = std::filesystem;
+
+ fs::copy_file(certfile("test.crt"), tmp.path() / "test.crt");
+ fs::copy_file(certfile("test.key"), tmp.path() / "test.key");
+
+ auto cert = (tmp.path() / "test.crt").native();
+ auto key = (tmp.path() / "test.key").native();
+ std::unordered_set<sstring> changed;
+ promise<> p;
+
+ tls::credentials_builder b;
+ b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
+ b.set_dh_level();
+
+ queue<std::exception_ptr> q(10);
+
+ auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) {
+ if (ep) {
+ q.push(std::move(ep));
+ return;
+ }
+ changed.insert(files.begin(), files.end());
+ if (changed.count(cert) && changed.count(key)) {
+ p.set_value();
+ }
+ }).get0();
+
+ // very intentionally use blocking calls. We want all our modifications to happen
+ // before any other continuation is allowed to process.
+
+ fs::remove(cert);
+ fs::remove(key);
+
+ // should get one or two exceptions
+ q.pop_eventually().get();
+
+ fs::copy_file(certfile("test.crt"), cert);
+ fs::copy_file(certfile("test.key"), key);
+
+ // now it should reload
+ p.get_future().get();
+}
diff --git a/src/seastar/tests/unit/tmpdir.hh b/src/seastar/tests/unit/tmpdir.hh
new file mode 100644
index 000000000..0da29a7f1
--- /dev/null
+++ b/src/seastar/tests/unit/tmpdir.hh
@@ -0,0 +1,49 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB Ltd.
+ */
+
+#include <seastar/util/tmp_file.hh>
+
+namespace seastar {
+
+/**
+ * Temp dir helper for RAII usage when doing tests
+ * in seastar threads. Will not work in "normal" mode.
+ * Just use tmp_dir::do_with for that.
+ */
+class tmpdir {
+ seastar::tmp_dir _tmp;
+public:
+ tmpdir(tmpdir&&) = default;
+ tmpdir(const tmpdir&) = delete;
+
+ tmpdir(const sstring& name = sstring(seastar::default_tmpdir()) + "/testXXXX") {
+ _tmp.create(std::filesystem::path(name)).get();
+ }
+ ~tmpdir() {
+ _tmp.remove().get();
+ }
+ auto path() const {
+ return _tmp.get_path();
+ }
+};
+
+}
diff --git a/src/seastar/tests/unit/tuple_utils_test.cc b/src/seastar/tests/unit/tuple_utils_test.cc
new file mode 100644
index 000000000..6a278f01c
--- /dev/null
+++ b/src/seastar/tests/unit/tuple_utils_test.cc
@@ -0,0 +1,99 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <seastar/util/tuple_utils.hh>
+
+#include <boost/test/included/unit_test.hpp>
+
+#include <sstream>
+#include <type_traits>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(map) {
+ const auto pairs = tuple_map(std::make_tuple(10, 5.5, true), [](auto&& e) { return std::make_tuple(e, e); });
+
+ BOOST_REQUIRE(pairs == std::make_tuple(std::make_tuple(10, 10),
+ std::make_tuple(5.5, 5.5),
+ std::make_tuple(true, true)));
+}
+
+BOOST_AUTO_TEST_CASE(for_each) {
+ std::ostringstream os;
+
+ tuple_for_each(std::make_tuple('a', 10, false, 5.4), [&os](auto&& e) {
+ os << e;
+ });
+
+ BOOST_REQUIRE_EQUAL(os.str(), "a1005.4");
+}
+
+namespace {
+
+template <typename T>
+struct transform_type final {
+ using type = T;
+};
+
+template <>
+struct transform_type<bool> final { using type = int; };
+
+template <>
+struct transform_type<double> final { using type = char; };
+
+}
+
+BOOST_AUTO_TEST_CASE(map_types) {
+ using before_tuple = std::tuple<double, bool, const char*>;
+ using after_tuple = typename tuple_map_types<transform_type, before_tuple>::type;
+
+ BOOST_REQUIRE((std::is_same<after_tuple, std::tuple<char, int, const char*>>::value));
+}
+
+namespace {
+
+//
+// Strip all `bool` fields.
+//
+
+template <typename>
+struct keep_type final {
+ static constexpr auto value = true;
+};
+
+template <>
+struct keep_type<bool> final {
+ static constexpr auto value = false;
+};
+
+}
+
+BOOST_AUTO_TEST_CASE(filter_by_type) {
+ using before_tuple = std::tuple<bool, int, bool, double, bool, char>;
+
+ const auto t = tuple_filter_by_type<keep_type>(before_tuple{true, 10, false, 5.5, true, 'a'});
+ using filtered_type = typename std::decay<decltype(t)>::type;
+
+ BOOST_REQUIRE((std::is_same<filtered_type, std::tuple<int, double, char>>::value));
+ BOOST_REQUIRE(t == std::make_tuple(10, 5.5, 'a'));
+}
diff --git a/src/seastar/tests/unit/uname_test.cc b/src/seastar/tests/unit/uname_test.cc
new file mode 100644
index 000000000..1e1b1a40c
--- /dev/null
+++ b/src/seastar/tests/unit/uname_test.cc
@@ -0,0 +1,76 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2019 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include "../../src/core/uname.hh"
+
+using namespace seastar::internal;
+
+BOOST_AUTO_TEST_CASE(test_nowait_aio_fix) {
+ auto check = [] (const char* uname) {
+ return parse_uname(uname).whitelisted({"5.1", "5.0.8", "4.19.35", "4.14.112"});
+ };
+ BOOST_REQUIRE_EQUAL(check("5.1.0"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.1"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.1-44.distro"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.1-44.7.distro"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.0"), false);
+ BOOST_REQUIRE_EQUAL(check("5.0.7"), false);
+ BOOST_REQUIRE_EQUAL(check("5.0.7-55.el19"), false);
+ BOOST_REQUIRE_EQUAL(check("5.0.8"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.9"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.8-200.fedora"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.9-200.fedora"), true);
+ BOOST_REQUIRE_EQUAL(check("5.2.0"), true);
+ BOOST_REQUIRE_EQUAL(check("5.2.9"), true);
+ BOOST_REQUIRE_EQUAL(check("5.2.9-77.el153"), true);
+ BOOST_REQUIRE_EQUAL(check("6.0.0"), true);
+ BOOST_REQUIRE_EQUAL(check("3.9.0"), false);
+ BOOST_REQUIRE_EQUAL(check("4.19"), false);
+ BOOST_REQUIRE_EQUAL(check("4.19.34"), false);
+ BOOST_REQUIRE_EQUAL(check("4.19.35"), true);
+ BOOST_REQUIRE_EQUAL(check("4.19.36"), true);
+ BOOST_REQUIRE_EQUAL(check("4.20.36"), false);
+ BOOST_REQUIRE_EQUAL(check("4.14.111"), false);
+ BOOST_REQUIRE_EQUAL(check("4.14.112"), true);
+ BOOST_REQUIRE_EQUAL(check("4.14.113"), true);
+}
+
+
+BOOST_AUTO_TEST_CASE(test_xfs_concurrency_fix) {
+ auto check = [] (const char* uname) {
+ return parse_uname(uname).whitelisted({"3.15", "3.10.0-325.el7"});
+ };
+ BOOST_REQUIRE_EQUAL(check("3.15.0"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.0"), true);
+ BOOST_REQUIRE_EQUAL(check("3.14.0"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.14"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325.ubuntu"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325.el7"), true);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-326.el7"), true);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-324.el7"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325.665.el7"), true);
+}
diff --git a/src/seastar/tests/unit/unix_domain_test.cc b/src/seastar/tests/unit/unix_domain_test.cc
new file mode 100644
index 000000000..fb71e3f3f
--- /dev/null
+++ b/src/seastar/tests/unit/unix_domain_test.cc
@@ -0,0 +1,234 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+#include <seastar/util/std-compat.hh>
+
+using namespace seastar;
+using std::string;
+using namespace std::string_literals;
+using namespace std::chrono_literals;
+
+static logger iplog("unix_domain");
+
+class ud_server_client {
+public:
+ ud_server_client(string server_path, std::optional<string> client_path, int rounds) :
+ ud_server_client(server_path, client_path, rounds, 0) {};
+
+ ud_server_client(string server_path, std::optional<string> client_path, int rounds,
+ int abort_run) :
+ server_addr{unix_domain_addr{server_path}}, client_path{client_path},
+ rounds{rounds},
+ rounds_left{rounds}, abort_after{abort_run} {}
+
+ future<> run();
+ ud_server_client(ud_server_client&&) = default;
+ ud_server_client(const ud_server_client&) = delete;
+
+private:
+ const string test_message{"are you still the same?"s};
+ future<> init_server();
+ future<> client_round();
+ const socket_address server_addr;
+
+ const std::optional<string> client_path;
+ server_socket server;
+ const int rounds;
+ int rounds_left;
+ server_socket* lstn_sock;
+ seastar::thread th;
+ int abort_after; // if set - force the listening socket down after that number of rounds
+ bool planned_abort{false}; // set when abort_accept() is called
+};
+
+future<> ud_server_client::init_server() {
+ return do_with(seastar::listen(server_addr), [this](server_socket& lstn) mutable {
+
+ lstn_sock = &lstn; // required when aborting (on some tests)
+
+ // start the clients here, where we know the server is listening
+
+ th = seastar::thread([this]{
+ for (int i=0; i<rounds; ++i) {
+ if (abort_after) {
+ if (--abort_after == 0) {
+ planned_abort = true;
+ lstn_sock->abort_accept();
+ break;
+ }
+ }
+ (void)client_round().get0();
+ }
+ });
+
+ return do_until([this](){return rounds_left<=0;}, [&lstn,this]() {
+ return lstn.accept().then([this](accept_result from_accept) {
+ connected_socket cn = std::move(from_accept.connection);
+ socket_address cn_addr = std::move(from_accept.remote_address);
+ --rounds_left;
+ // verify the client address
+ if (client_path) {
+ socket_address tmmp{unix_domain_addr{*client_path}};
+ BOOST_REQUIRE_EQUAL(cn_addr, socket_address{unix_domain_addr{*client_path}});
+ }
+
+ return do_with(cn.input(), cn.output(), [](auto& inp, auto& out) {
+
+ return inp.read().then([&out](auto bb) {
+ string ans = "-"s;
+ if (bb && bb.size()) {
+ ans = "+"s + string{bb.get(), bb.size()};
+ }
+ return out.write(ans).then([&out](){return out.flush();}).
+ then([&out](){return out.close();});
+ }).then([&inp]() { return inp.close(); }).
+ then([]() { return make_ready_future<>(); });
+
+ }).then([]{ return make_ready_future<>();});
+ });
+ }).handle_exception([this](auto e) {
+ // OK to get here only if the test was a "planned abort" one
+ if (!planned_abort) {
+ std::rethrow_exception(e);
+ }
+ }).finally([this]{
+ return th.join();
+ });
+ });
+}
+
+/// Send a message to the server, and expect (almost) the same string back.
+/// If 'client_path' is set, the client binds to the named path.
+future<> ud_server_client::client_round() {
+ auto cc = client_path ?
+ engine().net().connect(server_addr, socket_address{unix_domain_addr{*client_path}}).get0() :
+ engine().net().connect(server_addr).get0();
+
+ return do_with(cc.input(), cc.output(), [this](auto& inp, auto& out) {
+
+ return out.write(test_message).then(
+ [&out](){ return out.flush(); }).then(
+ [&inp](){ return inp.read(); }).then(
+ [this,&inp](auto bb){
+ BOOST_REQUIRE_EQUAL(std::string_view(bb.begin(), bb.size()), "+"s+test_message);
+ return inp.close();
+ }).then([&out](){return out.close();}).then(
+ []{ return make_ready_future<>(); });
+ });
+
+}
+
+future<> ud_server_client::run() {
+ return async([this] {
+ auto serverfut = init_server();
+ (void)serverfut.get();
+ });
+
+}
+
+// testing the various address types, both on the server and on the
+// client side
+
+SEASTAR_TEST_CASE(unixdomain_server) {
+ system("rm -f /tmp/ry");
+ ud_server_client uds("/tmp/ry", std::nullopt, 3);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(unixdomain_abs) {
+ char sv_name[]{'\0', '1', '1', '1'};
+ //ud_server_client uds(string{"\0111",4}, string{"\0112",4}, 1);
+ ud_server_client uds(string{sv_name,4}, std::nullopt, 4);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+ //return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(unixdomain_abs_bind) {
+ char sv_name[]{'\0', '1', '1', '1'};
+ char cl_name[]{'\0', '1', '1', '2'};
+ ud_server_client uds(string{sv_name,4}, string{cl_name,4}, 1);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+SEASTAR_TEST_CASE(unixdomain_abs_bind_2) {
+ char sv_name[]{'\0', '1', '\0', '\12', '1'};
+ char cl_name[]{'\0', '1', '\0', '\12', '2'};
+ ud_server_client uds(string{sv_name,5}, string{cl_name,5}, 2);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+SEASTAR_TEST_CASE(unixdomain_text) {
+ socket_address addr1{unix_domain_addr{"abc"}};
+ BOOST_REQUIRE_EQUAL(format("{}", addr1), "abc");
+ socket_address addr2{unix_domain_addr{""}};
+ BOOST_REQUIRE_EQUAL(format("{}", addr2), "{unnamed}");
+ socket_address addr3{unix_domain_addr{std::string("\0abc", 5)}};
+ BOOST_REQUIRE_EQUAL(format("{}", addr3), "@abc_");
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(unixdomain_bind) {
+ system("rm -f 111 112");
+ ud_server_client uds("111"s, "112"s, 1);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+SEASTAR_TEST_CASE(unixdomain_short) {
+ system("rm -f 3");
+ ud_server_client uds("3"s, std::nullopt, 10);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+// test our ability to abort the accept()'ing on a socket.
+// The test covers a specific bug in the handling of abort_accept()
+SEASTAR_TEST_CASE(unixdomain_abort) {
+ std::string sockname{"7"s}; // note: no portable & warnings-free option
+ std::ignore = ::unlink(sockname.c_str());
+ ud_server_client uds(sockname, std::nullopt, 10, 4);
+ return do_with(std::move(uds), [sockname](auto& uds){
+ return uds.run().finally([sockname](){
+ std::ignore = ::unlink(sockname.c_str());
+ return seastar::make_ready_future<>();
+ });
+ });
+}
+
diff --git a/src/seastar/tests/unit/unwind_test.cc b/src/seastar/tests/unit/unwind_test.cc
new file mode 100644
index 000000000..b2f2e7f55
--- /dev/null
+++ b/src/seastar/tests/unit/unwind_test.cc
@@ -0,0 +1,70 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <pthread.h>
+#include <seastar/util/defer.hh>
+#include <seastar/core/posix.hh>
+#include <seastar/util/backtrace.hh>
+
+using namespace seastar;
+
+void foo() {
+ throw std::runtime_error("foo");
+}
+
+// Exploits issue #1725
+BOOST_AUTO_TEST_CASE(test_signal_mask_is_preserved_on_unwinding) {
+ sigset_t mask;
+ sigset_t old;
+ sigfillset(&mask);
+ auto res = ::pthread_sigmask(SIG_SETMASK, &mask, &old);
+ throw_pthread_error(res);
+
+ // Check which signals we actually managed to block
+ res = ::pthread_sigmask(SIG_SETMASK, NULL, &mask);
+ throw_pthread_error(res);
+
+ try {
+ foo();
+ } catch (...) {
+ // ignore
+ }
+
+ // Check backtrace()
+ {
+ size_t count = 0;
+ backtrace([&count] (auto) { ++count; });
+ BOOST_REQUIRE(count > 0);
+ }
+
+ {
+ sigset_t mask2;
+ auto res = ::pthread_sigmask(SIG_SETMASK, &old, &mask2);
+ throw_pthread_error(res);
+
+ for (int i = 1; i < NSIG; ++i) {
+ BOOST_REQUIRE(sigismember(&mask2, i) == sigismember(&mask, i));
+ }
+ }
+}
diff --git a/src/seastar/tests/unit/weak_ptr_test.cc b/src/seastar/tests/unit/weak_ptr_test.cc
new file mode 100644
index 000000000..769c27481
--- /dev/null
+++ b/src/seastar/tests/unit/weak_ptr_test.cc
@@ -0,0 +1,136 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/weak_ptr.hh>
+
+using namespace seastar;
+
+class myclass : public weakly_referencable<myclass> {};
+
+static_assert(std::is_nothrow_default_constructible_v<myclass>);
+
+static_assert(std::is_nothrow_default_constructible_v<weak_ptr<myclass>>);
+static_assert(std::is_nothrow_move_constructible_v<weak_ptr<myclass>>);
+
+BOOST_AUTO_TEST_CASE(test_weak_ptr_is_empty_when_default_initialized) {
+ weak_ptr<myclass> wp;
+ BOOST_REQUIRE(!bool(wp));
+}
+
+BOOST_AUTO_TEST_CASE(test_weak_ptr_is_reset) {
+ auto owning_ptr = std::make_unique<myclass>();
+ weak_ptr<myclass> wp = owning_ptr->weak_from_this();
+ BOOST_REQUIRE(bool(wp));
+ BOOST_REQUIRE(&*wp == &*owning_ptr);
+ owning_ptr = {};
+ BOOST_REQUIRE(!bool(wp));
+}
+
+BOOST_AUTO_TEST_CASE(test_weak_ptr_can_be_moved) {
+ auto owning_ptr = std::make_unique<myclass>();
+ weak_ptr<myclass> wp1 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp2 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp3 = owning_ptr->weak_from_this();
+
+ auto wp3_moved = std::move(wp3);
+ auto wp1_moved = std::move(wp1);
+ auto wp2_moved = std::move(wp2);
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+ BOOST_REQUIRE(bool(wp1_moved));
+ BOOST_REQUIRE(bool(wp2_moved));
+ BOOST_REQUIRE(bool(wp3_moved));
+
+ owning_ptr = {};
+
+ BOOST_REQUIRE(!bool(wp1_moved));
+ BOOST_REQUIRE(!bool(wp2_moved));
+ BOOST_REQUIRE(!bool(wp3_moved));
+}
+
+BOOST_AUTO_TEST_CASE(test_multipe_weak_ptrs) {
+ auto owning_ptr = std::make_unique<myclass>();
+
+ weak_ptr<myclass> wp1 = owning_ptr->weak_from_this();
+ BOOST_REQUIRE(bool(wp1));
+ BOOST_REQUIRE(&*wp1 == &*owning_ptr);
+
+ weak_ptr<myclass> wp2 = owning_ptr->weak_from_this();
+ BOOST_REQUIRE(bool(wp2));
+ BOOST_REQUIRE(&*wp2 == &*owning_ptr);
+
+ owning_ptr = {};
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+}
+
+BOOST_AUTO_TEST_CASE(test_multipe_weak_ptrs_going_away_first) {
+ auto owning_ptr = std::make_unique<myclass>();
+
+ weak_ptr<myclass> wp1 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp2 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp3 = owning_ptr->weak_from_this();
+
+ BOOST_REQUIRE(bool(wp1));
+ BOOST_REQUIRE(bool(wp2));
+ BOOST_REQUIRE(bool(wp3));
+
+ wp2 = {};
+
+ owning_ptr = std::make_unique<myclass>();
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+
+ wp1 = owning_ptr->weak_from_this();
+ wp2 = owning_ptr->weak_from_this();
+ wp3 = owning_ptr->weak_from_this();
+
+ BOOST_REQUIRE(bool(wp1));
+ BOOST_REQUIRE(bool(wp2));
+ BOOST_REQUIRE(bool(wp3));
+
+ wp3 = {};
+ owning_ptr = std::make_unique<myclass>();
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+
+ wp1 = owning_ptr->weak_from_this();
+ wp2 = owning_ptr->weak_from_this();
+ wp3 = owning_ptr->weak_from_this();
+
+ wp1 = {};
+ wp3 = {};
+ owning_ptr = std::make_unique<myclass>();
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+}