summaryrefslogtreecommitdiffstats
path: root/src/seastar/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/seastar/tests')
-rw-r--r--src/seastar/tests/CMakeLists.txt44
-rw-r--r--src/seastar/tests/dist/CMakeLists.txt54
-rw-r--r--src/seastar/tests/dist/consumer/CMakeLists.txt51
-rw-r--r--src/seastar/tests/dist/consumer/Makefile38
-rw-r--r--src/seastar/tests/dist/consumer/cmake_consumer.cc15
-rw-r--r--src/seastar/tests/dist/consumer/cmake_testing_consumer.cc13
-rw-r--r--src/seastar/tests/dist/consumer/pkgconfig_consumer.cc15
-rw-r--r--src/seastar/tests/dist/consumer/pkgconfig_testing_consumer.cc15
-rw-r--r--src/seastar/tests/dist/consumer/recipe/test_dist.cmake43
-rwxr-xr-xsrc/seastar/tests/dist/consumer_test.sh61
-rw-r--r--src/seastar/tests/perf/CMakeLists.txt77
-rw-r--r--src/seastar/tests/perf/fstream_perf.cc79
-rw-r--r--src/seastar/tests/perf/future_util_perf.cc75
-rw-r--r--src/seastar/tests/perf/perf-tests.md106
-rw-r--r--src/seastar/tests/perf/perf_tests.cc362
-rw-r--r--src/seastar/tests/perf/perf_tests.hh230
-rw-r--r--src/seastar/tests/unit/CMakeLists.txt310
-rw-r--r--src/seastar/tests/unit/abort_source_test.cc77
-rw-r--r--src/seastar/tests/unit/alien_test.cc106
-rw-r--r--src/seastar/tests/unit/alloc_test.cc69
-rw-r--r--src/seastar/tests/unit/allocator_test.cc235
-rw-r--r--src/seastar/tests/unit/catest.key51
-rw-r--r--src/seastar/tests/unit/catest.pem33
-rw-r--r--src/seastar/tests/unit/checked_ptr_test.cc107
-rw-r--r--src/seastar/tests/unit/chunked_fifo_test.cc338
-rw-r--r--src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc124
-rw-r--r--src/seastar/tests/unit/circular_buffer_test.cc109
-rw-r--r--src/seastar/tests/unit/connect_test.cc75
-rw-r--r--src/seastar/tests/unit/defer_test.cc76
-rw-r--r--src/seastar/tests/unit/directory_test.cc55
-rw-r--r--src/seastar/tests/unit/distributed_test.cc181
-rw-r--r--src/seastar/tests/unit/dns_test.cc134
-rw-r--r--src/seastar/tests/unit/execution_stage_test.cc336
-rw-r--r--src/seastar/tests/unit/expiring_fifo_test.cc189
-rw-r--r--src/seastar/tests/unit/fair_queue_test.cc379
-rw-r--r--src/seastar/tests/unit/file_io_test.cc143
-rw-r--r--src/seastar/tests/unit/foreign_ptr_test.cc110
-rw-r--r--src/seastar/tests/unit/fstream_test.cc510
-rw-r--r--src/seastar/tests/unit/futures_test.cc955
-rw-r--r--src/seastar/tests/unit/httpd_test.cc641
-rw-r--r--src/seastar/tests/unit/json_formatter_test.cc54
-rw-r--r--src/seastar/tests/unit/loopback_socket.hh258
-rw-r--r--src/seastar/tests/unit/lowres_clock_test.cc117
-rw-r--r--src/seastar/tests/unit/mkcert.gmk91
-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/noncopyable_function_test.cc87
-rw-r--r--src/seastar/tests/unit/output_stream_test.cc131
-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/rpc_test.cc547
-rw-r--r--src/seastar/tests/unit/semaphore_test.cc249
-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.cc80
-rw-r--r--src/seastar/tests/unit/sstring_test.cc135
-rw-r--r--src/seastar/tests/unit/stall_detector_test.cc83
-rw-r--r--src/seastar/tests/unit/test.crl13
-rw-r--r--src/seastar/tests/unit/test.crt33
-rw-r--r--src/seastar/tests/unit/test.csr30
-rw-r--r--src/seastar/tests/unit/test.key51
-rw-r--r--src/seastar/tests/unit/thread_context_switch_test.cc95
-rw-r--r--src/seastar/tests/unit/thread_test.cc170
-rw-r--r--src/seastar/tests/unit/timer_test.cc105
-rw-r--r--src/seastar/tests/unit/tls-ca-bundle.pem4195
-rw-r--r--src/seastar/tests/unit/tls_test.cc532
-rw-r--r--src/seastar/tests/unit/tuple_utils_test.cc99
-rw-r--r--src/seastar/tests/unit/unwind_test.cc70
-rw-r--r--src/seastar/tests/unit/weak_ptr_test.cc131
72 files changed, 14949 insertions, 0 deletions
diff --git a/src/seastar/tests/CMakeLists.txt b/src/seastar/tests/CMakeLists.txt
new file mode 100644
index 00000000..1fe24ce6
--- /dev/null
+++ b/src/seastar/tests/CMakeLists.txt
@@ -0,0 +1,44 @@
+#
+# 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 ()
+
+# Distribution tests.
+if (Seastar_INSTALL)
+ add_subdirectory (dist)
+endif ()
+
+# Performance tests.
+add_subdirectory (perf)
+
+# Unit tests.
+add_subdirectory (unit)
diff --git a/src/seastar/tests/dist/CMakeLists.txt b/src/seastar/tests/dist/CMakeLists.txt
new file mode 100644
index 00000000..e8368741
--- /dev/null
+++ b/src/seastar/tests/dist/CMakeLists.txt
@@ -0,0 +1,54 @@
+#
+# 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.
+#
+
+add_custom_command (
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/consumer
+ DEPENDS
+ ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
+ ${CMAKE_CURRENT_SOURCE_DIR}/consumer/CMakeLists.txt
+ ${CMAKE_CURRENT_SOURCE_DIR}/consumer/Makefile
+ ${CMAKE_CURRENT_SOURCE_DIR}/consumer/cmake_consumer.cc
+ ${CMAKE_CURRENT_SOURCE_DIR}/consumer/pkgconfig_consumer.cc
+ ${CMAKE_CURRENT_SOURCE_DIR}/consumer/recipe/test_dist.cmake
+ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/consumer ${CMAKE_CURRENT_BINARY_DIR}/consumer)
+
+configure_file (
+ ${Seastar_SOURCE_DIR}/cooking.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/consumer/cooking.sh
+ COPYONLY)
+
+add_custom_target (test_dist_consumer_test_run
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/consumer
+ COMMAND
+ ${CMAKE_COMMAND} -E env
+ CONSUMER_SOURCE_DIR=${CMAKE_CURRENT_BINARY_DIR}/consumer
+ SEASTAR_SOURCE_DIR=${Seastar_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/consumer_test.sh
+ USES_TERMINAL)
+
+add_test (
+ NAME Seastar.dist.consumer
+ COMMAND ${CMAKE_COMMAND} --build ${Seastar_BINARY_DIR} --target test_dist_consumer_test_run)
+
+add_custom_target (test_dist
+ COMMAND ctest --verbose -R Seastar.dist
+ USES_TERMINAL)
diff --git a/src/seastar/tests/dist/consumer/CMakeLists.txt b/src/seastar/tests/dist/consumer/CMakeLists.txt
new file mode 100644
index 00000000..4130239a
--- /dev/null
+++ b/src/seastar/tests/dist/consumer/CMakeLists.txt
@@ -0,0 +1,51 @@
+#
+# 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.
+#
+
+cmake_minimum_required (VERSION 3.5)
+
+list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
+include (Cooking OPTIONAL)
+
+project (SeastarConsumer)
+
+find_package (Boost 1.64.0 REQUIRED
+ COMPONENTS unit_test_framework)
+
+find_package (Seastar 1.0 REQUIRED)
+
+add_executable (cmake_consumer
+ cmake_consumer.cc)
+
+target_link_libraries (cmake_consumer
+ PRIVATE Seastar::seastar)
+
+add_executable (cmake_testing_consumer
+ cmake_testing_consumer.cc)
+
+target_compile_definitions (cmake_testing_consumer
+ PRIVATE SEASTAR_TESTING_MAIN)
+
+target_link_libraries (cmake_testing_consumer
+ PRIVATE
+ Boost::unit_test_framework
+ Seastar::seastar
+ Seastar::seastar_testing)
diff --git a/src/seastar/tests/dist/consumer/Makefile b/src/seastar/tests/dist/consumer/Makefile
new file mode 100644
index 00000000..d84aa1e7
--- /dev/null
+++ b/src/seastar/tests/dist/consumer/Makefile
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+# Required environmental variables:
+#
+# BUILD_DIR
+
+.PHONY: prepare
+
+all: pkgconfig_consumer pkgconfig_testing_consumer
+
+pkgconfig_consumer: pkgconfig_consumer.cc prepare
+ $(CXX) $< `pkg-config seastar --cflags --libs --static` -o $(BUILD_DIR)/$@
+
+pkgconfig_testing_consumer: pkgconfig_testing_consumer.cc prepare
+ $(CXX) $< `pkg-config seastar seastar-testing --cflags --libs --static` -o $(BUILD_DIR)/$@
+
+prepare:
+ mkdir -p $(BUILD_DIR)
diff --git a/src/seastar/tests/dist/consumer/cmake_consumer.cc b/src/seastar/tests/dist/consumer/cmake_consumer.cc
new file mode 100644
index 00000000..ebdc060d
--- /dev/null
+++ b/src/seastar/tests/dist/consumer/cmake_consumer.cc
@@ -0,0 +1,15 @@
+#include <iostream>
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/future.hh>
+
+namespace sr = seastar;
+
+int main(int argc, char** argv) {
+ sr::app_template app;
+
+ return app.run(argc, argv, [] {
+ std::cout << "\"Hello\" from the Seastar CMake consumer!\n";
+ return sr::make_ready_future<>();
+ });
+}
diff --git a/src/seastar/tests/dist/consumer/cmake_testing_consumer.cc b/src/seastar/tests/dist/consumer/cmake_testing_consumer.cc
new file mode 100644
index 00000000..a2dc6656
--- /dev/null
+++ b/src/seastar/tests/dist/consumer/cmake_testing_consumer.cc
@@ -0,0 +1,13 @@
+#include <iostream>
+
+#include <seastar/core/future.hh>
+#include <seastar/testing/test_case.hh>
+
+namespace sr = seastar;
+
+SEASTAR_TEST_CASE(greeting) {
+ return sr::make_ready_future<>().then([] {
+ BOOST_REQUIRE(true);
+ std::cout << "\"Hello\" from the Seastar CMake testing consumer!\n";
+ });
+}
diff --git a/src/seastar/tests/dist/consumer/pkgconfig_consumer.cc b/src/seastar/tests/dist/consumer/pkgconfig_consumer.cc
new file mode 100644
index 00000000..a0cce755
--- /dev/null
+++ b/src/seastar/tests/dist/consumer/pkgconfig_consumer.cc
@@ -0,0 +1,15 @@
+#include <iostream>
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/future.hh>
+
+namespace sr = seastar;
+
+int main(int argc, char** argv) {
+ sr::app_template app;
+
+ return app.run(argc, argv, [] {
+ std::cout << "\"Hello\" from the Seastar pkg-config consumer!\n";
+ return sr::make_ready_future<>();
+ });
+}
diff --git a/src/seastar/tests/dist/consumer/pkgconfig_testing_consumer.cc b/src/seastar/tests/dist/consumer/pkgconfig_testing_consumer.cc
new file mode 100644
index 00000000..f3948a31
--- /dev/null
+++ b/src/seastar/tests/dist/consumer/pkgconfig_testing_consumer.cc
@@ -0,0 +1,15 @@
+#define SEASTAR_TESTING_MAIN
+
+#include <iostream>
+
+#include <seastar/core/future.hh>
+#include <seastar/testing/test_case.hh>
+
+namespace sr = seastar;
+
+SEASTAR_TEST_CASE(greeting) {
+ return sr::make_ready_future<>().then([] {
+ BOOST_REQUIRE(true);
+ std::cout << "\"Hello\" from the Seastar pkg-config testing consumer!\n";
+ });
+}
diff --git a/src/seastar/tests/dist/consumer/recipe/test_dist.cmake b/src/seastar/tests/dist/consumer/recipe/test_dist.cmake
new file mode 100644
index 00000000..9ebf5967
--- /dev/null
+++ b/src/seastar/tests/dist/consumer/recipe/test_dist.cmake
@@ -0,0 +1,43 @@
+cmake_host_system_information (
+ RESULT build_concurrency_factor
+ QUERY NUMBER_OF_LOGICAL_CORES)
+
+set (make_command make -j ${build_concurrency_factor})
+
+cooking_ingredient (Boost
+ EXTERNAL_PROJECT_ARGS
+ # The 1.67.0 release has a bug in Boost Lockfree around a missing header.
+ URL https://dl.bintray.com/boostorg/release/1.64.0/source/boost_1_64_0.tar.gz
+ URL_MD5 319c6ffbbeccc366f14bb68767a6db79
+ PATCH_COMMAND
+ ./bootstrap.sh
+ --prefix=<INSTALL_DIR>
+ --with-libraries=atomic,chrono,date_time,filesystem,program_options,system,test,thread
+ CONFIGURE_COMMAND <DISABLE>
+ BUILD_COMMAND <DISABLE>
+ INSTALL_COMMAND
+ ${CMAKE_COMMAND} -E chdir <SOURCE_DIR>
+ ./b2
+ -j ${build_concurrency_factor}
+ --layout=system
+ --build-dir=<BINARY_DIR>
+ install
+ variant=debug
+ link=shared
+ threading=multi
+ hardcode-dll-paths=true
+ dll-path=<INSTALL_DIR>/lib)
+
+cooking_ingredient (Seastar
+ REQUIRES Boost
+ COOKING_RECIPE dev
+ COOKING_CMAKE_ARGS
+ # Not `lib64`.
+ -DCMAKE_INSTALL_LIBDIR=lib
+ -DSeastar_APPS=OFF
+ -DSeastar_DOCS=OFF
+ -DSeastar_DEMOS=OFF
+ -DSeastar_DPDK=ON
+ -DSeastar_TESTING=OFF
+ EXTERNAL_PROJECT_ARGS
+ SOURCE_DIR $ENV{SEASTAR_SOURCE_DIR})
diff --git a/src/seastar/tests/dist/consumer_test.sh b/src/seastar/tests/dist/consumer_test.sh
new file mode 100755
index 00000000..bf23ccb8
--- /dev/null
+++ b/src/seastar/tests/dist/consumer_test.sh
@@ -0,0 +1,61 @@
+#
+# 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.
+#
+
+# This test expects the following environmental variables to be defined:
+#
+# CONSUMER_SOURCE_DIR
+# SEASTAR_SOURCE_DIR
+#
+
+set -e
+
+cd "${CONSUMER_SOURCE_DIR}"
+./cooking.sh -r test_dist -t Release
+
+#
+# Consume from CMake.
+#
+
+cmake --build build
+build/cmake_consumer
+build/cmake_testing_consumer
+
+ingredients_dir="build/_cooking/installed"
+library_path="${ingredients_dir}"/lib
+
+#
+# Consume Seastar from its build directory with pkg-config.
+#
+
+pkg_config_path="build/_cooking/ingredient/Seastar/build"
+make BUILD_DIR=build-no-inst PKG_CONFIG_PATH="${pkg_config_path}"
+LD_LIBRARY_PATH="${library_path}" build-no-inst/pkgconfig_consumer
+LD_LIBRARY_PATH="${library_path}" build-no-inst/pkgconfig_testing_consumer
+
+#
+# Consume Seastar installed to the file-system, with pkg-config.
+#
+
+pkg_config_path="${library_path}"/pkgconfig
+make BUILD_DIR=build PKG_CONFIG_PATH="${pkg_config_path}"
+LD_LIBRARY_PATH="${library_path}" build/pkgconfig_consumer
+LD_LIBRARY_PATH="${library_path}" build/pkgconfig_testing_consumer
diff --git a/src/seastar/tests/perf/CMakeLists.txt b/src/seastar/tests/perf/CMakeLists.txt
new file mode 100644
index 00000000..bd97681c
--- /dev/null
+++ b/src/seastar/tests/perf/CMakeLists.txt
@@ -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.
+#
+
+add_library (seastar_perf_testing
+ perf_tests.hh
+ perf_tests.cc)
+
+target_link_libraries (seastar_perf_testing
+ PUBLIC
+ fmt::fmt
+ seastar_with_flags)
+
+# 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_with_flags)
+ else ()
+ set (libraries
+ seastar_with_flags
+ 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})
+
+ 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 (future_util
+ SOURCES future_util_perf.cc)
diff --git a/src/seastar/tests/perf/fstream_perf.cc b/src/seastar/tests/perf/fstream_perf.cc
new file mode 100644
index 00000000..b8cf4503
--- /dev/null
+++ b/src/seastar/tests/perf/fstream_perf.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) 2016 ScyllaDB
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/fstream.hh>
+#include <seastar/core/file.hh>
+#include <seastar/core/app-template.hh>
+
+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;
+ auto os = make_file_output_stream(f, foso);
+ 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 00000000..4b7e8f93
--- /dev/null
+++ b/src/seastar/tests/perf/future_util_perf.cc
@@ -0,0 +1,75 @@
+/*
+ * 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 "perf_tests.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 00000000..c42e7be4
--- /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 00000000..2b745fe3
--- /dev/null
+++ b/src/seastar/tests/perf/perf_tests.cc
@@ -0,0 +1,362 @@
+/*
+ * 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 "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>
+
+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/perf_tests.hh b/src/seastar/tests/perf/perf_tests.hh
new file mode 100644
index 00000000..be01298a
--- /dev/null
+++ b/src/seastar/tests/perf/perf_tests.hh
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+#include <fmt/format.h>
+
+#include <seastar/core/future.hh>
+#include <seastar/core/future-util.hh>
+
+
+using namespace seastar;
+
+namespace perf_tests {
+namespace internal {
+
+struct config;
+
+using clock_type = std::chrono::steady_clock;
+
+class performance_test {
+ std::string _test_case;
+ std::string _test_group;
+
+ uint64_t _single_run_iterations = 0;
+ std::atomic<uint64_t> _max_single_run_iterations;
+private:
+ void do_run(const config&);
+protected:
+ [[gnu::always_inline]] [[gnu::hot]]
+ bool stop_iteration() const {
+ return _single_run_iterations >= _max_single_run_iterations.load(std::memory_order_relaxed);
+ }
+
+ [[gnu::always_inline]] [[gnu::hot]]
+ void next_iteration() {
+ _single_run_iterations++;
+ }
+
+ virtual void set_up() = 0;
+ virtual void tear_down() noexcept = 0;
+ virtual future<clock_type::duration> do_single_run() = 0;
+public:
+ performance_test(const std::string& test_case, const std::string& test_group)
+ : _test_case(test_case)
+ , _test_group(test_group)
+ { }
+
+ virtual ~performance_test() = default;
+
+ const std::string& test_case() const { return _test_case; }
+ const std::string& test_group() const { return _test_group; }
+ std::string name() const { return fmt::format("{}.{}", test_group(), test_case()); }
+
+ void run(const config&);
+public:
+ static void register_test(std::unique_ptr<performance_test>);
+};
+
+// Helper for measuring time.
+// Each microbenchmark can either use the default behaviour which measures
+// only the start and stop time of the whole run or manually invoke
+// start_measuring_time() and stop_measuring_time() in order to measure
+// only parts of each iteration.
+class time_measurement {
+ clock_type::time_point _run_start_time;
+ clock_type::time_point _start_time;
+ clock_type::duration _total_time;
+public:
+ [[gnu::always_inline]] [[gnu::hot]]
+ void start_run() {
+ _total_time = { };
+ auto t = clock_type::now();
+ _run_start_time = t;
+ _start_time = t;
+ }
+
+ [[gnu::always_inline]] [[gnu::hot]]
+ clock_type::duration stop_run() {
+ auto t = clock_type::now();
+ if (_start_time == _run_start_time) {
+ return t - _start_time;
+ }
+ return _total_time;
+ }
+
+ [[gnu::always_inline]] [[gnu::hot]]
+ void start_iteration() {
+ _start_time = clock_type::now();
+ }
+
+ [[gnu::always_inline]] [[gnu::hot]]
+ void stop_iteration() {
+ auto t = clock_type::now();
+ _total_time += t - _start_time;
+ }
+};
+
+extern time_measurement measure_time;
+
+namespace {
+
+template<bool Condition, typename TrueFn, typename FalseFn>
+struct do_if_constexpr_ : FalseFn {
+ do_if_constexpr_(TrueFn, FalseFn false_fn) : FalseFn(std::move(false_fn)) { }
+ decltype(auto) operator()() const {
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
+ return FalseFn::operator()(0);
+ }
+};
+template<typename TrueFn, typename FalseFn>
+struct do_if_constexpr_<true, TrueFn, FalseFn> : TrueFn {
+ do_if_constexpr_(TrueFn true_fn, FalseFn) : TrueFn(std::move(true_fn)) { }
+ decltype(auto) operator()() const { return TrueFn::operator()(0); }
+};
+
+template<bool Condition, typename TrueFn, typename FalseFn>
+do_if_constexpr_<Condition, TrueFn, FalseFn> if_constexpr_(TrueFn&& true_fn, FalseFn&& false_fn)
+{
+ return do_if_constexpr_<Condition, TrueFn, FalseFn>(std::forward<TrueFn>(true_fn),
+ std::forward<FalseFn>(false_fn));
+}
+
+}
+
+template<typename Test>
+class concrete_performance_test final : public performance_test {
+ compat::optional<Test> _test;
+protected:
+ virtual void set_up() override {
+ _test.emplace();
+ }
+
+ virtual void tear_down() noexcept override {
+ _test = compat::nullopt;
+ }
+
+ [[gnu::hot]]
+ virtual future<clock_type::duration> do_single_run() override {
+ // Redundant 'this->'s courtesy of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61636
+ return if_constexpr_<is_future<decltype(_test->run())>::value>([&] (auto&&...) {
+ measure_time.start_run();
+ return do_until([this] { return this->stop_iteration(); }, [this] {
+ this->next_iteration();
+ return _test->run();
+ }).then([] {
+ return measure_time.stop_run();
+ });
+ }, [&] (auto&&...) {
+ measure_time.start_run();
+ while (!stop_iteration()) {
+ this->next_iteration();
+ _test->run();
+ }
+ return make_ready_future<clock_type::duration>(measure_time.stop_run());
+ })();
+ }
+public:
+ using performance_test::performance_test;
+};
+
+void register_test(std::unique_ptr<performance_test>);
+
+template<typename Test>
+struct test_registrar {
+ test_registrar(const std::string& test_group, const std::string& test_case) {
+ auto test = std::make_unique<concrete_performance_test<Test>>(test_case, test_group);
+ performance_test::register_test(std::move(test));
+ }
+};
+
+}
+
+[[gnu::always_inline]]
+inline void start_measuring_time()
+{
+ internal::measure_time.start_iteration();
+}
+
+[[gnu::always_inline]]
+inline void stop_measuring_time()
+{
+ internal::measure_time.stop_iteration();
+}
+
+
+template<typename T>
+void do_not_optimize(const T& v)
+{
+ asm volatile("" : : "r,m" (v));
+}
+
+}
+
+#define PERF_TEST_F(test_group, test_case) \
+ struct test_##test_group##_##test_case : test_group { \
+ [[gnu::always_inline]] inline auto run(); \
+ }; \
+ static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
+ test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
+ [[gnu::always_inline]] auto test_##test_group##_##test_case::run()
+
+#define PERF_TEST(test_group, test_case) \
+ struct test_##test_group##_##test_case { \
+ [[gnu::always_inline]] inline auto run(); \
+ }; \
+ static ::perf_tests::internal::test_registrar<test_##test_group##_##test_case> \
+ test_##test_group##_##test_case##_registrar(#test_group, #test_case); \
+ [[gnu::always_inline]] auto test_##test_group##_##test_case::run()
diff --git a/src/seastar/tests/unit/CMakeLists.txt b/src/seastar/tests/unit/CMakeLists.txt
new file mode 100644
index 00000000..171b66e9
--- /dev/null
+++ b/src/seastar/tests/unit/CMakeLists.txt
@@ -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) 2018 Scylladb, Ltd.
+#
+
+# Logical target for all unit tests.
+add_custom_target (unit_tests)
+
+macro (seastar_add_test name)
+ set (args ${ARGN})
+
+ cmake_parse_arguments (parsed_args
+ "NO_SEASTAR_TESTING_LIBRARY"
+ "WORKING_DIRECTORY"
+ "RUN_ARGS;SOURCES"
+ ${args})
+
+ set (command_args "")
+ set (depends_args "")
+
+ if (parsed_args_SOURCES)
+ if (parsed_args_NO_SEASTAR_TESTING_LIBRARY)
+ set (libraries seastar_with_flags)
+
+ if (parsed_args_RUN_ARGS)
+ set (run_args ${parsed_args_RUN_ARGS})
+ else ()
+ set (run_args -c 2)
+ endif ()
+ else ()
+ set (libraries
+ seastar_with_flags
+ seastar_testing)
+
+ if (NOT (Seastar_JENKINS STREQUAL ""))
+ seastar_jenkins_arguments (${name} jenkins_args)
+ else ()
+ set (jenkins_args "")
+ endif ()
+
+ if (parsed_args_RUN_ARGS)
+ set (test_args ${parsed_args_RUN_ARGS})
+ else ()
+ set (test_args -- -c 2)
+ endif ()
+
+ set (run_args
+ ${jenkins_args}
+ ${test_args})
+ endif ()
+
+ 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)
+
+ target_include_directories (${executable_target}
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${Seastar_SOURCE_DIR}/src)
+
+ set_target_properties (${executable_target}
+ PROPERTIES
+ OUTPUT_NAME ${name})
+
+ add_dependencies (unit_tests ${executable_target})
+ list (APPEND command_args COMMAND ${executable_target} ${run_args})
+ endif ()
+
+ set (target test_unit_${name}_run)
+
+ if (parsed_args_WORKING_DIRECTORY)
+ list (APPEND command_args WORKING_DIRECTORY ${parsed_args_WORKING_DIRECTORY})
+ endif ()
+
+ add_custom_target (${target}
+ ${command_args}
+ ${parsed_args_UNPARSED_ARGUMENTS}
+ 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})
+endmacro ()
+
+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_test (alien
+ SOURCES alien_test.cc
+ NO_SEASTAR_TESTING_LIBRARY)
+
+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 (defer
+ SOURCES defer_test.cc)
+
+seastar_add_test (directory
+ SOURCES directory_test.cc
+ NO_SEASTAR_TESTING_LIBRARY)
+
+seastar_add_test (distributed
+ SOURCES distributed_test.cc
+ NO_SEASTAR_TESTING_LIBRARY)
+
+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 (foreign_ptr
+ SOURCES foreign_ptr_test.cc)
+
+seastar_add_test (fstream
+ SOURCES
+ fstream_test.cc
+ mock_file.hh)
+
+seastar_add_test (futures
+ SOURCES futures_test.cc)
+
+seastar_add_test (httpd
+ SOURCES
+ httpd_test.cc
+ loopback_socket.hh)
+
+seastar_add_test (json_formatter
+ SOURCES json_formatter_test.cc)
+
+seastar_add_test (lowres_clock
+ SOURCES lowres_clock_test.cc)
+
+seastar_add_test (net_config
+ SOURCES net_config_test.cc)
+
+seastar_add_test (noncopyable_function
+ SOURCES noncopyable_function_test.cc)
+
+seastar_add_test (output_stream
+ SOURCES output_stream_test.cc)
+
+seastar_add_test (packet
+ SOURCES packet_test.cc)
+
+seastar_add_test (program_options
+ SOURCES program_options_test.cc)
+
+seastar_add_test (queue
+ SOURCES queue_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
+ SOURCES shared_ptr_test.cc)
+
+seastar_add_test (signal
+ SOURCES signal_test.cc)
+
+seastar_add_test (simple_stream
+ 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_test (smp
+ SOURCES smp_test.cc
+ NO_SEASTAR_TESTING_LIBRARY)
+
+seastar_add_test (sstring
+ 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 (thread_context_switch
+ SOURCES thread_context_switch_test.cc
+ NO_SEASTAR_TESTING_LIBRARY)
+
+seastar_add_test (timer
+ SOURCES timer_test.cc
+ NO_SEASTAR_TESTING_LIBRARY)
+
+set (tls_certificate_files
+ catest.key
+ catest.pem
+ tls-ca-bundle.pem
+ test.crl
+ test.crt
+ test.csr
+ test.key)
+
+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})
+
+# TODO: Disabled for now. See GH-514.
+# seastar_add_test (tls
+# DEPENDS ${out_tls_certificate_files}
+# SOURCES tls_test.cc
+# WORKING_DIRECTORY ${Seastar_BINARY_DIR})
+
+seastar_add_test (tuple_utils
+ SOURCES tuple_utils_test.cc)
+
+seastar_add_test (unwind
+ SOURCES unwind_test.cc)
+
+seastar_add_test (weak_ptr
+ SOURCES weak_ptr_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 00000000..9dbc3bb4
--- /dev/null
+++ b/src/seastar/tests/unit/abort_source_test.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) 2017 ScyllaDB
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/gate.hh>
+#include <seastar/core/sleep.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] {
+ 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] {
+ 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([] { });
+ 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)] { });
+}
diff --git a/src/seastar/tests/unit/alien_test.cc b/src/seastar/tests/unit/alien_test.cc
new file mode 100644
index 00000000..cc4e3126
--- /dev/null
+++ b/src/seastar/tests/unit/alien_test.cc
@@ -0,0 +1,106 @@
+// -*- 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/app-template.hh>
+#include <seastar/core/posix.hh>
+#include <seastar/core/reactor.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);
+ }));
+ }
+ 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;
+ seastar::pollable_fd_state alien_done_fds{std::move(alien_done)};
+ 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_fds, &result]() {
+ // check if alien has dismissed me.
+ return seastar::engine().read_some(alien_done_fds, &result, sizeof(result));
+ }).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 00000000..41cb09a4
--- /dev/null
+++ b/src/seastar/tests/unit/alloc_test.cc
@@ -0,0 +1,69 @@
+/*
+ * 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/reactor.hh>
+#include <vector>
+
+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);
+ });
+}
diff --git a/src/seastar/tests/unit/allocator_test.cc b/src/seastar/tests/unit/allocator_test.cc
new file mode 100644
index 00000000..c0d93210
--- /dev/null
+++ b/src/seastar/tests/unit/allocator_test.cc
@@ -0,0 +1,235 @@
+/*
+ * 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 <random>
+#include <cmath>
+#include <iostream>
+#include <iomanip>
+#include <algorithm>
+#include <cassert>
+#include <memory>
+#include <chrono>
+#include <boost/program_options.hpp>
+
+using namespace seastar;
+
+template <size_t N>
+void test_aligned_allocator() {
+#ifdef __cpp_aligned_new
+ using aptr = std::unique_ptr<char[]>;
+ std::vector<aptr> v;
+ for (unsigned i = 0; i < 1000; ++i) {
+ aptr p(new (std::align_val_t(64)) char[N]);
+ assert(reinterpret_cast<uintptr_t>(p.get()) % 64 == 0);
+ v.push_back(std::move(p));
+ }
+#endif
+}
+
+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;
+ 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")
+ ;
+ bpo::variables_map vm;
+ bpo::store(bpo::parse_command_line(ac, av, opts), vm);
+ bpo::notify(vm);
+ test_aligned_allocator<1>();
+ test_aligned_allocator<4>();
+ test_aligned_allocator<80>();
+ test_cpp17_aligned_allocator();
+ std::default_random_engine random_engine;
+ 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: {
+ auto n = std::min<size_t>(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;
+ }
+ 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/catest.key b/src/seastar/tests/unit/catest.key
new file mode 100644
index 00000000..145d2742
--- /dev/null
+++ b/src/seastar/tests/unit/catest.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEA3sDA2IyU3JOmKi4smlra990pXSVgSqIYTEx8FH7FZxx1b7Cz
+9DrPPFOULpWtW0iYs7mw7QyFEc1uUNB/IkRapq4btifG6XRahdhZH7eXi9+rmhhl
+TGqKo9B979L+8tgkpljAVdwaQ101xiQWvTVIF7LvCCW3mma/xB+oc6yDnJjN4lT8
+/4bQzhsG6rGIO2sT34Wj84lurE6NcQSlE7Q5cR2fZxH/CVQuD0Qh+KjWe9g/WAN2
+ZXs3DQ1I5llqBBk4NkyRlm/e6lU9s5nPT2xu0zKCC/NubRxtI3QUWGAVA4F2NDVW
+NYEw6S3wxwxbMCOK/2jgxyGj9A653hk8T5347qVb13jhT5hdFg/o6DpG5rbR/tPO
+enfn6dJvRIILjIDCsWXwrnPr1AN74lP5TyJBTXaLZk70NS5Oh/WCtjhtlBeHxJ6x
+x5u9E+wuNP1a8SRB0xg/dsVlv8Vq/T9pxCbCqXuDokAOMK/gjhmFA4pt7N/SY8tv
+k7Hi7Qa+kaKffWR9K8Vsix1gUYP3aRn/Zwk6drcs3TmakHPVdTJm96zRJw6y1ud3
+d3DTOp/gdVQTdvJ6HoJ1foaIpKvDsSwVRjaCpSfDhuDzVrsCqAlFzcGZX48Hoj4g
+jDhP51S/uYONH/66+jBPup5/QX71ayhnvlFgSEc59LZIXwuvPvNZ2Q5zTMcCAwEA
+AQKCAgEAnO06btSLQuIZ2/lvnsaHILuEGoTsU2fiqk3v1BiDRWL9MNRR0qtjt+JB
+sJft6zM3sNYO2NFLJgGNyA06o494NZjPGQLo1SsNYuHJ19hlQTswD31EUBN23HVT
+Y5NH3Rl8qFw6E8LeFbsi2RYvlthkk52RXDIGKRZd3vNWnzdX+QiFcv/gxLvbenf7
+5XdwvDtxYrUpjbaya11js81L5pe/J/twgxJgk9fkIwkizaVUhScRaRX3YQLvA5jq
+VK2FTKHfwhErN8pURs4Ki+695/xCDzOz/mteziuKj8GFW8VEyJV6CsnQlB0RshSV
+XgVMcJt5nvnp8R+3+4YsV1V78bMe3OC4zH6K2Q/uNRXx8gHiqvZudOmO5FMpNgp6
+exjtzA8DHpqE9bQQEObOhfRMpqZJ+HoGUSyP1sUliTfAiR1F4d5BYTgyPhtAhEFE
+FJdSPPvo82Rl1oZMXZnfpOdWwI96Dvfpa4MA5BYkV+U0uIYr0txvnlWq8CKFTJ2P
+mZS+E7G/Y8FJ00Den7qnxHhc5C37oV+JaO/YhlvQM8LjVVAbfViboi59x+uPn60n
+o2fSBrAORG095tuAXIgkyJbBDvYLR9N6CnBMSSfeNU86o/H/0VtUIby313dKq1fG
++10Tn4SnlZGtngVRZBofD6He1nWvxiG6MTzxbpwPePkC8mz2MkECggEBAPUlg0wE
+pNL3GOlXQsJWjqb/NMlaGnVHRCKdBoO/6KZCY3BFHbviNBqRpZrdgSjxYQ4/kkuY
+fVYXt+c5Bh3cWJq3YuAmhwXCKm1d+fszO02f2bPe4X3Cz8KagoWqpCXQ/8clndtz
+eoo6TQP3cnjh0G4iEevhfyneT1WJDJjUtbrTLJ6LKetY/dXmbBCtBdmBoP+C9zdq
+PRmhYQjtHe+oDfyfYnp+/vo53lN07I9VEWOF1CbwhFd2wi4kO2J42UgJ6HYBxh/s
+m1aNohxuz4Sd+cwnmEyd68xXIt/rMHez0uxDlM3FRCXeHpAPtEYGC0kTEyo6A9NZ
+qWbaX5uvpWFw+3ECggEBAOidboncnSNJJpGkmTcx45gjXmrpZxXFPzBP1SJ/YMIC
+V3FxJRQ5ubAeNZDZvU0cm7tOtGzsmwSDyFdslOQt2nzDQTt95h70HfMeIntbtQGB
+QMoja3BGPcqEdPb2Vwc9R4ehWCvkAbofrCTuPvq+1vHvg6s6WEef7bV9B3tIkK6l
+9n+TftVr2XS8l2l1tSahNO+jjXxPDac9Hnga70+dTq0twSzRfSWMfWsbvhbxIM2o
+6avZgCL8l8GPiBZzqYpVISvzxyaXL6zw+XmN3N3TIqFjSaXIYNbzcOI/f3QeExV3
+LEoX2AfxEouyGv6lYzUoOaP6kX0a+yqdPfR4W57a/7cCggEBAJcUEE+YCRAu0j5z
+1aO4/l8yppB0pBuk2PvP9ATcD3/vKCM3pTR2GpBJNFs1qXTXFW5XhUxrZMrbAS5R
+uVBLzJtE632ioNHOsKEIKphCIYkcO2mbsTH1Dl8rI8dGu7TGketkZl2pVFq9xVrt
+c7HF0NMe0hahuOHPrOrU9Ft3s6u4myX2M9Zj2MOrJuw8BX/fYJ43Uy3mnlMeXpPG
+tg1Nb5lBjMpbW75QTZD3XRaUYYwJHQ8GaTkR6mfPUn3EZnv8BzQ6saRZB/6WeNK3
+A9MCHMFRoY2OQZSEGu0On6cVvqZ2m80YhoAj3IgB9aK19NyLEeOTL2pgNoM5j3R+
+Ehj0LTECggEAdym3J86pfRsLNA8TIlBfXF+6DaDV2zQ5o6Ex+UMxqRGUBBxHN24+
+7rb7D+JLdIZUTQcLrMUkwJJV/ls0hxPqWoGYGEbtrSu3cAUe+vzG5Cd3rlWow1Jo
+cyeZ163odV/yFcwUpB9gtx1kjWKzRrae3D+rvvrboI2QM6oCPCi3XZDNjxtbHS/r
+rT6zfiX7j/eDa8PoRiclQmgwBK0frTRTyqmmzTPgHW00DruejJgoCtMeKZf5aXLm
+txS1TXMhBimIHcD7Y4yNstWbp6aB3+06T1zSrKfS3CrHyE9pFm4VrqhnOumuFJtt
+ubya0ZqeEOwzYwqIn4ND6de1llhV7zIXTwKCAQAqJZEOjddxXHNsBFU0K+VSkcHj
+UBUwuKYmaH11lJ5GVapJOsFhtAuyghtGi0h5ufWVKnBJO+autOoh7thmROkGm+YB
+hehO4FvZFhvS2RLWG1Oe8ALG52xRi5PkF/bLg3wn5/V2CtgcuG/St2LK2pEyzXty
+Z7jWxhTcQa51RfoN6yVvr7dBp1967Kt5iZ30lNrJIiLOXUVaupxXfBPj6oSXqKnP
+HNBUSgt64X2TccVVBDQxqeKe83XvySXldfSuckNtXmgOCdq/j1wrYvCSmyeuNZnu
+jK9O4mDTMdFIVtVkG/q1Bs/ohhyb5ZFy1ifFNCER9quF5701zUmPuIrFPDx0
+-----END RSA PRIVATE KEY-----
diff --git a/src/seastar/tests/unit/catest.pem b/src/seastar/tests/unit/catest.pem
new file mode 100644
index 00000000..5c8b0594
--- /dev/null
+++ b/src/seastar/tests/unit/catest.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFzDCCA7QCCQDMNOfV+wKafTANBgkqhkiG9w0BAQUFADCBpzELMAkGA1UEBhMC
+U0UxEjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRUwEwYD
+VQQKDAxzY3lsbGFkYi5vcmcxFTATBgNVBAsMDHNjeWxsYWRiLm9yZzEaMBgGA1UE
+AwwRdGVzdC5zY3lsbGFkYi5vcmcxJjAkBgkqhkiG9w0BCQEWF3Bvc3RtYXN0ZXJA
+c2N5bGxhZGIub3JnMB4XDTE1MTIwOTA2MTY0NVoXDTI1MTIwNjA2MTY0NVowgacx
+CzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcMCVN0b2Nr
+aG9sbTEVMBMGA1UECgwMc2N5bGxhZGIub3JnMRUwEwYDVQQLDAxzY3lsbGFkYi5v
+cmcxGjAYBgNVBAMMEXRlc3Quc2N5bGxhZGIub3JnMSYwJAYJKoZIhvcNAQkBFhdw
+b3N0bWFzdGVyQHNjeWxsYWRiLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+AgoCggIBAN7AwNiMlNyTpiouLJpa2vfdKV0lYEqiGExMfBR+xWccdW+ws/Q6zzxT
+lC6VrVtImLO5sO0MhRHNblDQfyJEWqauG7Ynxul0WoXYWR+3l4vfq5oYZUxqiqPQ
+fe/S/vLYJKZYwFXcGkNdNcYkFr01SBey7wglt5pmv8QfqHOsg5yYzeJU/P+G0M4b
+BuqxiDtrE9+Fo/OJbqxOjXEEpRO0OXEdn2cR/wlULg9EIfio1nvYP1gDdmV7Nw0N
+SOZZagQZODZMkZZv3upVPbOZz09sbtMyggvzbm0cbSN0FFhgFQOBdjQ1VjWBMOkt
+8McMWzAjiv9o4Mcho/QOud4ZPE+d+O6lW9d44U+YXRYP6Og6Rua20f7Tznp35+nS
+b0SCC4yAwrFl8K5z69QDe+JT+U8iQU12i2ZO9DUuTof1grY4bZQXh8SescebvRPs
+LjT9WvEkQdMYP3bFZb/Fav0/acQmwql7g6JADjCv4I4ZhQOKbezf0mPLb5Ox4u0G
+vpGin31kfSvFbIsdYFGD92kZ/2cJOna3LN05mpBz1XUyZves0ScOstbnd3dw0zqf
+4HVUE3byeh6CdX6GiKSrw7EsFUY2gqUnw4bg81a7AqgJRc3BmV+PB6I+IIw4T+dU
+v7mDjR/+uvowT7qef0F+9WsoZ75RYEhHOfS2SF8Lrz7zWdkOc0zHAgMBAAEwDQYJ
+KoZIhvcNAQEFBQADggIBABIHmMhiuBMpGHRdGLEgEnvkjh1VAgQgVCYigcvxxO6i
+L2QVviJQPZFlhZS25/Y4tJZ08hS2VNBq0T9sdLI0+ILeVCc+g1+qkzmV9m3WUhV/
+JwJjNIBmkNHIi+EzfNtgqRK0zL2dCMuZ6IwB35YBTd8EoGGDZkFJOanD4ohyH5o8
+jHcqDH7+/cd6cf/8SHR030YsAHab+B9cJkQsHU9a6snbFOSthraSp7LoBsT1txr2
+K6pJiKFEku7VydeNCked8o/uLZkGcaVZct6HuF+e9D+4LsPr8x353TP2k7y4dlLm
+A0fpiip/zRJXwiwNsdfjGjt+YefcfPt6sHeRrCZpWqxQvf5GBEXPNsXTRsvWa1Ao
+MxDc4uYj3yh8YvSgMJM+CN8Df8BFyLvM/re3eA/ClfgweTAlcpxs/BZF6kIHaBa4
+LY/vAYSiMCh6wZorzR4FlAIlY2uS+GBylN2gD6IO/mwLYJrKmjIFIB9XXLhyTdNJ
+FBfttmhJCRq2GgwdWmZdIfBgfjBJokoQGH5gyifm150rSFiblPWr0lsSAdXHlQhJ
+Lxw329zNcrVizofz5JwXJZaxOYzP8OBodEc54lE3cC9fYklsAO+1Ba2gNicjk74d
+m0nKgTRND4c+88THYlH2QI1srdeLHClv75qmiuI17tMU5AKEufLEthnxqwqWDW1Q
+-----END CERTIFICATE-----
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 00000000..d4de8908
--- /dev/null
+++ b/src/seastar/tests/unit/checked_ptr_test.cc
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+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 00000000..584a3da9
--- /dev/null
+++ b/src/seastar/tests/unit/chunked_fifo_test.cc
@@ -0,0 +1,338 @@
+/*
+ * 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
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 00000000..43b84ed9
--- /dev/null
+++ b/src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc
@@ -0,0 +1,124 @@
+/*
+ * 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>;
+
+
+BOOST_AUTO_TEST_CASE(test_edge_cases) {
+ cb16_t cb;
+ BOOST_REQUIRE(cb.begin() == cb.end());
+ cb.push_front(3); // underflows indexes
+ BOOST_REQUIRE_EQUAL(cb[0], 3);
+ BOOST_REQUIRE(cb.begin() < cb.end());
+ cb.push_back(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(1);
+ cb.pop_back();
+ BOOST_REQUIRE_EQUAL(cb.back(), 1);
+}
+
+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 00000000..9de0af36
--- /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 00000000..1d7bdcb0
--- /dev/null
+++ b/src/seastar/tests/unit/connect_test.cc
@@ -0,0 +1,75 @@
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/net/ip.hh>
+
+#include <random>
+
+using namespace seastar;
+using namespace net;
+
+SEASTAR_TEST_CASE(test_connection_attempt_is_shutdown) {
+ ipv4_addr server_addr("172.16.0.1");
+ auto unconn = engine().net().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;
+}
+
+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::random_device rnd;
+ 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 = engine().net().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::random_device rnd;
+ 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()), [] (auto& listener) {
+ using ftype = future<connected_socket, socket_address>;
+ 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/defer_test.cc b/src/seastar/tests/unit/defer_test.cc
new file mode 100644
index 00000000..b7924569
--- /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/directory_test.cc b/src/seastar/tests/unit/directory_test.cc
new file mode 100644
index 00000000..30e86962
--- /dev/null
+++ b/src/seastar/tests/unit/directory_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) 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;
+
+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) {
+ fmt::print("{}\n", de.name);
+ return make_ready_future<>();
+ }
+ };
+ return app_template().run_deprecated(ac, av, [] {
+ return engine().open_directory(".").then([] (file f) {
+ auto l = make_lw_shared<lister>(std::move(f));
+ return l->done().then([l] {
+ // ugly thing to keep *l alive
+ engine().exit(0);
+ });
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/distributed_test.cc b/src/seastar/tests/unit/distributed_test.cc
new file mode 100644
index 00000000..b5c1ccb9
--- /dev/null
+++ b/src/seastar/tests/unit/distributed_test.cc
@@ -0,0 +1,181 @@
+/*
+ * 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/app-template.hh>
+#include <seastar/core/distributed.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/thread.hh>
+
+using namespace seastar;
+
+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();
+ sleep(std::chrono::milliseconds(100 + 100 * engine().cpu_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 = engine().cpu_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]{});
+}
+
+future<> 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"));
+ });
+ });
+}
+
+future<> 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<>(); }
+};
+
+future<> 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));
+ }
+ });
+ });
+ });
+}
+
+future<> 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");
+ }
+ });
+ });
+ });
+}
+
+future<> 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)));
+ });
+}
+
+future<> 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 (engine().cpu_id() != c) {
+ if (remote.counter != 1) {
+ throw std::runtime_error("remote not modified");
+ }
+ }
+ }).get();
+ s.stop().get();
+ });
+ }).get();
+ }
+ });
+}
+
+int main(int argc, char** argv) {
+ app_template app;
+ return app.run(argc, argv, [] {
+ return test_that_each_core_gets_the_arguments().then([] {
+ return test_functor_version();
+ }).then([] {
+ return test_constructor_argument_is_passed_to_each_core();
+ }).then([] {
+ return test_map_reduce();
+ }).then([] {
+ return test_async();
+ }).then([] {
+ return test_invoke_on_others();
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/dns_test.cc b/src/seastar/tests/unit/dns_test.cc
new file mode 100644
index 00000000..8bf3a4cb
--- /dev/null
+++ b/src/seastar/tests/unit/dns_test.cc
@@ -0,0 +1,134 @@
+/*
+ * 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/core/future-util.hh>
+#include <seastar/net/dns.hh>
+#include <seastar/net/inet_address.hh>
+
+using namespace seastar;
+using namespace seastar::net;
+
+static const inet_address google_addr = inet_address("216.58.201.164");
+static const sstring google_name = "www.google.com";
+
+static future<> test_resolve(dns_resolver::options opts) {
+ auto d = ::make_lw_shared<dns_resolver>(std::move(opts));
+ return d->get_host_by_name(google_name, inet_address::family::INET).then([d](hostent e) {
+ //BOOST_REQUIRE(std::count(e.addr_list.begin(), e.addr_list.end(), google_addr));
+ 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(google_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 00000000..52020e2e
--- /dev/null
+++ b/src/seastar/tests/unit/execution_stage_test.cc
@@ -0,0 +1,336 @@
+/*
+ * 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 <random>
+#include <vector>
+#include <chrono>
+
+#include <seastar/core/thread.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/execution_stage.hh>
+#include <seastar/core/sleep.hh>
+
+using namespace std::chrono_literals;
+
+using namespace seastar;
+
+static std::random_device rd;
+
+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(rd());
+ 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 00000000..b6dcbeb7
--- /dev/null
+++ b/src/seastar/tests/unit/expiring_fifo_test.cc
@@ -0,0 +1,189 @@
+/*
+ * 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/core/future-util.hh>
+#include <seastar/core/expiring_fifo.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 00000000..4ba4646b
--- /dev/null
+++ b/src/seastar/tests/unit/fair_queue_test.cc
@@ -0,0 +1,379 @@
+/*
+ * 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/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/fair_queue.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sleep.hh>
+#include <boost/range/irange.hpp>
+#include <random>
+#include <chrono>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+class test_request {
+ fair_queue* _fq;
+ promise<> _pr;
+ future<> _res;
+public:
+ test_request(fair_queue& fq) : _fq(&fq), _res(make_exception_future<>(std::runtime_error("impossible"))) {}
+ ~test_request() {
+ }
+
+ test_request(const test_request&) = delete;
+ test_request(test_request&&) = default;
+ future<> get_future() {
+ return _pr.get_future();
+ }
+ void add_result(future<> f) {
+ _res = std::move(f);
+ }
+};
+
+fair_queue::config make_config(unsigned capacity) {
+ fair_queue::config cfg;
+ cfg.capacity = capacity;
+ cfg.max_req_count = capacity;
+ return cfg;
+}
+
+struct test_env {
+ fair_queue fq;
+ std::vector<int> results;
+ std::vector<priority_class_ptr> classes;
+ std::vector<future<>> inflight;
+ test_env(unsigned capacity) : fq(capacity)
+ {}
+
+ size_t register_priority_class(uint32_t shares) {
+ results.push_back(0);
+ classes.push_back(fq.register_priority_class(shares));
+ return classes.size() - 1;
+ }
+
+ void do_op(unsigned index, unsigned weight) {
+ auto cl = classes[index];
+ struct request {
+ promise<> pr;
+ fair_queue_request_descriptor fqdesc;
+ };
+
+ auto req = std::make_unique<request>();
+ req->fqdesc.weight = weight;
+ req->fqdesc.size = 0;
+ inflight.push_back(req->pr.get_future());
+ auto fqdesc = req->fqdesc;
+
+ fq.queue(cl, fqdesc, [this, index, req = std::move(req)] () mutable noexcept {
+ try {
+ results[index]++;
+ sleep(100us).then_wrapped([this, req = std::move(req)] (future<> f) mutable {
+ f.forward_to(std::move(req->pr));
+ fq.notify_requests_finished(req->fqdesc);
+ fq.dispatch_requests();
+ });
+ } catch (...) {
+ req->pr.set_exception(std::current_exception());
+ fq.notify_requests_finished(req->fqdesc);
+ fq.dispatch_requests();
+ }
+ });
+ fq.dispatch_requests();
+ }
+ void update_shares(unsigned index, uint32_t shares) {
+ auto cl = classes[index];
+ fq.update_shares(cl, shares);
+ }
+ // 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
+ future<> verify(sstring name, std::vector<unsigned> ratios, unsigned expected_error = 1) {
+ return wait_on_pending().then([name, r = results, ratios = std::move(ratios), this, expected_error] {
+ assert(ratios.size() == r.size());
+ auto str = name + ":";
+ for (auto i = 0ul; i < r.size(); ++i) {
+ str += format(" r[{:d}] = {:d}", i, r[i]);
+ }
+ std::cout << str << std::endl;
+ for (auto i = 0ul; i < ratios.size(); ++i) {
+ int min_expected = ratios[i] * (r[0] - expected_error);
+ int max_expected = ratios[i] * (r[0] + expected_error);
+ BOOST_REQUIRE(r[i] >= min_expected);
+ BOOST_REQUIRE(r[i] <= max_expected);
+ }
+ for (auto& p: classes) {
+ fq.unregister_priority_class(p);
+ }
+ });
+ }
+ future<> wait_on_pending() {
+ auto curr = make_lw_shared<std::vector<future<>>>();
+ curr->swap(inflight);
+ return when_all(curr->begin(), curr->end()).discard_result();
+ }
+};
+
+// Equal ratios. Expected equal results.
+SEASTAR_TEST_CASE(test_fair_queue_equal_2classes) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(10ms).then([env] {
+ return env->verify("equal_2classes", {1, 1});
+ }).then([env] {});
+}
+
+// Equal results, spread among 4 classes.
+SEASTAR_TEST_CASE(test_fair_queue_equal_4classes) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(10ms).then([env] {
+ return env->verify("equal_4classes", {1, 1, 1, 1});
+ }).then([env] {});
+}
+
+// Class2 twice as powerful. Expected class2 to have 2 x more requests.
+SEASTAR_TEST_CASE(test_fair_queue_different_shares) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(10ms).then([env] {
+ return env->verify("different_shares", {1, 2});
+ }).then([env] {});
+}
+
+// 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_TEST_CASE(test_fair_queue_equal_hi_capacity_2classes) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(1ms).then([env] {
+ return env->verify("hi_capacity_2classes", {1, 1});
+ }).then([env] {});
+
+}
+
+// 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_TEST_CASE(test_fair_queue_different_shares_hi_capacity) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(1ms).then([env] {
+ return env->verify("different_shares_hi_capacity", {1, 2});
+ }).then([env] {});
+}
+
+// Classes equally powerful. But Class1 issues twice as expensive requests. Expected Class2 to have 2 x more requests.
+SEASTAR_TEST_CASE(test_fair_queue_different_weights) {
+ auto env = make_lw_shared<test_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, 2);
+ env->do_op(b, 1);
+ }
+ return sleep(5ms).then([env] {
+ return env->verify("different_weights", {1, 2});
+ }).then([env] {});
+}
+
+// Class2 pushes many requests over 10ms. In the next msec at least, don't expect Class2 to be able to push anything else.
+SEASTAR_TEST_CASE(test_fair_queue_dominant_queue) {
+ auto env = make_lw_shared<test_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);
+ }
+ return env->wait_on_pending().then([env, a, b] {
+ env->results[b] = 0;
+ for (int i = 0; i < 20; ++i) {
+ env->do_op(a, 1);
+ env->do_op(b, 1);
+ }
+ return sleep(1ms).then([env] {
+ return env->verify("dominant_queue", {1, 0});
+ });
+ }).then([env] {});
+}
+
+// Class2 pushes many requests over 10ms. After enough time, this shouldn't matter anymore.
+SEASTAR_TEST_CASE(test_fair_queue_forgiving_queue) {
+ auto env = make_lw_shared<test_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);
+ }
+ return env->wait_on_pending().then([] {
+ return sleep(500ms);
+ }).then([env, a, b] {
+ env->results[b] = 0;
+ for (int i = 0; i < 100; ++i) {
+ env->do_op(a, 1);
+ env->do_op(b, 1);
+ }
+ return sleep(10ms).then([env] {
+ return env->verify("forgiving_queue", {1, 1});
+ });
+ }).then([env] {});
+}
+
+// Classes push requests and then update swap their shares. In the end, should have executed
+// the same number of requests.
+SEASTAR_TEST_CASE(test_fair_queue_update_shares) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(10ms).then([env, a, b] {
+ env->update_shares(a, 10);
+ env->update_shares(b, 20);
+ return sleep(10ms);
+ }).then([env] {
+ return env->verify("update_shares", {1, 1}, 2);
+ }).then([env] {});
+}
+
+// Classes run for a longer period of time. Balance must be kept over many timer
+// periods.
+SEASTAR_TEST_CASE(test_fair_queue_longer_run) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(1s).then([env] {
+ return env->verify("longer_run", {1, 1}, 2);
+ }).then([env] {});
+}
+
+// Classes run for a longer period of time. Proportional balance must be kept over many timer
+// periods, despite unequal shares..
+SEASTAR_TEST_CASE(test_fair_queue_longer_run_different_shares) {
+ auto env = make_lw_shared<test_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);
+ }
+ return sleep(1s).then([env] {
+ return env->verify("longer_run_different_shares", {1, 2}, 2);
+ }).then([env] {});
+}
+
+// Classes run for a random period of time. Equal operations expected.
+SEASTAR_TEST_CASE(test_fair_queue_random_run) {
+ auto env = make_lw_shared<test_env>(1);
+
+ auto a = env->register_priority_class(1);
+ auto b = env->register_priority_class(1);
+
+ auto seed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
+ std::default_random_engine generator(seed);
+ // 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 / 2) + 10; ++i) {
+ env->do_op(a, 1);
+ env->do_op(b, 1);
+ }
+
+ return sleep(reqs * 100us).then([env, reqs] {
+ // Accept 5 % error.
+ auto expected_error = std::max(1, int(round(reqs * 0.05)));
+ return env->verify(format("random_run ({:d} msec)", reqs / 10), {1, 1}, expected_error);
+ }).then([env] {});
+}
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 00000000..6e4fef20
--- /dev/null
+++ b/src/seastar/tests/unit/file_io_test.cc
@@ -0,0 +1,143 @@
+/*
+ * 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/core/semaphore.hh>
+#include <seastar/core/condition-variable.hh>
+#include <seastar/core/file.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/stall_sampler.hh>
+#include <iostream>
+
+using namespace seastar;
+
+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<>();
+}
+
+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).
+ static constexpr auto max = 10000;
+ return open_file_dma("testfile.tmp", open_flags::rw | open_flags::create).then([] (file f) {
+ auto ft = new file_test{std::move(f)};
+ for (size_t i = 0; i < max; ++i) {
+ 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();
+ 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();
+ 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 {
+ std::cout << "done\n";
+ delete ft;
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(parallel_write_fsync) {
+ return internal::report_reactor_stalls([] {
+ return async([] {
+ // Plan: open a file and write to it like crazy. In parallel fsync() it all the time.
+ auto fname = "testfile.tmp";
+ 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);
+ 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";
+ });
+}
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 00000000..5ae2abfc
--- /dev/null
+++ b/src/seastar/tests/unit/foreign_ptr_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) 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>
+
+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(engine().cpu_id()) { }
+ ~dummy() { BOOST_REQUIRE_EQUAL(_cpu, engine().cpu_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);
+ });
+}
diff --git a/src/seastar/tests/unit/fstream_test.cc b/src/seastar/tests/unit/fstream_test.cc
new file mode 100644
index 00000000..f3b4c352
--- /dev/null
+++ b/src/seastar/tests/unit/fstream_test.cc
@@ -0,0 +1,510 @@
+/*
+ * 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/reactor.hh>
+#include <seastar/core/fstream.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/testing/test_case.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/defer.hh>
+#include <random>
+#include <boost/range/adaptor/transformed.hpp>
+#include <boost/algorithm/cxx11/any_of.hpp>
+#include "mock_file.hh"
+
+using namespace seastar;
+
+struct writer {
+ output_stream<char> out;
+ writer(file f) : out(make_file_output_stream(std::move(f))) {}
+};
+
+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) {
+ auto sem = make_lw_shared<semaphore>(0);
+
+ open_file_dma("testfile.tmp",
+ open_flags::rw | open_flags::create | open_flags::truncate).then([sem] (file f) {
+ auto w = make_shared<writer>(std::move(f));
+ auto buf = static_cast<char*>(::malloc(4096));
+ memset(buf, 0, 4096);
+ buf[0] = '[';
+ buf[1] = 'A';
+ buf[4095] = ']';
+ 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([] {
+ return open_file_dma("testfile.tmp", 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] {});
+ }).finally([sem] () {
+ sem->signal();
+ });
+ });
+
+ return sem->wait();
+}
+
+SEASTAR_TEST_CASE(test_consume_skip_bytes) {
+ return seastar::async([] {
+ auto f = open_file_dma("testfile.tmp",
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ auto w = make_lw_shared<writer>(std::move(f));
+ 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("testfile.tmp", 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) {
+ auto sem = make_lw_shared<semaphore>(0);
+
+ open_file_dma("testfile.tmp",
+ open_flags::rw | open_flags::create | open_flags::truncate).then([sem] (file f) {
+ auto w = make_shared<writer>(std::move(f));
+ auto buf = static_cast<char*>(::malloc(40));
+ memset(buf, 0, 40);
+ buf[0] = '[';
+ buf[1] = 'A';
+ buf[39] = ']';
+ w->out.write(buf, 40).then([buf, w] {
+ ::free(buf);
+ return w->out.close().then([w] {});
+ }).then([] {
+ return open_file_dma("testfile.tmp", 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([] {
+ return open_file_dma("testfile.tmp", 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] {});
+ }).finally([sem] () {
+ sem->signal();
+ });
+ });
+
+ return sem->wait();
+}
+
+future<> test_consume_until_end(uint64_t size) {
+ return open_file_dma("testfile.tmp",
+ open_flags::rw | open_flags::create | open_flags::truncate).then([size] (file f) {
+ return do_with(make_file_output_stream(f), [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>(compat::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();
+ });
+ });
+ });
+ });
+}
+
+
+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 seastar::async([] {
+ auto flen = uint64_t(5341);
+ auto rdist = std::uniform_int_distribution<char>();
+ auto reng = std::default_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 f = open_file_dma("file.tmp",
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ auto out = make_file_output_stream(f);
+ 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();
+ });
+}
+
+SEASTAR_TEST_CASE(file_handle_test) {
+ return seastar::async([] {
+ auto f = open_file_dma("testfile.tmp", 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[engine().cpu_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
+
+ compat::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;
+ 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 00000000..ab92fdd5
--- /dev/null
+++ b/src/seastar/tests/unit/futures_test.cc
@@ -0,0 +1,955 @@
+/*
+ * 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/shared_ptr.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/shared_future.hh>
+#include <seastar/core/thread.hh>
+#include <boost/iterator/counting_iterator.hpp>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+class expected_exception : std::runtime_error {
+public:
+ expected_exception() : runtime_error("expected") {}
+};
+
+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();
+}
+
+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 = std::move(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) {
+ // .then() usually returns a ready future, but sometimes it
+ // doesn't, so call it a million times. This exercises both
+ // available and unavailable paths in when_all().
+ futures.push_back(make_ready_future<>().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 std::get<0>(f.get()) == 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);
+ });
+}
+
+// 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_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, [&sum] (int) -> future<> {
+ throw 5;
+ }).get(), int, [] (int v) { return v == 5; });
+
+ // throws after suspension
+ BOOST_CHECK_EXCEPTION(parallel_for_each(range, [&sum] (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);
+ });
+}
+
+#ifndef SEASTAR_SHUFFLE_TASK_QUEUE
+SEASTAR_TEST_CASE(test_high_priority_task_runs_before_ready_continuations) {
+ return now().then([] {
+ auto flag = make_lw_shared<bool>(false);
+ engine().add_high_priority_task(make_task([flag] {
+ *flag = true;
+ }));
+ make_ready_future().then([flag] {
+ BOOST_REQUIRE(*flag);
+ });
+ });
+}
+
+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_apply_val_exception) {
+ return futurize<int>::apply([] (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_apply_val_ok) {
+ return futurize<int>::apply([] (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_apply_val_future_exception) {
+ return futurize<int>::apply([] (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_apply_val_future_ok) {
+ return futurize<int>::apply([] (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_apply_void_exception) {
+ return futurize<void>::apply([] (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_apply_void_ok) {
+ return futurize<void>::apply([] (auto arg) { }, 0).then_wrapped([] (future<> f) {
+ try {
+ f.get();
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_apply_void_future_exception) {
+ return futurize<void>::apply([] (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_apply_void_future_ok) {
+ auto a = make_lw_shared<int>(1);
+ return futurize<void>::apply([] (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_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_futurize_from_tuple) {
+ std::tuple<int> v1 = std::make_tuple(3);
+ std::tuple<> v2 = {};
+ BOOST_REQUIRE(futurize<int>::from_tuple(v1).get() == v1);
+ BOOST_REQUIRE(futurize<void>::from_tuple(v2).get() == v2);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_repeat_until_value) {
+ return do_with(int(), [] (int& counter) {
+ return repeat_until_value([&counter] () -> future<compat::optional<int>> {
+ if (counter == 10000) {
+ return make_ready_future<compat::optional<int>>(counter);
+ } else {
+ ++counter;
+ return make_ready_future<compat::optional<int>>(compat::nullopt);
+ }
+ }).then([&counter] (int result) {
+ BOOST_REQUIRE(counter == 10000);
+ BOOST_REQUIRE(result == counter);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_allx) {
+ return when_all(later(), later(), make_ready_future()).discard_result();
+}
+
+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_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());
+ });
+}
+
+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<int, sstring>(84, "hi"),
+ make_ready_future<bool>(true)
+ ).then([] (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([] (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;
+ });
+}
diff --git a/src/seastar/tests/unit/httpd_test.cc b/src/seastar/tests/unit/httpd_test.cc
new file mode 100644
index 00000000..17a26c80
--- /dev/null
+++ b/src/seastar/tests/unit/httpd_test.cc
@@ -0,0 +1,641 @@
+/*
+ * 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/future-util.hh>
+#include <seastar/testing/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_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)), [&ss, &cr] (output_stream<char>& os, std::vector<sstring>& parts) {
+ return do_for_each(parts, [&os](auto& p) {
+ return os.write(p);
+ }).then([&os, &ss] {
+ 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))),
+ [&ss] (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, &cr] {
+ 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 = std::get<connected_socket>(lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get());
+ input_stream<char> input(std::move(c_socket.input()));
+ output_stream<char> output(std::move(c_socket.output()));
+ bool more = true;
+ size_t count = 0;
+ while (more) {
+ http_consumer htp;
+ htp._concat = false;
+
+ write_request(output).get();
+ repeat([&c_socket, &input, &htp] {
+ return input.read().then([&c_socket, &input, &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 writer = 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(writer));
+ }).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 = std::get<connected_socket>(lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get());
+ input_stream<char> input(std::move(c_socket.input()));
+ output_stream<char> output(std::move(c_socket.output()));
+ bool more = true;
+ size_t count = 0;
+ while (more) {
+ http_consumer htp;
+ write_request(output).get();
+ repeat([&c_socket, &input, &htp] {
+ return input.read().then([&c_socket, &input, &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 writer = 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(writer));
+ }).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, success] () 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);
+ }
+ }
+
+ extra_big_object(extra_big_object&&) = default;
+};
+
+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(extra_big_object(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;
+ });
+}
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 00000000..1c6e51f9
--- /dev/null
+++ b/src/seastar/tests/unit/json_formatter_test.cc
@@ -0,0 +1,54 @@
+/*
+ * 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/reactor.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/future-util.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/loopback_socket.hh b/src/seastar/tests/unit/loopback_socket.hh
new file mode 100644
index 00000000..888b0692
--- /dev/null
+++ b/src/seastar/tests/unit/loopback_socket.hh
@@ -0,0 +1,258 @@
+/*
+ * 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/future-util.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/net/stack.hh>
+#include <seastar/core/reactor.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({});
+ });
+ }
+};
+
+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 {
+ 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};
+ }
+};
+
+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<connected_socket, socket_address> accept() override {
+ return _pending->pop_eventually().then([] (connected_socket&& cs) {
+ return make_ready_future<connected_socket, socket_address>(std::move(cs), socket_address());
+ });
+ }
+ void abort_accept() override {
+ _pending->abort(std::make_exception_ptr(std::system_error(ECONNABORTED, std::system_category())));
+ }
+};
+
+
+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[engine().cpu_id()]) {
+ _pending[engine().cpu_id()] = make_lw_shared<queue<connected_socket>>(10);
+ }
+ return server_socket(std::make_unique<loopback_server_socket_impl>(_pending[engine().cpu_id()]));
+ }
+ future<> make_new_server_connection(foreign_ptr<lw_shared_ptr<loopback_buffer>> b1, lw_shared_ptr<loopback_buffer> b2) {
+ if (!_pending[engine().cpu_id()]) {
+ _pending[engine().cpu_id()] = make_lw_shared<queue<connected_socket>>(10);
+ }
+ return _pending[engine().cpu_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;
+ }
+};
+
+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) {
+ 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, shard] (foreign_ptr<lw_shared_ptr<loopback_buffer>> b2) {
+ return _factory.make_new_client_connection(_b1, std::move(b2));
+ });
+ }
+
+ void shutdown() {
+ _b1->shutdown();
+ smp::submit_to(_b2.get_owner_shard(), [this, 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 00000000..c0f14e30
--- /dev/null
+++ b/src/seastar/tests/unit/lowres_clock_test.cc
@@ -0,0 +1,117 @@
+/*
+ * 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 <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/mkcert.gmk b/src/seastar/tests/unit/mkcert.gmk
new file mode 100644
index 00000000..829dc4be
--- /dev/null
+++ b/src/seastar/tests/unit/mkcert.gmk
@@ -0,0 +1,91 @@
+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
+
+hosts =
+
+all : $(crt)
+
+clean :
+ @rm -f $(crt) $(csr) $(pubkey) $(prvkey)
+
+%.key :
+ @echo generating $@
+ openssl genrsa -out $@ $(width)
+
+%.pub : %.key
+ @echo generating $@
+ openssl rsa -in $< -out $@
+
+$(config) : $(MAKEFILE_LIST)
+ @echo generating $@
+ @( \
+ echo RANDFILE = $ENV::HOME/.rnd ; \
+ echo [ req ] ; \
+ echo default_bits = $(width) ; \
+ echo default_keyfile = $(prvkey) ; \
+ 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 00000000..3ee47695
--- /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&) override {
+ throw std::bad_function_call();
+ }
+ virtual future<size_t> write_dma(uint64_t, std::vector<iovec>, const io_priority_class&) override {
+ throw std::bad_function_call();
+ }
+ virtual future<size_t> read_dma(uint64_t pos, void*, size_t len, const io_priority_class&) 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&) 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() override {
+ return make_ready_future<>();
+ }
+ virtual future<struct stat> stat() override {
+ throw std::bad_function_call();
+ }
+ virtual future<> truncate(uint64_t) {
+ throw std::bad_function_call();
+ }
+ virtual future<> discard(uint64_t offset, uint64_t length) override {
+ throw std::bad_function_call();
+ }
+ virtual future<> allocate(uint64_t position, uint64_t length) override {
+ throw std::bad_function_call();
+ }
+ virtual future<uint64_t> size() override {
+ return make_ready_future<uint64_t>(_total_file_size);
+ }
+ virtual future<> close() 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&) 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 00000000..ec26135f
--- /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/noncopyable_function_test.cc b/src/seastar/tests/unit/noncopyable_function_test.cc
new file mode 100644
index 00000000..dc19d752
--- /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 00000000..84d38fa6
--- /dev/null
+++ b/src/seastar/tests/unit/output_stream_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) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/vector-data-sink.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/net/packet.hh>
+#include <seastar/testing/test_case.hh>
+#include <vector>
+
+using namespace seastar;
+using namespace net;
+
+static sstring to_sstring(const packet& p) {
+ sstring res(sstring::initialized_later(), 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::initializer_list<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]{});
+}
diff --git a/src/seastar/tests/unit/packet_test.cc b/src/seastar/tests/unit/packet_test.cc
new file mode 100644
index 00000000..a0bdd291
--- /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 00000000..408752d5
--- /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 00000000..a8405e0a
--- /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/rpc_test.cc b/src/seastar/tests/unit/rpc_test.cc
new file mode 100644
index 00000000..6daf7ed7
--- /dev/null
+++ b/src/seastar/tests/unit/rpc_test.cc
@@ -0,0 +1,547 @@
+/*
+ * 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/multi_algo_compressor_factory.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/util/defer.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(sstring::initialized_later(), size);
+ in.read(ret.begin(), 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 shutdown() override {
+ if (_connect) {
+ _socket.shutdown();
+ } else {
+ _p.set_exception(std::make_exception_ptr(std::system_error(ECONNABORTED, std::system_category())));
+ }
+ }
+};
+
+future<>
+with_rpc_env(rpc::resource_limits resource_limits, rpc::server_options so, bool connect, bool inject_error,
+ std::function<future<> (test_rpc_proto& proto, test_rpc_proto::server& server, make_socket_fn make_socket)> test_fn) {
+ struct state {
+ test_rpc_proto proto{serializer()};
+ loopback_connection_factory lcf;
+ std::vector<std::unique_ptr<test_rpc_proto::server>> servers;
+ };
+ return do_with(state(), [=] (state& s) {
+ s.servers.resize(smp::count);
+ return smp::invoke_on_all([=, &s] {
+ s.servers[engine().cpu_id()] = std::make_unique<test_rpc_proto::server>(s.proto, so, s.lcf.get_server_socket(), resource_limits);
+ }).then([=, &s] {
+ auto make_socket = [&s, connect, inject_error] () {
+ return seastar::socket(std::make_unique<rpc_socket_impl>(s.lcf, connect, inject_error));
+ };
+ return test_fn(s.proto, *s.servers[0], make_socket).finally([&] {
+ return smp::invoke_on_all([&s] {
+ auto sptr = s.servers[engine().cpu_id()].get();
+ s.lcf.destroy_shard(engine().cpu_id());
+ return sptr->stop().finally([p = std::move(s.servers[engine().cpu_id()])] {});
+ });
+ });
+ });
+ });
+}
+
+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;
+ }
+ std::unique_ptr<rpc::compressor> negotiate(sstring feature, bool is_server) const override {
+ if (feature == name) {
+ use_compression++;
+ return std::make_unique<rpc::lz4_compressor>();
+ } else {
+ return nullptr;
+ }
+ }
+};
+#if 1
+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;
+ auto f = with_rpc_env({}, so, true, false, [co] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return seastar::async([&proto, make_socket, co] {
+ test_rpc_proto::client c1(proto, co, make_socket(), ipv4_addr());
+ auto sum = proto.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ });
+ auto result = sum(c1, 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ c1.stop().get();
+ });
+ }).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;
+ return with_rpc_env({}, so, true, false, [co] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return seastar::async([&proto, make_socket, co] {
+ test_rpc_proto::client c1(proto, co, make_socket(), ipv4_addr());
+ auto sum = proto.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ });
+ auto result = sum(c1, 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ c1.stop().get();
+ });
+ }).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) {
+ return with_rpc_env({}, {}, false, false, [] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return seastar::async([&proto, make_socket] {
+ test_rpc_proto::client c1(proto, {}, make_socket(), ipv4_addr());
+ auto f = proto.register_handler(1, []() { return make_ready_future<>(); });
+ c1.stop().get0();
+ try {
+ f(c1).get0();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_cancel) {
+ using namespace std::chrono_literals;
+ return with_rpc_env({}, {}, true, false, [] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return seastar::async([&proto, make_socket] {
+ test_rpc_proto::client c1(proto, {}, make_socket(), ipv4_addr());
+ bool rpc_executed = false;
+ int good = 0;
+ promise<> handler_called;
+ future<> f_handler_called = handler_called.get_future();
+ auto call = proto.register_handler(1, [&rpc_executed, handler_called = std::move(handler_called)] () mutable {
+ handler_called.set_value(); rpc_executed = true; return sleep(1ms);
+ });
+ 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;
+ };
+ c1.stop().get();
+ BOOST_REQUIRE_EQUAL(good, 11);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_message_to_big) {
+ return with_rpc_env({0, 1, 100}, {}, true, false, [] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return seastar::async([&proto, make_socket] {
+ test_rpc_proto::client c(proto, {}, make_socket(), ipv4_addr());
+ bool good = true;
+ auto call = proto.register_handler(1, [&] (sstring payload) mutable {
+ good = false;
+ });
+ try {
+ call(c, sstring(sstring::initialized_later(), 101)).get();
+ good = false;
+ } catch(std::runtime_error& err) {
+ } catch(...) {
+ good = false;
+ }
+ c.stop().get();
+ BOOST_REQUIRE_EQUAL(good, true);
+ });
+ });
+}
+#endif
+
+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(test_rpc_proto& proto, make_socket_fn make_socket, bool stop_client) {
+ return seastar::async([&proto, make_socket, stop_client] {
+ stream_test_result r;
+ test_rpc_proto::client c(proto, {}, make_socket(), ipv4_addr());
+ future<> server_done = make_ready_future();
+ proto.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();
+ }
+ sink.close().get();
+ });
+ 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;
+ }
+ }
+ });
+ server_done = when_all_succeed(std::move(sink_loop), std::move(source_loop)).discard_result();
+ return sink;
+ });
+ auto call = proto.make_client<rpc::source<sstring> (int, rpc::sink<int>)>(1);
+ auto x = [&] {
+ try {
+ return c.make_stream_sink<serializer, int>(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();
+ for (int i = 1; i < 101; 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);
+ return with_rpc_env({}, so, true, false, [] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return stream_test_func(proto, make_socket, false).then([] (stream_test_result r) {
+ BOOST_REQUIRE(r.client_source_closed &&
+ r.server_source_closed &&
+ r.server_sum == 5050 &&
+ !r.sink_exception &&
+ !r.sink_close_exception &&
+ !r.source_done_exception &&
+ !r.server_done_exception &&
+ !r.client_stop_exception);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_stream_stop_client) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ return with_rpc_env({}, so, true, false, [] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return stream_test_func(proto, make_socket, true).then([] (stream_test_result r) {
+ BOOST_REQUIRE(!r.client_source_closed &&
+ !r.server_source_closed &&
+ r.sink_exception &&
+ r.sink_close_exception &&
+ r.source_done_exception &&
+ r.server_done_exception &&
+ !r.client_stop_exception);
+ });
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_stream_connection_error) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ return with_rpc_env({}, so, true, true, [] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return stream_test_func(proto, make_socket, false).then([] (stream_test_result r) {
+ BOOST_REQUIRE(!r.client_source_closed &&
+ !r.server_source_closed &&
+ r.sink_exception &&
+ r.sink_close_exception &&
+ r.source_done_exception &&
+ r.server_done_exception &&
+ !r.client_stop_exception);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_scheduling) {
+ return with_rpc_env({}, {}, true, false, [] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return seastar::async([&proto, make_socket] {
+ test_rpc_proto::client c1(proto, {}, make_socket(), ipv4_addr());
+ auto sg = create_scheduling_group("rpc", 100).get0();
+ auto call = proto.register_handler(1, sg, [sg] () mutable {
+ BOOST_REQUIRE(sg == current_scheduling_group());
+ return make_ready_future<>();
+ });
+ call(c1).get();
+ c1.stop().get();
+ });
+ });
+}
+
+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;
+ };
+ with_rpc_env(limits, {}, true, false, [sg1, sg2] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return async([&proto, make_socket, sg1, sg2] {
+ rpc::client_options co1;
+ co1.isolation_cookie = "sg1";
+ test_rpc_proto::client c1(proto, co1, make_socket(), ipv4_addr());
+ rpc::client_options co2;
+ co2.isolation_cookie = "sg2";
+ test_rpc_proto::client c2(proto, co2, make_socket(), ipv4_addr());
+ auto call = proto.register_handler(1, [sg1, sg2] (int which) mutable {
+ scheduling_group expected;
+ if (which == 1) {
+ expected = sg1;
+ } else if (which == 2) {
+ expected = sg2;
+ }
+ BOOST_REQUIRE(current_scheduling_group() == expected);
+ return make_ready_future<>();
+ });
+ call(c1, 1).get();
+ call(c2, 2).get();
+ 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;
+ };
+ with_rpc_env(limits, {}, true, false, [sg1, sg2] (test_rpc_proto& proto, test_rpc_proto::server& s, make_socket_fn make_socket) {
+ return async([&proto, make_socket, sg1, sg2] {
+ rpc::client_options co1;
+ co1.isolation_cookie = "sg1";
+ test_rpc_proto::client c1(proto, co1, make_socket(), ipv4_addr());
+ rpc::client_options co2;
+ co2.isolation_cookie = "sg2";
+ test_rpc_proto::client c2(proto, co2, make_socket(), ipv4_addr());
+ // An old client, that doesn't have an isolation cookie
+ rpc::client_options co3;
+ test_rpc_proto::client c3(proto, co3, make_socket(), ipv4_addr());
+ // A server that uses sg1 if the client is old
+ auto call = proto.register_handler(1, sg1, [sg1, sg2] (int which) mutable {
+ scheduling_group expected;
+ if (which == 1) {
+ expected = sg1;
+ } else if (which == 2) {
+ expected = sg2;
+ }
+ BOOST_REQUIRE(current_scheduling_group() == expected);
+ return make_ready_future<>();
+ });
+ call(c1, 1).get();
+ call(c2, 2).get();
+ call(c3, 1).get();
+ c1.stop().get();
+ c2.stop().get();
+ c3.stop().get();
+ });
+ }).get();
+}
diff --git a/src/seastar/tests/unit/semaphore_test.cc b/src/seastar/tests/unit/semaphore_test.cc
new file mode 100644
index 00000000..c12edb99
--- /dev/null
+++ b/src/seastar/tests/unit/semaphore_test.cc
@@ -0,0 +1,249 @@
+/*
+ * 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/reactor.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/future-util.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) {
+ x.first.wait().then([&x] {
+ x.second++;
+ });
+ x.first.signal();
+ return sleep(10ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 1);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_semaphore_2) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ x.first.wait().then([&x] {
+ x.second++;
+ });
+ return sleep(10ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 0);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_semaphore_timeout_1) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ x.first.wait(10ms).then([&x] {
+ x.second++;
+ });
+ sleep(3ms).then([&x] {
+ x.first.signal();
+ });
+ return sleep(20ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 1);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_semaphore_timeout_2) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ x.first.wait(3ms).then([&x] {
+ x.second++;
+ });
+ sleep(10ms).then([&x] {
+ x.first.signal();
+ });
+ return sleep(20ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 0);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_semaphore_mix_1) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ x.first.wait(3ms).then([&x] {
+ x.second++;
+ });
+ x.first.wait().then([&x] {
+ x.second = 10;
+ });
+ sleep(10ms).then([&x] {
+ x.first.signal();
+ });
+ return sleep(20ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 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(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);
+}
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 00000000..3ee4b658
--- /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 00000000..080de068
--- /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 00000000..32e0bf2d
--- /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 00000000..7588dd79
--- /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 00000000..bc523a13
--- /dev/null
+++ b/src/seastar/tests/unit/smp_test.cc
@@ -0,0 +1,80 @@
+/*
+ * 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>
+
+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/sstring_test.cc b/src/seastar/tests/unit/sstring_test.cc
new file mode 100644
index 00000000..1452b792
--- /dev/null
+++ b/src/seastar/tests/unit/sstring_test.cc
@@ -0,0 +1,135 @@
+/*
+ * 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_construction) {
+ BOOST_REQUIRE_EQUAL(sstring(compat::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_REQUIRE_THROW(str.erase(str.begin() + 5, str.begin() + 6), std::out_of_range);
+}
+
+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");
+}
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 00000000..7df0b3aa
--- /dev/null
+++ b/src/seastar/tests/unit/stall_detector_test.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) 2018 ScyllaDB Ltd.
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.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 = std::chrono::steady_clock::now() + how_much;
+ while (std::chrono::steady_clock::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);
+ BOOST_REQUIRE_EQUAL(reports, 10);
+}
+
+
diff --git a/src/seastar/tests/unit/test.crl b/src/seastar/tests/unit/test.crl
new file mode 100644
index 00000000..2f8277b8
--- /dev/null
+++ b/src/seastar/tests/unit/test.crl
@@ -0,0 +1,13 @@
+-----BEGIN X509 CRL-----
+MIICDDCB9QIBATANBgkqhkiG9w0BAQsFADCBhDEMMAoGA1UEAxMDQXBhMRMwEQYK
+CZImiZPyLGQBARMDQXBhMQwwCgYDVQQLEwNBcGExDDAKBgNVBAoTA0FwYTEMMAoG
+A1UEBxMDQXBhMQwwCgYDVQQIEwNBcGExCzAJBgNVBAYTAkFQMRowGAYJKoZIhvcN
+AQkBFgthcGFAYXBhLm9yZxgPMjAxNTExMzAxMjUyMjRaGA8yMDI1MTEyNzEyNTIy
+OFowAKA2MDQwHwYDVR0jBBgwFoAU2MdBt0YHNIRXTbna3CWvPm7GbXwwEQYDVR0U
+BAoCCFZcRowkm2s+MA0GCSqGSIb3DQEBCwUAA4IBAQDEk8nRGZb/wQ598wHjdcFT
+bCIQFYorgqH772dAtQZ8takN/0spno3BccnetkldRT38/WqF9t30eKPlOv4ua9en
+7kDspPkwgoSf5kDSZ0WwDdMg8ZksLk7gXr9UvOLiQ5VCcBYQbvbcOFPbqUPEk8Xk
+qNIWhP95iMztn1CKo/rvrzt5skTVnuS30Tccq5xTsYPjVEx/VxK4xcQOick71xJU
+KOyNSjuoT76vhWsROFf0lHFII5VvmD/aCOxMmv0bfIJs1mGSmvAKRVSITiOmcsiS
+JZL11MvYJ/2l/mc4e2oIxlYxjDTdexecJXW/IwZKgleHc1hJ+60ir8+os/j8BkEh
+-----END X509 CRL-----
diff --git a/src/seastar/tests/unit/test.crt b/src/seastar/tests/unit/test.crt
new file mode 100644
index 00000000..395ab386
--- /dev/null
+++ b/src/seastar/tests/unit/test.crt
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFzDCCA7QCCQDAICfAgwoXXzANBgkqhkiG9w0BAQUFADCBpzELMAkGA1UEBhMC
+U0UxEjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRUwEwYD
+VQQKDAxzY3lsbGFkYi5vcmcxFTATBgNVBAsMDHNjeWxsYWRiLm9yZzEaMBgGA1UE
+AwwRdGVzdC5zY3lsbGFkYi5vcmcxJjAkBgkqhkiG9w0BCQEWF3Bvc3RtYXN0ZXJA
+c2N5bGxhZGIub3JnMB4XDTE1MTIwOTA2MTY0NloXDTI1MTIwNjA2MTY0Nlowgacx
+CzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcMCVN0b2Nr
+aG9sbTEVMBMGA1UECgwMc2N5bGxhZGIub3JnMRUwEwYDVQQLDAxzY3lsbGFkYi5v
+cmcxGjAYBgNVBAMMEXRlc3Quc2N5bGxhZGIub3JnMSYwJAYJKoZIhvcNAQkBFhdw
+b3N0bWFzdGVyQHNjeWxsYWRiLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+AgoCggIBAMgUwyxgR/zPrpDj2OE+mCybAN3Dj5Ia3vTk79d9RES9c1mMZL6HMGMg
+fQib7iKoPHVNyT+X6FqvoCASTDoz7xIReVhQMd2U3CFGii2cciRqYbMJyQmGT2K8
+4qs7/apRC+qdqNAUxH9pkv2vG+Zbkliz3V/LGp/sOCKH8+oZN9FFt5kPzS91fX1U
+NMZI46wAzQpTJjaY5UniLuCF1iegKEu7s9vxCWj4AJ86z2bOc2e1Ef7iHTE/RSld
+fMWpkb6LZeC2YYcWxMZ9LrCdMEvZaTDT/39Z3L+pVQzBMukLXphaQHHsvaqHVzq2
+R8Nq43GM5yufbb4Y2aCzZUXcfdXFszTZ2KODyr6E/irWTZHhWqDMdZCZ1CdH385N
+6i5ZcRy9UIYrh2mhY0lUcvnAdrloFdyKQBnT4eoZK4koB1KyfhDTKLOoCSYTj51H
+oro0MoeWQmR0zM9sSVMr7/LzhNGjoDU2G/JXf+aJhV42J2TUxKiHWyQlHws/3ZSG
+W+doSxI4zMGygaSCZgxYR0ApeLGucSlfluvv9EiGTVYP3cCYOWTQYk5070wpisi+
+zHTsN1XnonDdF9+pRyp/vmFcxLMRxo73m4NeqQyPon9o1cVHWMxyejfKrb9hK49S
+1APBAQjztw9mYCTKNmn+7bhGVCGDh/QC1yjqA3hMwfI1D2V9cuBTAgMBAAEwDQYJ
+KoZIhvcNAQEFBQADggIBAC099rTp7ibS1g14S6VySl4dRLG7iS+2qqRt78DPHUO0
+Q44vHcNP9scGhJ9oqsClii0PFTUe1aJ13nPieKZzQLT6BXweMGVcbUn83oOEQ/+G
+neTOFfb2/0TZDjkv2xotaGd5sDGgIbuxbHeAjJCMGDmb6GmFoU4vz+/YWoBS/eGJ
+ZUil2UP3tSv082w+yT71oViT4rQAxOjSt41bp+tKf6iaOFcvf4vm6v5Ct1uPwj8g
+EF4/WNfON6wyNxlHdiyzQfi40nTQwdwgdtSZ3wjXl03UgK656RrcuYGxe/aCrKtq
+a2pP3ea2KISGTj972oSxnfTueHUactoPOrmfCFLFU72N3a04L4BpO/VK419qDC2i
+/eLs7LOc734Ggws4lvryhLAfWsWHhVKjKAnbaQEeWYyjpBm4W+K/j1PVKSWt/paR
+eYBfbRT6SdLy23bT4YWkwWCb7qR+iuFr/6s6DjTMWsrko6G0FKkYiO5A7Q6wBoF2
+qkpLjg06lUJWRswD5ewxqwk0DDF9CUb3ksIsTIYGsYXx2oURK1SOQI6GIMq+cgNI
+weIubGdXAvwPk1sUywUHHIcjW+vu72jTQj0XGxTPGuq+V+eZyaaqP3/FdMB/zK7K
+qmVZe1CwbOhYcjpky+Dw3baynviPLaMO+LShFWgiMMsHBmqsXzoBsZCwk+cD51zc
+-----END CERTIFICATE-----
diff --git a/src/seastar/tests/unit/test.csr b/src/seastar/tests/unit/test.csr
new file mode 100644
index 00000000..6535fd30
--- /dev/null
+++ b/src/seastar/tests/unit/test.csr
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIFFjCCAv4CAQAwgacxCzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0x
+EjAQBgNVBAcMCVN0b2NraG9sbTEVMBMGA1UECgwMc2N5bGxhZGIub3JnMRUwEwYD
+VQQLDAxzY3lsbGFkYi5vcmcxGjAYBgNVBAMMEXRlc3Quc2N5bGxhZGIub3JnMSYw
+JAYJKoZIhvcNAQkBFhdwb3N0bWFzdGVyQHNjeWxsYWRiLm9yZzCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAMgUwyxgR/zPrpDj2OE+mCybAN3Dj5Ia3vTk
+79d9RES9c1mMZL6HMGMgfQib7iKoPHVNyT+X6FqvoCASTDoz7xIReVhQMd2U3CFG
+ii2cciRqYbMJyQmGT2K84qs7/apRC+qdqNAUxH9pkv2vG+Zbkliz3V/LGp/sOCKH
+8+oZN9FFt5kPzS91fX1UNMZI46wAzQpTJjaY5UniLuCF1iegKEu7s9vxCWj4AJ86
+z2bOc2e1Ef7iHTE/RSldfMWpkb6LZeC2YYcWxMZ9LrCdMEvZaTDT/39Z3L+pVQzB
+MukLXphaQHHsvaqHVzq2R8Nq43GM5yufbb4Y2aCzZUXcfdXFszTZ2KODyr6E/irW
+TZHhWqDMdZCZ1CdH385N6i5ZcRy9UIYrh2mhY0lUcvnAdrloFdyKQBnT4eoZK4ko
+B1KyfhDTKLOoCSYTj51Horo0MoeWQmR0zM9sSVMr7/LzhNGjoDU2G/JXf+aJhV42
+J2TUxKiHWyQlHws/3ZSGW+doSxI4zMGygaSCZgxYR0ApeLGucSlfluvv9EiGTVYP
+3cCYOWTQYk5070wpisi+zHTsN1XnonDdF9+pRyp/vmFcxLMRxo73m4NeqQyPon9o
+1cVHWMxyejfKrb9hK49S1APBAQjztw9mYCTKNmn+7bhGVCGDh/QC1yjqA3hMwfI1
+D2V9cuBTAgMBAAGgKTAnBgkqhkiG9w0BCQ4xGjAYMAkGA1UdEwQCMAAwCwYDVR0P
+BAQDAgXgMA0GCSqGSIb3DQEBBQUAA4ICAQBti1cO3CAi/fti2gL96gfjO9Q5i9yo
+TcERqGSFeCNxnz1YFy9qQzoMVNHMs12twcXn/ScEXJj7k+AAoTHbmQGrH6tEjqIl
+99c4b75xfIImG9HR2isf0f1n9m+BhQ9OH2p/W7Wscmu0oudUThyvwTgAUWz/UOyJ
+N85yoB0F3cwW474Bkfhkh4qm7l/4hwqFgbMdnqZrCJX+vyoXTquyUIW7m0K/dZer
+KL7RtRghTJhpf3F2x3ExEI4UDci6WqwzRhihWb4/4chQjv6WtZtBDLmRMGmTFY3J
+miWFBHJk+V0RNzy6uE7R+9DzkxEWEmNYmfpVenful/PJge+UUYew5v9XK0RtXHzc
+gN4zAqOmfNMnYxDQs11Uzvp2M75yNLpShYxcITbSfugMeousmJypXrCn95OwDRfC
+T4xi7rsfmdyznfkIextZjwSZn8AwfNTvv+PaSkB1DuUoUQ7OLawsHettMmQ4HPf6
+bYKD8U/hHpdjB/53dYFMATD5eSa7ag8osuDlOb/V2YsFnEkydbCNW7W2ujUzJpas
+tbFgPZ7eAIryfm+ktw5z444C0/zPgk781+8bpd560O1mDmUwqM/HyK+jHbg91WVd
+hutKMpZfv6IAlQrNa1Tyw5Y4L1fUu99OfKQ5Z93X/JWsT+i10z2LHyZurDZ8kBdA
+gnAqawq0vVWvIQ==
+-----END CERTIFICATE REQUEST-----
diff --git a/src/seastar/tests/unit/test.key b/src/seastar/tests/unit/test.key
new file mode 100644
index 00000000..315bd89b
--- /dev/null
+++ b/src/seastar/tests/unit/test.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAyBTDLGBH/M+ukOPY4T6YLJsA3cOPkhre9OTv131ERL1zWYxk
+vocwYyB9CJvuIqg8dU3JP5foWq+gIBJMOjPvEhF5WFAx3ZTcIUaKLZxyJGphswnJ
+CYZPYrziqzv9qlEL6p2o0BTEf2mS/a8b5luSWLPdX8san+w4Iofz6hk30UW3mQ/N
+L3V9fVQ0xkjjrADNClMmNpjlSeIu4IXWJ6AoS7uz2/EJaPgAnzrPZs5zZ7UR/uId
+MT9FKV18xamRvotl4LZhhxbExn0usJ0wS9lpMNP/f1ncv6lVDMEy6QtemFpAcey9
+qodXOrZHw2rjcYznK59tvhjZoLNlRdx91cWzNNnYo4PKvoT+KtZNkeFaoMx1kJnU
+J0ffzk3qLllxHL1QhiuHaaFjSVRy+cB2uWgV3IpAGdPh6hkriSgHUrJ+ENMos6gJ
+JhOPnUeiujQyh5ZCZHTMz2xJUyvv8vOE0aOgNTYb8ld/5omFXjYnZNTEqIdbJCUf
+Cz/dlIZb52hLEjjMwbKBpIJmDFhHQCl4sa5xKV+W6+/0SIZNVg/dwJg5ZNBiTnTv
+TCmKyL7MdOw3VeeicN0X36lHKn++YVzEsxHGjvebg16pDI+if2jVxUdYzHJ6N8qt
+v2Erj1LUA8EBCPO3D2ZgJMo2af7tuEZUIYOH9ALXKOoDeEzB8jUPZX1y4FMCAwEA
+AQKCAgAX29n2Rbjv3bgcUP9AxN0SnJ061KIfMxMZMt+i264zYEAMEqDE04wilfIy
+/50jBtrGxjLUYYH0pnK6wFPUsPK2Pd0xecaofKLPWQELNVerHgBugCE4AIsg5BNH
+hgzWrXl1Tb2eqotQAj/j/mieTJcj+rbQQID5Rwrem0WrybwNOXoOR4MZQLJpKoxs
+hK6ZiTLqI0YwRoU5DT63yV3jNcb4WPa2qISNvt0cH8Axqza5zC7MLRx8DeZqa1qA
+m5rklOzeIgF5QW7PmIfjyarDsLZJe05BWm7ncALTVYqDnbZ3BnQe4bMwTZlKSAhA
+tlNO7BV47zb/7yiscBgIf6WFw2B+G+ZcvNXpcwI+IVRaZgZaFBhnGoj71ppwavrj
+/Klm9jlNuk0Iwmx482WoJne2XQKs/OiWFJmyoBBJeI/NNwucgrXhaujMUrF4Q1O0
+Que/HlkaP7OMzBYdlNG5TkTzNTkiGsEAQKP0dhgB6t8TgJcsIPs+DBH/QRPsSX1A
+7twr9nAncJr6GGs//KoRZjtYEVHVclq33QB1LjB0ohaJQyqcQQAGHgiJLLihxowD
+MKT8CJnxJe1uzBUT7S7GgyV+wNybYhvECIhm1fjZ+uuvVgW/Vj4aj+bmD0q13kO+
+2WLEKTbvdcABFE7WWV0fMjh6QpUq636VZeJKWFlHJCf52O4ZWQKCAQEA6hoJOQjb
+MSVc/USlMggaeUHUtQxC7+sa95XvEODI/lby9Bp9cLdoIyN+8xFv7mp3W6Nc40i1
+kx1vNoHGL6bXnKzvCWxfNmzC1lkPKCllMzveiooUlkZetCMAHNjLGAaPCJCj4I6o
+qSO02wcXAVbG6+MC3w31KEaaYXOCKkjWbb41IQPQmt9EAP5VNG+lCFqgzDyuOCoK
+dtVmq0HbQEzZxTA4i7P1DEs39vJ5ygLPyQKP+gfMaOcAYaRGv+IzeQN+qTNYd4CX
+xyazBOctftMlMF33BflG3EzzB8AceogHvsabKIPfvV+AuVGGxoswqFNYaDo1xKZy
+A3Y6vJLUztHVnwKCAQEA2swLj6AKR2BZo1y6KQ62mE3/vI0+aFNWGHkI9BHDsNCh
+N4gTnXv51J6amxWoteBlnx2S34GcxBFkcGHy4iNVcHOTEHhw7LxKetqiNxyPClf4
+tC9BanBPeHIbnzSCsM0y4AlTD/qKoDG0RKec8jtg3X8KdtdbXzEfPrP4OLLVzI9S
+qTG96UBXMwU9GmuKnMnB7YdO7L+XG18T9yC3VDGdbvdIMSVaahIuG/JtPUHVgTFU
+fhXIthPW1uWaiJMpukLXvk08e+VKi+R10dgtqJStYlyxhQJekZ6poFu0RHywOxgz
+GOUN6LXvhaK5/Q9zuHfw2Z8/EAQDzv3WBOoKaYMwzQKCAQEA5k/PGwmXe/ZiUdmj
+HGHUic82URhLEnafBU3A8T31ACTSyUz2dFo8XbWiQ8i38jtUSheiXwk9egrPSzTJ
+oj/miAGq+f5gfc7qsK7VtpFjOtB/1JScRGve6CI3DipLRMvPFIIYd+hiDmVOnN2Q
+yIRzF8czH/c/ZexwHcfiG2+lZjpnUp/KL1PQN5oAgaIFHv7qi4R3clIKEXdJltsn
+tU7mQJV8TBUz1HB8ErvjDddOTVf0Bex+MgZx3Z4c7NLCCSBjEFuvgYJF0GxGW2CE
+5e0c/US0rm/cQwiBYPKkEfiahhoms+lpyYmXvrpyegqeSDwXEhOSqnGGYdmDV9qs
++vx9yQKCAQBSgv6dJGKb9kb3p4GA+E5983RNHAr79umSAxsQSB6/cH5L3eJf0qAt
+wb5WW/2q0TwhnFqGNW/0NQbmptmc1GxlIwDEBle3v25rFEZ2eCutX+2nreaCiGY/
+6vBlwrzChi/4cyvC694ZeYUdGwTCCQiHn2BH3wFTTcgVsnMalr6wDSDEM8EF1MDN
+ud0IOKQmaqPauttVxw7qQJnb6ZeZhbh0X4b3GboWJFMFMRBnOIuW1A6kGfz+RU8Q
+7bewZ6cl2g1Yc/IqWxcY2IhiIZ9eYutcG87KdVFKo0Ye9lZuOYOQVZj3e3IaX3o7
+sFlpiMlAOOM4fqielpUFG475fXV9wv7FAoIBAQDdgbYXBVbcqo912gFE1brGaWu+
+OlUtga5K3xXMe9pesr97EGJ+pBw6byBEOEON4Ge+39qIsnA3tf+t9+K9HoChjVgj
+3amZGjrnTdO08B3uqUiXSGRI9V9GiXcBm7xNlVynCDFaIVDoxaMdkZQSEOplUkwE
+NAXD/bfFbCUzOTnEj2Fjjd5yRG94/xVvPhlkCTK2CUIL+LzCNSeEweKZOHdrzL86
+rrBAUO6ELjqLneDnTGLhlnjc6OsYF9LCaRyigxFlWeNM0V/27/fn8CJMRuQ0Iv+x
+eZzlLW/oTVBL7fvTX8fIa4Q7jWpxyLRuuiJ8GEY7eYhQ8thTRffK5XC2JDu9
+-----END RSA PRIVATE KEY-----
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 00000000..c83b95d1
--- /dev/null
+++ b/src/seastar/tests/unit/thread_context_switch_test.cc
@@ -0,0 +1,95 @@
+/*
+ * 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>
+
+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, [] {
+ return do_with(distributed<context_switch_tester>(), [] (distributed<context_switch_tester>& dcst) {
+ 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([] {
+ 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 00000000..c4198bd7
--- /dev/null
+++ b/src/seastar/tests/unit/thread_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) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/thread.hh>
+#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/semaphore.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sleep.hh>
+
+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();
+ }
+}
+
+SEASTAR_TEST_CASE(test_thread_sched_group) {
+ return async([] {
+ float metered_ratio = 0.2;
+ thread_scheduling_group sched_group(1ms, metered_ratio);
+ float metered_result = 0;
+ float unmetered_result = 0;
+ bool done = false;
+ uint64_t u_ctr = 0, m_ctr = 0;
+ thread unmetered([&] { compute(unmetered_result, done, u_ctr); });
+ thread_attributes metered_thread_attributes;
+ metered_thread_attributes.scheduling_group = &sched_group;
+ thread metered(metered_thread_attributes, [&] { compute(metered_result, done, m_ctr); });
+ sleep(500ms).get();
+ done = true;
+ when_all(metered.join(), unmetered.join()).discard_result().get();
+ auto ratio = float(m_ctr) / (u_ctr + m_ctr);
+#ifndef DEBUG
+ BOOST_REQUIRE(ratio > metered_ratio - 0.05);
+ BOOST_REQUIRE(ratio < metered_ratio + 0.05);
+#else
+ // debug mode is too slow to test this accurately
+ (void)ratio;
+#endif
+ });
+}
+
+#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
diff --git a/src/seastar/tests/unit/timer_test.cc b/src/seastar/tests/unit/timer_test.cc
new file mode 100644
index 00000000..5b7f354f
--- /dev/null
+++ b/src/seastar/tests/unit/timer_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) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/print.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(); });
+ }
+
+ 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; });
+ }
+};
+
+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");
+ 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 00000000..d56c7e67
--- /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 00000000..929d2361
--- /dev/null
+++ b/src/seastar/tests/unit/tls_test.cc
@@ -0,0 +1,532 @@
+/*
+ * 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/do_with.hh>
+#include "test_case.hh"
+#include <seastar/core/sstring.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/net/tls.hh>
+
+#if 0
+#include <gnutls/gnutls.h>
+
+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
+
+using namespace seastar;
+
+static future<> connect_to_ssl_addr(::shared_ptr<tls::certificate_credentials> certs, ipv4_addr 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) {
+ auto addr = make_ipv4_address(ipv4_addr("216.58.209.132:443"));
+ return connect_to_ssl_addr(std::move(certs), addr);
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client) {
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ return certs->set_x509_trust_file("tests/unit/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;
+ 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;
+ 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",
+ "NONE:+VERS-TLS-ALL:+AES-128-CBC:+ECDHE-RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1:+CURVE-SECP256R1",
+ "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;
+ 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;
+ b.set_system_trust();
+ b.set_priority_string(prio);
+ return connect_to_ssl_google(b.build_certificate_credentials()).then([] {
+ BOOST_FAIL("Expected exception");
+ }).handle_exception([](auto ep) {
+ // ok.
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_failed_connect) {
+ tls::credentials_builder b;
+ 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 = engine().listen(addr, opts);
+
+ auto c = server.accept();
+
+ tls::credentials_builder b;
+ b.set_system_trust();
+
+ auto f = connect_to_ssl_addr(b.build_certificate_credentials(), addr);
+
+
+ return c.then([this, f = std::move(f)](::connected_socket s, socket_address) mutable {
+ 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("tests/unit/test.crt", "tests/unit/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 = tls::listen(certs, addr, opts);
+ auto c = server.accept();
+ BOOST_CHECK(!c.available()); // should not be finished
+
+ server.abort_accept();
+
+ return c.then([](auto, 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("tests/unit/test.crt", "tests/unit/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("tests/unit/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.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 = engine().listen(addr, opts);
+ auto sa = server.accept();
+
+ tls::credentials_builder b;
+ b.set_x509_trust_file("tests/unit/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;
+
+ streams(::connected_socket cs) : s(std::move(cs)), in(s.input()), out(s.output())
+ {}
+};
+
+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;
+public:
+ echoserver(size_t message_size)
+ : _certs(
+ ::make_shared<tls::server_credentials>(
+ ::make_shared<tls::dh_params>()))
+ , _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);
+
+ with_gate(_gate, [this] {
+ return _socket.accept().then([this](::connected_socket s, socket_address) {
+ 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<>();
+ }
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error &) {
+ // assume ok
+ return make_ready_future<>();
+ }
+ return make_exception_future(std::move(ep));
+ });
+ });
+ return make_ready_future<>();
+ });
+ }
+
+ future<> stop() {
+ _stopped = true;
+ _socket.abort_accept();
+ return _gate.close().handle_exception([] (std::exception_ptr ignored) { });
+ }
+};
+
+static future<> run_echo_test(sstring message,
+ int loops,
+ sstring trust,
+ sstring name,
+ sstring crt = "tests/unit/test.crt",
+ sstring key = "tests/unit/test.key",
+ tls::client_auth ca = tls::client_auth::NONE,
+ sstring client_crt = {},
+ sstring client_key = {},
+ bool do_read = true
+)
+{
+ 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);
+ }
+
+ return f.then([=] {
+ return certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
+ }).then([=] {
+ return server->start(msg->size()).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, do_read](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) {
+ 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()) {
+ f2.handle_exception([] (std::exception_ptr ignored) { });
+ return std::move(f1);
+ }
+ f1.handle_exception([] (std::exception_ptr ignored) { });
+ return std::move(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, "tests/unit/catest.pem", "test.scylladb.org");
+}
+
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_again) {
+ return run_echo_test(message, 20, "tests/unit/catest.pem", "test.scylladb.org");
+}
+
+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, "tests/unit/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, "tests/unit/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(sstring::initialized_later(), 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, "tests/unit/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, "tests/unit/catest.pem", "test.scylladb.org", "tests/unit/test.crt", "tests/unit/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, "tests/unit/catest.pem", "test.scylladb.org", "tests/unit/test.crt", "tests/unit/test.key", tls::client_auth::REQUIRE, "tests/unit/test.crt", "tests/unit/test.key");
+}
+
+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(sstring::initialized_later(), 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, "tests/unit/catest.pem", "test.scylladb.org", "tests/unit/test.crt", "tests/unit/test.key", tls::client_auth::NONE, {}, {}, false);
+ });
+}
+
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 00000000..6a278f01
--- /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/unwind_test.cc b/src/seastar/tests/unit/unwind_test.cc
new file mode 100644
index 00000000..b2f2e7f5
--- /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 00000000..98fdee18
--- /dev/null
+++ b/src/seastar/tests/unit/weak_ptr_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 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> {};
+
+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));
+}