summaryrefslogtreecommitdiffstats
path: root/src/seastar/tests/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/seastar/tests/unit
parentInitial commit. (diff)
downloadceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz
ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/seastar/tests/unit/CMakeLists.txt560
-rw-r--r--src/seastar/tests/unit/abort_source_test.cc86
-rw-r--r--src/seastar/tests/unit/alien_test.cc116
-rw-r--r--src/seastar/tests/unit/alloc_test.cc162
-rw-r--r--src/seastar/tests/unit/allocator_test.cc220
-rw-r--r--src/seastar/tests/unit/cert.cfg.in23
-rw-r--r--src/seastar/tests/unit/checked_ptr_test.cc125
-rw-r--r--src/seastar/tests/unit/chunked_fifo_test.cc358
-rw-r--r--src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc173
-rw-r--r--src/seastar/tests/unit/circular_buffer_test.cc109
-rw-r--r--src/seastar/tests/unit/connect_test.cc74
-rw-r--r--src/seastar/tests/unit/coroutines_test.cc153
-rw-r--r--src/seastar/tests/unit/defer_test.cc76
-rw-r--r--src/seastar/tests/unit/deleter_test.cc79
-rw-r--r--src/seastar/tests/unit/directory_test.cc86
-rw-r--r--src/seastar/tests/unit/distributed_test.cc318
-rw-r--r--src/seastar/tests/unit/dns_test.cc131
-rw-r--r--src/seastar/tests/unit/execution_stage_test.cc334
-rw-r--r--src/seastar/tests/unit/expiring_fifo_test.cc190
-rw-r--r--src/seastar/tests/unit/fair_queue_test.cc413
-rw-r--r--src/seastar/tests/unit/file_io_test.cc756
-rw-r--r--src/seastar/tests/unit/file_utils_test.cc287
-rw-r--r--src/seastar/tests/unit/foreign_ptr_test.cc132
-rw-r--r--src/seastar/tests/unit/fsnotifier_test.cc227
-rw-r--r--src/seastar/tests/unit/fstream_test.cc538
-rw-r--r--src/seastar/tests/unit/futures_test.cc1617
-rw-r--r--src/seastar/tests/unit/httpd_test.cc889
-rw-r--r--src/seastar/tests/unit/ipv6_test.cc105
-rw-r--r--src/seastar/tests/unit/json_formatter_test.cc52
-rw-r--r--src/seastar/tests/unit/locking_test.cc223
-rw-r--r--src/seastar/tests/unit/log_buf_test.cc55
-rw-r--r--src/seastar/tests/unit/loopback_socket.hh278
-rw-r--r--src/seastar/tests/unit/lowres_clock_test.cc118
-rw-r--r--src/seastar/tests/unit/metrics_test.cc170
-rw-r--r--src/seastar/tests/unit/mkcert.gmk94
-rw-r--r--src/seastar/tests/unit/mock_file.hh113
-rw-r--r--src/seastar/tests/unit/net_config_test.cc127
-rw-r--r--src/seastar/tests/unit/network_interface_test.cc88
-rw-r--r--src/seastar/tests/unit/noncopyable_function_test.cc87
-rw-r--r--src/seastar/tests/unit/output_stream_test.cc159
-rw-r--r--src/seastar/tests/unit/packet_test.cc121
-rw-r--r--src/seastar/tests/unit/program_options_test.cc63
-rw-r--r--src/seastar/tests/unit/queue_test.cc70
-rw-r--r--src/seastar/tests/unit/request_parser_test.cc74
-rw-r--r--src/seastar/tests/unit/rpc_test.cc1199
-rw-r--r--src/seastar/tests/unit/scheduling_group_test.cc251
-rw-r--r--src/seastar/tests/unit/semaphore_test.cc310
-rw-r--r--src/seastar/tests/unit/sharded_test.cc110
-rw-r--r--src/seastar/tests/unit/shared_ptr_test.cc204
-rwxr-xr-xsrc/seastar/tests/unit/signal_test.cc48
-rw-r--r--src/seastar/tests/unit/simple_stream_test.cc99
-rw-r--r--src/seastar/tests/unit/slab_test.cc127
-rw-r--r--src/seastar/tests/unit/smp_test.cc81
-rw-r--r--src/seastar/tests/unit/socket_test.cc82
-rw-r--r--src/seastar/tests/unit/sstring_test.cc186
-rw-r--r--src/seastar/tests/unit/stall_detector_test.cc108
-rw-r--r--src/seastar/tests/unit/thread_context_switch_test.cc96
-rw-r--r--src/seastar/tests/unit/thread_test.cc264
-rw-r--r--src/seastar/tests/unit/timer_test.cc141
-rw-r--r--src/seastar/tests/unit/tls-ca-bundle.pem4195
-rw-r--r--src/seastar/tests/unit/tls_test.cc845
-rw-r--r--src/seastar/tests/unit/tmpdir.hh49
-rw-r--r--src/seastar/tests/unit/tuple_utils_test.cc99
-rw-r--r--src/seastar/tests/unit/uname_test.cc76
-rw-r--r--src/seastar/tests/unit/unix_domain_test.cc234
-rw-r--r--src/seastar/tests/unit/unwind_test.cc70
-rw-r--r--src/seastar/tests/unit/weak_ptr_test.cc136
67 files changed, 19239 insertions, 0 deletions
diff --git a/src/seastar/tests/unit/CMakeLists.txt b/src/seastar/tests/unit/CMakeLists.txt
new file mode 100644
index 000000000..fd2f90368
--- /dev/null
+++ b/src/seastar/tests/unit/CMakeLists.txt
@@ -0,0 +1,560 @@
+#
+# This file is open source software, licensed to you under the terms
+# of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+# distributed with this work for additional information regarding copyright
+# ownership. You may not use this file except in compliance with the License.
+#
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# Copyright (C) 2018 Scylladb, Ltd.
+#
+
+# Logical target for all unit tests.
+add_custom_target (unit_tests)
+
+set (Seastar_UNIT_TEST_SMP
+ 2
+ CACHE
+ STRING
+ "Run unit tests with this many cores.")
+
+#
+# Define a new unit test with the given name.
+#
+# seastar_add_test (name
+# [KIND {SEASTAR,BOOST,CUSTOM}]
+# [SOURCES source1 source2 ... sourcen]
+# [WORKING_DIRECTORY dir]
+# [LIBRARIES library1 library2 ... libraryn]
+# [RUN_ARGS arg1 arg2 ... argn])
+#
+# There are three kinds of test we support (the KIND parameter):
+#
+# - SEASTAR: Unit tests which use macros like `SEASTAR_TEST_CASE`
+# - BOOST: Unit tests which use macros like `BOOST_AUTO_TEST_CASE`
+# - CUSTOM: Custom tests which need to be specified
+#
+# SEASTAR and BOOST tests will have their output saved for interpretation by the Jenkins continuous integration service
+# if this is configured for the build.
+#
+# KIND can be omitted, in which case it is assumed to be SEASTAR.
+#
+# If SOURCES is provided, then the test files are first compiled into an executable which has the same name as the test
+# but with a suffix ("_test").
+#
+# WORKING_DIRECTORY can be optionally provided to choose where the test is executed.
+#
+# If LIBRARIES is provided along with SOURCES, then the executable is additionally linked with these libraries.
+#
+# RUN_ARGS are optional additional arguments to pass to the executable. For SEASTAR tests, these come after `--`. For
+# CUSTOM tests with no SOURCES, this parameter can be used to specify the executable name as well as its arguments since
+# no executable is compiled.
+#
+function (seastar_add_test name)
+ set (test_kinds
+ SEASTAR
+ BOOST
+ CUSTOM)
+
+ cmake_parse_arguments (parsed_args
+ ""
+ "WORKING_DIRECTORY;KIND"
+ "RUN_ARGS;SOURCES;LIBRARIES;DEPENDS"
+ ${ARGN})
+
+ if (NOT parsed_args_KIND)
+ set (parsed_args_KIND SEASTAR)
+ elseif (NOT (parsed_args_KIND IN_LIST test_kinds))
+ message (FATAL_ERROR "Invalid test kind. KIND must be one of ${test_kinds}")
+ endif ()
+
+ if (parsed_args_SOURCES)
+ # These may be unused.
+ seastar_jenkins_arguments (${name} jenkins_args)
+
+ #
+ # Each kind of test must populate the `args` and `libraries` lists.
+ #
+
+ set (libraries "${parsed_args_LIBRARIES}")
+
+ set (args "")
+ if (parsed_args_KIND STREQUAL "SEASTAR")
+ list (APPEND libraries
+ seastar_testing
+ seastar_private)
+
+ if (NOT (Seastar_JENKINS STREQUAL ""))
+ list (APPEND args ${jenkins_args})
+ endif ()
+
+ list (APPEND args -- -c ${Seastar_UNIT_TEST_SMP})
+ elseif (parsed_args_KIND STREQUAL "BOOST")
+ list (APPEND libraries
+ Boost::unit_test_framework
+ seastar_private)
+
+ if (NOT (Seastar_JENKINS STREQUAL ""))
+ list (APPEND args ${jenkins_args})
+ endif ()
+ endif ()
+
+ list (APPEND args ${parsed_args_RUN_ARGS})
+
+ set (executable_target test_unit_${name})
+ add_executable (${executable_target} ${parsed_args_SOURCES})
+
+ target_link_libraries (${executable_target}
+ PRIVATE ${libraries})
+
+ target_compile_definitions (${executable_target}
+ PRIVATE SEASTAR_TESTING_MAIN)
+
+ if ((Seastar_STACK_GUARDS STREQUAL "ON") OR
+ ((Seastar_STACK_GUARDS STREQUAL "DEFAULT") AND
+ (CMAKE_BUILD_TYPE IN_LIST Seastar_STACK_GUARD_MODES)))
+ target_compile_definitions (${executable_target}
+ PRIVATE
+ SEASTAR_THREAD_STACK_GUARDS)
+ endif ()
+
+ target_include_directories (${executable_target}
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${Seastar_SOURCE_DIR}/src)
+
+ set_target_properties (${executable_target}
+ PROPERTIES
+ OUTPUT_NAME ${name}_test)
+
+ add_dependencies (unit_tests ${executable_target})
+ set (forwarded_args COMMAND ${executable_target} ${args})
+ else ()
+ if (NOT (parsed_args_KIND STREQUAL "CUSTOM"))
+ message (FATAL_ERROR "SOURCES are required for ${parsed_args_KIND} tests")
+ endif ()
+
+ set (forwarded_args COMMAND ${parsed_args_RUN_ARGS})
+ endif ()
+
+ #
+ # We expect `forwarded_args` to be populated correctly at this point.
+ #
+
+ set (target test_unit_${name}_run)
+
+ if (parsed_args_WORKING_DIRECTORY)
+ list (APPEND forwarded_args WORKING_DIRECTORY ${parsed_args_WORKING_DIRECTORY})
+ endif ()
+
+ if (parsed_args_DEPENDS)
+ add_dependencies(${executable_target} ${parsed_args_DEPENDS})
+ endif()
+
+ add_custom_target (${target}
+ ${forwarded_args}
+ USES_TERMINAL)
+
+ add_test (
+ NAME Seastar.unit.${name}
+ COMMAND ${CMAKE_COMMAND} --build ${Seastar_BINARY_DIR} --target ${target})
+
+ set_tests_properties (Seastar.unit.${name}
+ PROPERTIES
+ TIMEOUT ${Seastar_TEST_TIMEOUT}
+ ENVIRONMENT "${Seastar_TEST_ENVIRONMENT}")
+endfunction ()
+
+#
+# Define a new custom unit test whose entry point is a Seastar application.
+#
+# seastar_add_app_test (name
+# [SOURCES source1 source2 ... sourcen]
+# [LIBRARIES library1 library2 ... libraryn]
+# [RUN_ARGS arg1 arg2 ... argn])
+#
+# These kinds of tests are structured like Seastar applications.
+#
+# These tests always link against `seastar_private` and are always invoked with
+# `-c ${Seastar_UNIT_TEST_SMP}`.
+#
+function (seastar_add_app_test name)
+ cmake_parse_arguments (parsed_args
+ ""
+ ""
+ "RUN_ARGS;SOURCES;LIBRARIES"
+ ${ARGN})
+
+ seastar_add_test (${name}
+ KIND CUSTOM
+ SOURCES ${parsed_args_SOURCES}
+ LIBRARIES
+ seastar_private
+ ${parsed_args_LIBRARIES}
+ RUN_ARGS
+ -c ${Seastar_UNIT_TEST_SMP}
+ ${parsed_args_RUN_ARGS})
+endfunction ()
+
+function (prepend_each var prefix)
+ set (result "")
+
+ foreach (x ${ARGN})
+ list (APPEND result ${prefix}/${x})
+ endforeach ()
+
+ set (${var} ${result} PARENT_SCOPE)
+endfunction ()
+
+add_custom_target (test_unit
+ COMMAND ctest --verbose -R Seastar.unit
+ USES_TERMINAL)
+
+seastar_add_test (abort_source
+ SOURCES abort_source_test.cc)
+
+seastar_add_test (alloc
+ SOURCES alloc_test.cc)
+
+if (NOT Seastar_EXECUTE_ONLY_FAST_TESTS)
+ set (allocator_test_args "")
+else ()
+ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set (allocator_test_args --iterations 5)
+ else ()
+ set (allocator_test_args --time 0.1)
+ endif ()
+endif ()
+
+seastar_add_test (allocator
+ SOURCES allocator_test.cc
+ RUN_ARGS ${allocator_test_args})
+
+seastar_add_app_test (alien
+ SOURCES alien_test.cc)
+
+seastar_add_test (checked_ptr
+ SOURCES checked_ptr_test.cc)
+
+seastar_add_test (chunked_fifo
+ SOURCES chunked_fifo_test.cc)
+
+seastar_add_test (circular_buffer
+ SOURCES circular_buffer_test.cc)
+
+seastar_add_test (circular_buffer_fixed_capacity
+ SOURCES circular_buffer_fixed_capacity_test.cc)
+
+seastar_add_test (connect
+ SOURCES connect_test.cc)
+
+seastar_add_test (coroutines
+ SOURCES coroutines_test.cc)
+
+seastar_add_test (defer
+ SOURCES defer_test.cc)
+
+seastar_add_test (deleter
+ SOURCES deleter_test.cc)
+
+seastar_add_app_test (directory
+ SOURCES directory_test.cc)
+
+seastar_add_test (distributed
+ SOURCES distributed_test.cc)
+
+seastar_add_test (dns
+ SOURCES dns_test.cc)
+
+seastar_add_test (execution_stage
+ SOURCES execution_stage_test.cc)
+
+seastar_add_test (expiring_fifo
+ SOURCES expiring_fifo_test.cc)
+
+seastar_add_test (fair_queue
+ SOURCES fair_queue_test.cc)
+
+seastar_add_test (file_io
+ SOURCES file_io_test.cc)
+
+seastar_add_test (file_utils
+ SOURCES file_utils_test.cc)
+
+seastar_add_test (foreign_ptr
+ SOURCES foreign_ptr_test.cc)
+
+seastar_add_test (fsnotifier
+ SOURCES fsnotifier_test.cc)
+
+seastar_add_test (fstream
+ SOURCES
+ fstream_test.cc
+ mock_file.hh)
+
+seastar_add_test (futures
+ SOURCES futures_test.cc)
+
+seastar_add_test (sharded
+ SOURCES sharded_test.cc)
+
+seastar_add_test (httpd
+ SOURCES
+ httpd_test.cc
+ loopback_socket.hh)
+
+seastar_add_test (ipv6
+ SOURCES ipv6_test.cc)
+
+seastar_add_test (network_interface
+ SOURCES network_interface_test.cc)
+
+seastar_add_test (json_formatter
+ SOURCES json_formatter_test.cc)
+
+seastar_add_test (locking
+ SOURCES locking_test.cc)
+
+seastar_add_test (lowres_clock
+ SOURCES lowres_clock_test.cc)
+
+seastar_add_test (metrics
+ SOURCES metrics_test.cc)
+
+seastar_add_test (net_config
+ KIND BOOST
+ SOURCES net_config_test.cc)
+
+seastar_add_test (noncopyable_function
+ KIND BOOST
+ SOURCES noncopyable_function_test.cc)
+
+seastar_add_test (output_stream
+ SOURCES output_stream_test.cc)
+
+seastar_add_test (packet
+ KIND BOOST
+ SOURCES packet_test.cc)
+
+seastar_add_test (program_options
+ KIND BOOST
+ SOURCES program_options_test.cc)
+
+seastar_add_test (queue
+ SOURCES queue_test.cc)
+
+seastar_add_test (request_parser
+ SOURCES request_parser_test.cc)
+
+seastar_add_test (rpc
+ SOURCES
+ loopback_socket.hh
+ rpc_test.cc)
+
+seastar_add_test (semaphore
+ SOURCES semaphore_test.cc)
+
+seastar_add_test (shared_ptr
+ KIND BOOST
+ SOURCES shared_ptr_test.cc)
+
+seastar_add_test (signal
+ SOURCES signal_test.cc)
+
+seastar_add_test (simple_stream
+ KIND BOOST
+ SOURCES simple_stream_test.cc)
+
+# TODO: Disabled for now. See GH-520.
+# seastar_add_test (slab
+# SOURCES slab_test.cc
+# NO_SEASTAR_TESTING_LIBRARY)
+
+seastar_add_app_test (smp
+ SOURCES smp_test.cc)
+
+seastar_add_app_test (socket
+ SOURCES socket_test.cc)
+
+seastar_add_test (sstring
+ KIND BOOST
+ SOURCES sstring_test.cc)
+
+seastar_add_test (stall_detector
+ SOURCES stall_detector_test.cc)
+
+seastar_add_test (thread
+ SOURCES thread_test.cc)
+
+seastar_add_test (scheduling_group
+ SOURCES scheduling_group_test.cc)
+
+seastar_add_app_test (thread_context_switch
+ SOURCES thread_context_switch_test.cc)
+
+seastar_add_app_test (timer
+ SOURCES timer_test.cc)
+
+seastar_add_test (uname
+ KIND BOOST
+ SOURCES uname_test.cc)
+
+function(seastar_add_certgen name)
+ cmake_parse_arguments(CERT
+ ""
+ "SUBJECT;SERVER;NAME;DOMAIN;COMMON;LOCALITY;ORG;WIDTH;STATE;COUNTRY;UNIT;EMAIL;DAYS;ALG"
+ "ALG_OPTS"
+ ${ARGN}
+ )
+
+ if (NOT CERT_SERVER)
+ execute_process(COMMAND hostname
+ RESULT_VARIABLE CERT_SERVER
+ )
+ endif()
+ if (NOT CERT_DOMAIN)
+ execute_process(COMMAND dnsdomainname
+ RESULT_VARIABLE CERT_DOMAIN
+ )
+ endif()
+ if (NOT CERT_NAME)
+ set(CERT_NAME ${CERT_SERVER})
+ endif()
+ if (NOT CERT_COUNTRY)
+ set(CERT_COUNTRY SE)
+ endif()
+ if (NOT CERT_STATE)
+ set(CERT_STATE Stockholm)
+ endif()
+ if (NOT CERT_LOCALITY)
+ set(CERT_LOCALITY ${CERT_STATE})
+ endif()
+ if (NOT CERT_ORG)
+ set(CERT_ORG ${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_UNIT)
+ set(CERT_UNIT ${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_COMMON)
+ set(CERT_COMMON ${CERT_SERVER}.${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_EMAIL)
+ set(CERT_EMAIL postmaster@${CERT_DOMAIN})
+ endif()
+ if (NOT CERT_WIDTH)
+ set(CERT_WIDTH 4096)
+ endif()
+ if (NOT CERT_DAYS)
+ set(CERT_DAYS 3650)
+ endif()
+ if ((NOT CERT_ALG) AND (NOT CERT_ALG_OPTS))
+ set(CERT_ALG_OPTS -pkeyopt rsa_keygen_bits:${CERT_WIDTH})
+ endif()
+ if (NOT CERT_ALG)
+ set(CERT_ALG RSA)
+ endif()
+
+ set(CERT_PRIVKEY ${CERT_NAME}.key)
+ set(CERT_REQ ${CERT_NAME}.csr)
+ set(CERT_CERT ${CERT_NAME}.crt)
+
+ set(CERT_CAPRIVKEY ca${CERT_NAME}.key)
+ set(CERT_CAROOT ca${CERT_NAME}.pem)
+
+ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cert.cfg.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/${CERT_NAME}.cfg"
+ )
+
+ find_program(OPENSSL openssl)
+
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_PRIVKEY}"
+ COMMAND ${OPENSSL} genpkey -out ${CERT_PRIVKEY} -algorithm ${CERT_ALG} ${CERT_ALG_OPTS}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_REQ}"
+ COMMAND ${OPENSSL} req -new -key ${CERT_PRIVKEY} -out ${CERT_REQ} -config ${CERT_NAME}.cfg
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_PRIVKEY}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAPRIVKEY}"
+ COMMAND ${OPENSSL} genpkey -out ${CERT_CAPRIVKEY} -algorithm ${CERT_ALG} ${CERT_ALG_OPTS}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAROOT}"
+ COMMAND ${OPENSSL} req -x509 -new -nodes -key ${CERT_CAPRIVKEY} -days ${CERT_DAYS} -config ${CERT_NAME}.cfg -out ${CERT_CAROOT}
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAPRIVKEY}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT}"
+ COMMAND ${OPENSSL} x509 -req -in ${CERT_REQ} -CA ${CERT_CAROOT} -CAkey ${CERT_CAPRIVKEY} -CAcreateserial -out ${CERT_CERT} -days ${CERT_DAYS}
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_REQ}" "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CAROOT}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+ add_custom_target(${name}
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT}"
+ )
+endfunction()
+
+seastar_add_certgen(testcrt DOMAIN scylladb.org SERVER test)
+seastar_add_certgen(othercrt DOMAIN apa.org SERVER other)
+
+set (tls_certificate_files
+ tls-ca-bundle.pem
+)
+
+prepend_each (
+ in_tls_certificate_files
+ ${CMAKE_CURRENT_SOURCE_DIR}/
+ ${tls_certificate_files})
+
+prepend_each (
+ out_tls_certificate_files
+ ${CMAKE_CURRENT_BINARY_DIR}/
+ ${tls_certificate_files})
+
+add_custom_command (
+ DEPENDS ${in_tls_certificate_files}
+ OUTPUT ${out_tls_certificate_files}
+ COMMAND ${CMAKE_COMMAND} -E copy ${in_tls_certificate_files} ${CMAKE_CURRENT_BINARY_DIR})
+
+add_custom_target(tls_files
+ DEPENDS ${out_tls_certificate_files}
+)
+
+seastar_add_test (tls
+ DEPENDS tls_files testcrt othercrt
+ SOURCES tls_test.cc
+ LIBRARIES boost_filesystem
+ WORKING_DIRECTORY ${Seastar_BINARY_DIR})
+
+seastar_add_test (tuple_utils
+ KIND BOOST
+ SOURCES tuple_utils_test.cc)
+
+seastar_add_test (unix_domain
+ SOURCES unix_domain_test.cc)
+
+seastar_add_test (unwind
+ KIND BOOST
+ SOURCES unwind_test.cc)
+
+seastar_add_test (weak_ptr
+ KIND BOOST
+ SOURCES weak_ptr_test.cc)
+
+seastar_add_test (log_buf
+ SOURCES log_buf_test.cc)
diff --git a/src/seastar/tests/unit/abort_source_test.cc b/src/seastar/tests/unit/abort_source_test.cc
new file mode 100644
index 000000000..066500637
--- /dev/null
+++ b/src/seastar/tests/unit/abort_source_test.cc
@@ -0,0 +1,86 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/gate.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/do_with.hh>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_abort_source_notifies_subscriber) {
+ bool signalled = false;
+ auto as = abort_source();
+ auto st_opt = as.subscribe([&signalled] () noexcept {
+ signalled = true;
+ });
+ BOOST_REQUIRE_EQUAL(true, bool(st_opt));
+ as.request_abort();
+ BOOST_REQUIRE_EQUAL(true, signalled);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_abort_source_subscription_unregister) {
+ bool signalled = false;
+ auto as = abort_source();
+ auto st_opt = as.subscribe([&signalled] () noexcept {
+ signalled = true;
+ });
+ BOOST_REQUIRE_EQUAL(true, bool(st_opt));
+ st_opt = { };
+ as.request_abort();
+ BOOST_REQUIRE_EQUAL(false, signalled);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_abort_source_rejects_subscription) {
+ auto as = abort_source();
+ as.request_abort();
+ auto st_opt = as.subscribe([] () noexcept { });
+ BOOST_REQUIRE_EQUAL(false, bool(st_opt));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_sleep_abortable) {
+ auto as = std::make_unique<abort_source>();
+ auto f = sleep_abortable(100s, *as).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have failed");
+ } catch (const sleep_aborted& e) {
+ // expected
+ } catch (...) {
+ BOOST_FAIL("unexpected exception");
+ }
+ });
+ as->request_abort();
+ return f.finally([as = std::move(as)] { });
+}
+
+// Verify that negative sleep does not sleep forever. It should not sleep
+// at all.
+SEASTAR_TEST_CASE(test_negative_sleep_abortable) {
+ return do_with(abort_source(), [] (abort_source& as) {
+ return sleep_abortable(-10s, as);
+ });
+}
diff --git a/src/seastar/tests/unit/alien_test.cc b/src/seastar/tests/unit/alien_test.cc
new file mode 100644
index 000000000..3aa527a4a
--- /dev/null
+++ b/src/seastar/tests/unit/alien_test.cc
@@ -0,0 +1,116 @@
+// -*- mode:C++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*-
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 Red Hat
+ */
+
+#include <future>
+#include <numeric>
+#include <iostream>
+#include <seastar/core/alien.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/posix.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/util/later.hh>
+
+using namespace seastar;
+
+enum {
+ ENGINE_READY = 24,
+ ALIEN_DONE = 42,
+};
+
+int main(int argc, char** argv)
+{
+ // we need a protocol that both seastar and alien understand.
+ // and on which, a seastar future can wait.
+ int engine_ready_fd = eventfd(0, 0);
+ auto alien_done = file_desc::eventfd(0, 0);
+
+ // use the raw fd, because seastar engine want to take over the fds, if it
+ // polls on them.
+ auto zim = std::async([engine_ready_fd,
+ alien_done=alien_done.get()] {
+ eventfd_t result = 0;
+ // wait until the seastar engine is ready
+ int r = ::eventfd_read(engine_ready_fd, &result);
+ if (r < 0) {
+ return -EINVAL;
+ }
+ if (result != ENGINE_READY) {
+ return -EINVAL;
+ }
+ std::vector<std::future<int>> counts;
+ for (auto i : boost::irange(0u, smp::count)) {
+ // send messages from alien.
+ counts.push_back(alien::submit_to(i, [i] {
+ return seastar::make_ready_future<int>(i);
+ }));
+ }
+ // std::future<void>
+ alien::submit_to(0, [] {
+ return seastar::make_ready_future<>();
+ }).wait();
+ int total = 0;
+ for (auto& count : counts) {
+ total += count.get();
+ }
+ // i am done. dismiss the engine
+ ::eventfd_write(alien_done, ALIEN_DONE);
+ return total;
+ });
+
+ seastar::app_template app;
+ eventfd_t result = 0;
+ app.run(argc, argv, [&] {
+ return seastar::now().then([engine_ready_fd] {
+ // engine ready!
+ ::eventfd_write(engine_ready_fd, ENGINE_READY);
+ return seastar::now();
+ }).then([alien_done = std::move(alien_done), &result]() mutable {
+ return do_with(seastar::pollable_fd(std::move(alien_done)), [&result] (pollable_fd& alien_done_fds) {
+ // check if alien has dismissed me.
+ return alien_done_fds.readable().then([&result, &alien_done_fds] {
+ auto ret = alien_done_fds.get_file_desc().read(&result, sizeof(result));
+ return make_ready_future<size_t>(*ret);
+ });
+ });
+ }).then([&result](size_t n) {
+ if (n != sizeof(result)) {
+ throw std::runtime_error("read from eventfd failed");
+ }
+ if (result != ALIEN_DONE) {
+ throw std::logic_error("alien failed to dismiss me");
+ }
+ return seastar::now();
+ }).handle_exception([](auto ep) {
+ std::cerr << "Error: " << ep << std::endl;
+ }).finally([] {
+ seastar::engine().exit(0);
+ });
+ });
+ int total = zim.get();
+ const auto shards = boost::irange(0u, smp::count);
+ auto expected = std::accumulate(std::begin(shards), std::end(shards), 0);
+ if (total != expected) {
+ std::cerr << "Bad total: " << total << " != " << expected << std::endl;
+ return 1;
+ }
+}
diff --git a/src/seastar/tests/unit/alloc_test.cc b/src/seastar/tests/unit/alloc_test.cc
new file mode 100644
index 000000000..7569d49d9
--- /dev/null
+++ b/src/seastar/tests/unit/alloc_test.cc
@@ -0,0 +1,162 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/memory.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/temporary_buffer.hh>
+#include <vector>
+#include <future>
+#include <iostream>
+
+#include <malloc.h>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(alloc_almost_all_and_realloc_it_with_a_smaller_size) {
+#ifndef SEASTAR_DEFAULT_ALLOCATOR
+ auto all = memory::stats().total_memory();
+ auto reserve = size_t(0.02 * all);
+ auto to_alloc = all - (reserve + (10 << 20));
+ auto orig_to_alloc = to_alloc;
+ auto obj = malloc(to_alloc);
+ while (!obj) {
+ to_alloc *= 0.9;
+ obj = malloc(to_alloc);
+ }
+ BOOST_REQUIRE(to_alloc > orig_to_alloc / 4);
+ BOOST_REQUIRE(obj != nullptr);
+ auto obj2 = realloc(obj, to_alloc - (1 << 20));
+ BOOST_REQUIRE(obj == obj2);
+ free(obj2);
+#endif
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(malloc_0_and_free_it) {
+#ifndef SEASTAR_DEFAULT_ALLOCATOR
+ auto obj = malloc(0);
+ BOOST_REQUIRE(obj != nullptr);
+ free(obj);
+#endif
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_live_objects_counter_with_cross_cpu_free) {
+ return smp::submit_to(1, [] {
+ auto ret = std::vector<std::unique_ptr<bool>>(1000000);
+ for (auto& o : ret) {
+ o = std::make_unique<bool>(false);
+ }
+ return ret;
+ }).then([] (auto&& vec) {
+ vec.clear(); // cause cross-cpu free
+ BOOST_REQUIRE(memory::stats().live_objects() < std::numeric_limits<size_t>::max() / 2);
+ });
+}
+
+SEASTAR_TEST_CASE(test_aligned_alloc) {
+ for (size_t align = sizeof(void*); align <= 65536; align <<= 1) {
+ for (size_t size = align; size <= align * 2; size <<= 1) {
+ void *p = aligned_alloc(align, size);
+ BOOST_REQUIRE(p != nullptr);
+ BOOST_REQUIRE((reinterpret_cast<uintptr_t>(p) % align) == 0);
+ ::memset(p, 0, size);
+ free(p);
+ }
+ }
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_temporary_buffer_aligned) {
+ for (size_t align = sizeof(void*); align <= 65536; align <<= 1) {
+ for (size_t size = align; size <= align * 2; size <<= 1) {
+ auto buf = temporary_buffer<char>::aligned(align, size);
+ void *p = buf.get_write();
+ BOOST_REQUIRE(p != nullptr);
+ BOOST_REQUIRE((reinterpret_cast<uintptr_t>(p) % align) == 0);
+ ::memset(p, 0, size);
+ }
+ }
+ return make_ready_future<>();
+}
+
+#ifndef SEASTAR_DEFAULT_ALLOCATOR
+
+struct thread_alloc_info {
+ memory::statistics before;
+ memory::statistics after;
+ void *ptr;
+};
+
+template <typename Func>
+thread_alloc_info run_with_stats(Func&& f) {
+ return std::async([&f](){
+ auto before = seastar::memory::stats();
+ void* ptr = f();
+ auto after = seastar::memory::stats();
+ return thread_alloc_info{before, after, ptr};
+ }).get();
+}
+
+template <typename Func>
+void test_allocation_function(Func f) {
+ // alien alloc and free
+ auto alloc_info = run_with_stats(f);
+ auto free_info = std::async([p = alloc_info.ptr]() {
+ auto before = seastar::memory::stats();
+ free(p);
+ auto after = seastar::memory::stats();
+ return thread_alloc_info{before, after, nullptr};
+ }).get();
+
+ // there were mallocs
+ BOOST_REQUIRE(alloc_info.after.foreign_mallocs() - alloc_info.before.foreign_mallocs() > 0);
+ // mallocs balanced with frees
+ BOOST_REQUIRE(alloc_info.after.foreign_mallocs() - alloc_info.before.foreign_mallocs() == free_info.after.foreign_frees() - free_info.before.foreign_frees());
+
+ // alien alloc reactor free
+ auto info = run_with_stats(f);
+ auto before_cross_frees = memory::stats().foreign_cross_frees();
+ free(info.ptr);
+ BOOST_REQUIRE(memory::stats().foreign_cross_frees() - before_cross_frees == 1);
+
+ // reactor alloc, alien free
+ void *p = f();
+ auto alien_cross_frees = std::async([p]() {
+ auto frees_before = memory::stats().cross_cpu_frees();
+ free(p);
+ return memory::stats().cross_cpu_frees()-frees_before;
+ }).get();
+ BOOST_REQUIRE(alien_cross_frees == 1);
+}
+
+SEASTAR_TEST_CASE(test_foreign_function_use_glibc_malloc) {
+ test_allocation_function([]() ->void * { return malloc(1); });
+ test_allocation_function([]() { return realloc(NULL, 10); });
+ test_allocation_function([]() {
+ auto p = malloc(1);
+ return realloc(p, 1000);
+ });
+ test_allocation_function([]() { return aligned_alloc(4, 1024); });
+ return make_ready_future<>();
+}
+#endif
diff --git a/src/seastar/tests/unit/allocator_test.cc b/src/seastar/tests/unit/allocator_test.cc
new file mode 100644
index 000000000..cc0e6c80d
--- /dev/null
+++ b/src/seastar/tests/unit/allocator_test.cc
@@ -0,0 +1,220 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2014 Cloudius Systems
+ */
+
+#include <seastar/core/memory.hh>
+#include <seastar/core/timer.hh>
+#include <seastar/testing/test_runner.hh>
+#include <cmath>
+#include <iostream>
+#include <iomanip>
+#include <algorithm>
+#include <cassert>
+#include <memory>
+#include <chrono>
+#include <boost/program_options.hpp>
+
+using namespace seastar;
+
+struct allocation {
+ size_t n;
+ std::unique_ptr<char[]> data;
+ char poison;
+ allocation(size_t n, char poison) : n(n), data(new char[n]), poison(poison) {
+ std::fill_n(data.get(), n, poison);
+ }
+ ~allocation() {
+ verify();
+ }
+ allocation(allocation&& x) noexcept = default;
+ void verify() {
+ if (data) {
+ assert(std::find_if(data.get(), data.get() + n, [this] (char c) {
+ return c != poison;
+ }) == data.get() + n);
+ }
+ }
+ allocation& operator=(allocation&& x) {
+ verify();
+ if (this != &x) {
+ data = std::move(x.data);
+ n = x.n;
+ poison = x.poison;
+ }
+ return *this;
+ }
+};
+
+#ifdef __cpp_aligned_new
+
+template <size_t N>
+struct alignas(N) cpp17_allocation final {
+ char v;
+};
+
+struct test17 {
+ struct handle {
+ const test17* d;
+ void* p;
+ handle(const test17* d, void* p) : d(d), p(p) {}
+ handle(const handle&) = delete;
+ handle(handle&& x) noexcept : d(std::exchange(x.d, nullptr)), p(std::exchange(x.p, nullptr)) {}
+ handle& operator=(const handle&) = delete;
+ handle& operator=(handle&& x) noexcept {
+ std::swap(d, x.d);
+ std::swap(p, x.p);
+ return *this;
+ }
+ ~handle() {
+ if (d) {
+ d->free(p);
+ }
+ }
+ };
+ virtual ~test17() {}
+ virtual handle alloc() const = 0;
+ virtual void free(void* ptr) const = 0;
+};
+
+template <size_t N>
+struct test17_concrete : test17 {
+ using value_type = cpp17_allocation<N>;
+ static_assert(sizeof(value_type) == N, "language does not guarantee size >= align");
+ virtual handle alloc() const override {
+ auto ptr = new value_type();
+ assert((reinterpret_cast<uintptr_t>(ptr) & (N - 1)) == 0);
+ return handle{this, ptr};
+ }
+ virtual void free(void* ptr) const override {
+ delete static_cast<value_type*>(ptr);
+ }
+};
+
+void test_cpp17_aligned_allocator() {
+ std::vector<std::unique_ptr<test17>> tv;
+ tv.push_back(std::make_unique<test17_concrete<1>>());
+ tv.push_back(std::make_unique<test17_concrete<2>>());
+ tv.push_back(std::make_unique<test17_concrete<4>>());
+ tv.push_back(std::make_unique<test17_concrete<8>>());
+ tv.push_back(std::make_unique<test17_concrete<16>>());
+ tv.push_back(std::make_unique<test17_concrete<64>>());
+ tv.push_back(std::make_unique<test17_concrete<128>>());
+ tv.push_back(std::make_unique<test17_concrete<2048>>());
+ tv.push_back(std::make_unique<test17_concrete<4096>>());
+ tv.push_back(std::make_unique<test17_concrete<4096*16>>());
+ tv.push_back(std::make_unique<test17_concrete<4096*256>>());
+
+ std::default_random_engine random_engine(testing::local_random_engine());
+ std::uniform_int_distribution<> type_dist(0, 1);
+ std::uniform_int_distribution<size_t> size_dist(0, tv.size() - 1);
+ std::uniform_real_distribution<> which_dist(0, 1);
+
+ std::vector<test17::handle> allocs;
+ for (unsigned i = 0; i < 10000; ++i) {
+ auto type = type_dist(random_engine);
+ switch (type) {
+ case 0: {
+ size_t sz_idx = size_dist(random_engine);
+ allocs.push_back(tv[sz_idx]->alloc());
+ break;
+ }
+ case 1:
+ if (!allocs.empty()) {
+ size_t idx = which_dist(random_engine) * allocs.size();
+ std::swap(allocs[idx], allocs.back());
+ allocs.pop_back();
+ }
+ break;
+ }
+ }
+}
+
+#else
+
+void test_cpp17_aligned_allocator() {
+}
+
+#endif
+
+int main(int ac, char** av) {
+ namespace bpo = boost::program_options;
+ bpo::options_description opts("Allowed options");
+ opts.add_options()
+ ("help", "produce this help message")
+ ("iterations", bpo::value<unsigned>(), "run s specified number of iterations")
+ ("time", bpo::value<float>()->default_value(5.0), "run for a specified amount of time, in seconds")
+ ("random-seed", boost::program_options::value<unsigned>(), "Random number generator seed");
+ ;
+ bpo::variables_map vm;
+ bpo::store(bpo::parse_command_line(ac, av, opts), vm);
+ bpo::notify(vm);
+ test_cpp17_aligned_allocator();
+ auto seed = vm.count("random-seed") ? vm["random-seed"].as<unsigned>() : std::random_device{}();
+ std::default_random_engine random_engine(seed);
+ std::exponential_distribution<> distr(0.2);
+ std::uniform_int_distribution<> type(0, 1);
+ std::uniform_int_distribution<char> poison(-128, 127);
+ std::uniform_real_distribution<> which(0, 1);
+ std::vector<allocation> allocations;
+ auto iteration = [&] {
+ auto typ = type(random_engine);
+ switch (typ) {
+ case 0: {
+ size_t n = std::min<double>(std::exp(distr(random_engine)), 1 << 25);
+ try {
+ allocations.emplace_back(n, poison(random_engine));
+ } catch (std::bad_alloc&) {
+
+ }
+ break;
+ }
+ case 1: {
+ if (allocations.empty()) {
+ break;
+ }
+ size_t i = which(random_engine) * allocations.size();
+ allocations[i] = std::move(allocations.back());
+ allocations.pop_back();
+ break;
+ }
+ }
+ };
+ if (vm.count("help")) {
+ std::cout << opts << "\n";
+ return 1;
+ }
+ std::cout << "random-seed=" << seed << "\n";
+ if (vm.count("iterations")) {
+ auto iterations = vm["iterations"].as<unsigned>();
+ for (unsigned i = 0; i < iterations; ++i) {
+ iteration();
+ }
+ } else {
+ auto time = vm["time"].as<float>();
+ using clock = steady_clock_type;
+ auto end = clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1) * time);
+ while (clock::now() < end) {
+ for (unsigned i = 0; i < 1000; ++i) {
+ iteration();
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/seastar/tests/unit/cert.cfg.in b/src/seastar/tests/unit/cert.cfg.in
new file mode 100644
index 000000000..1591a1d39
--- /dev/null
+++ b/src/seastar/tests/unit/cert.cfg.in
@@ -0,0 +1,23 @@
+[ req ]
+default_bits = @CERT_WIDTH@
+default_keyfile = @CERT_PRIVKEY@
+default_md = sha256
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+prompt = no
+[ req_distinguished_name ]
+C = @CERT_COUNTRY@
+ST = @CERT_STATE@
+L = @CERT_LOCALITY@
+O = @CERT_ORG@
+OU = @CERT_UNIT@
+CN= @CERT_COMMON@
+emailAddress = @CERT_EMAIL@
+[v3_ca]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer:always
+basicConstraints = CA:true
+[v3_req]
+# Extensions to add to a certificate request
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
diff --git a/src/seastar/tests/unit/checked_ptr_test.cc b/src/seastar/tests/unit/checked_ptr_test.cc
new file mode 100644
index 000000000..56133f417
--- /dev/null
+++ b/src/seastar/tests/unit/checked_ptr_test.cc
@@ -0,0 +1,125 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/checked_ptr.hh>
+#include <seastar/core/weak_ptr.hh>
+
+using namespace seastar;
+
+static_assert(std::is_nothrow_default_constructible_v<checked_ptr<int*>>);
+static_assert(std::is_nothrow_move_constructible_v<checked_ptr<int*>>);
+static_assert(std::is_nothrow_copy_constructible_v<checked_ptr<int*>>);
+
+static_assert(std::is_nothrow_default_constructible_v<checked_ptr<weak_ptr<int>>>);
+static_assert(std::is_nothrow_move_constructible_v<checked_ptr<weak_ptr<int>>>);
+
+template <typename T>
+class may_throw_on_null_ptr : public seastar::weak_ptr<T> {
+public:
+ may_throw_on_null_ptr(std::nullptr_t) {}
+};
+
+static_assert(!std::is_nothrow_default_constructible_v<may_throw_on_null_ptr<int>>);
+static_assert(!std::is_nothrow_default_constructible_v<checked_ptr<may_throw_on_null_ptr<int>>>);
+static_assert(std::is_nothrow_move_constructible_v<checked_ptr<may_throw_on_null_ptr<int>>>);
+static_assert(!std::is_nothrow_copy_constructible_v<checked_ptr<may_throw_on_null_ptr<int>>>);
+
+struct my_st : public weakly_referencable<my_st> {
+ my_st(int a_) : a(a_) {}
+ int a;
+};
+
+void const_ref_check_naked(const seastar::checked_ptr<my_st*>& cp) {
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+}
+
+void const_ref_check_smart(const seastar::checked_ptr<::weak_ptr<my_st>>& cp) {
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_empty_when_default_initialized) {
+ seastar::checked_ptr<int*> cp;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_empty_when_nullptr_initialized_nakes_ptr) {
+ seastar::checked_ptr<int*> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_empty_when_nullptr_initialized_smart_ptr) {
+ seastar::checked_ptr<::weak_ptr<my_st>> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_initialized_after_assignment_naked_ptr) {
+ seastar::checked_ptr<my_st*> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+ my_st i(3);
+ my_st k(3);
+ cp = &i;
+ seastar::checked_ptr<my_st*> cp1(&i);
+ seastar::checked_ptr<my_st*> cp2(&k);
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE(cp == cp1);
+ BOOST_REQUIRE(cp != cp2);
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+
+ const_ref_check_naked(cp);
+
+ cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+}
+
+BOOST_AUTO_TEST_CASE(test_checked_ptr_is_initialized_after_assignment_smart_ptr) {
+ seastar::checked_ptr<::weak_ptr<my_st>> cp = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+ std::unique_ptr<my_st> i = std::make_unique<my_st>(3);
+ cp = i->weak_from_this();
+ seastar::checked_ptr<::weak_ptr<my_st>> cp1(i->weak_from_this());
+ seastar::checked_ptr<::weak_ptr<my_st>> cp2;
+ BOOST_REQUIRE(bool(cp));
+ BOOST_REQUIRE(cp == cp1);
+ BOOST_REQUIRE(cp != cp2);
+ BOOST_REQUIRE((*cp).a == 3);
+ BOOST_REQUIRE(cp->a == 3);
+ BOOST_REQUIRE(cp.get()->a == 3);
+
+ const_ref_check_smart(cp);
+
+ i = nullptr;
+ BOOST_REQUIRE(!bool(cp));
+ BOOST_REQUIRE(!bool(cp1));
+ BOOST_REQUIRE(!bool(cp2));
+}
+
diff --git a/src/seastar/tests/unit/chunked_fifo_test.cc b/src/seastar/tests/unit/chunked_fifo_test.cc
new file mode 100644
index 000000000..3d57576ea
--- /dev/null
+++ b/src/seastar/tests/unit/chunked_fifo_test.cc
@@ -0,0 +1,358 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2016 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/chunked_fifo.hh>
+#include <stdlib.h>
+#include <chrono>
+#include <deque>
+#include <seastar/core/circular_buffer.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_small) {
+ // Check all the methods of chunked_fifo but with a trivial type (int) and
+ // only a few elements - and in particular a single chunk is enough.
+ chunked_fifo<int> fifo;
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ fifo.push_back(3);
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.push_back(17);
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 17);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ // The previously allocated chunk should have been freed, and now
+ // a new one will need to be allocated:
+ fifo.push_back(57);
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 57);
+ // check miscelleneous methods (at least they shouldn't crash)
+ fifo.clear();
+ fifo.shrink_to_fit();
+ fifo.reserve(1);
+ fifo.reserve(100);
+ fifo.reserve(1280);
+ fifo.shrink_to_fit();
+ fifo.reserve(1280);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_fullchunk) {
+ // Grow a chunked_fifo to exactly fill a chunk, and see what happens when
+ // we cross that chunk.
+ constexpr size_t N = 128;
+ chunked_fifo<int, N> fifo;
+ for (int i = 0; i < static_cast<int>(N); i++) {
+ fifo.push_back(i);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), N);
+ fifo.push_back(N);
+ BOOST_REQUIRE_EQUAL(fifo.size(), N+1);
+ for (int i = 0 ; i < static_cast<int>(N+1); i++) {
+ BOOST_REQUIRE_EQUAL(fifo.front(), i);
+ BOOST_REQUIRE_EQUAL(fifo.size(), N+1-i);
+ fifo.pop_front();
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_big) {
+ // Grow a chunked_fifo to many elements, and see things are working as
+ // expected
+ chunked_fifo<int> fifo;
+ constexpr size_t N = 100'000;
+ for (int i=0; i < static_cast<int>(N); i++) {
+ fifo.push_back(i);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), N);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ for (int i = 0 ; i < static_cast<int>(N); i++) {
+ BOOST_REQUIRE_EQUAL(fifo.front(), i);
+ BOOST_REQUIRE_EQUAL(fifo.size(), N-i);
+ fifo.pop_front();
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_constructor) {
+ // Check that chunked_fifo appropriately calls the type's constructor
+ // and destructor, and doesn't need anything else.
+ struct typ {
+ int val;
+ unsigned* constructed;
+ unsigned* destructed;
+ typ(int val, unsigned* constructed, unsigned* destructed)
+ : val(val), constructed(constructed), destructed(destructed) {
+ ++*constructed;
+ }
+ ~typ() { ++*destructed; }
+ };
+ chunked_fifo<typ> fifo;
+ unsigned constructed = 0, destructed = 0;
+ constexpr unsigned N = 1000;
+ for (unsigned i = 0; i < N; i++) {
+ fifo.emplace_back(i, &constructed, &destructed);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), N);
+ BOOST_REQUIRE_EQUAL(constructed, N);
+ BOOST_REQUIRE_EQUAL(destructed, 0u);
+ for (unsigned i = 0 ; i < N; i++) {
+ BOOST_REQUIRE_EQUAL(fifo.front().val, static_cast<int>(i));
+ BOOST_REQUIRE_EQUAL(fifo.size(), N-i);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(destructed, i+1);
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ // Check that destructing a fifo also destructs the objects it still
+ // contains
+ constructed = destructed = 0;
+ {
+ chunked_fifo<typ> fifo;
+ for (unsigned i = 0; i < N; i++) {
+ fifo.emplace_back(i, &constructed, &destructed);
+ BOOST_REQUIRE_EQUAL(fifo.front().val, 0);
+ BOOST_REQUIRE_EQUAL(fifo.size(), i+1);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ BOOST_REQUIRE_EQUAL(constructed, i+1);
+ BOOST_REQUIRE_EQUAL(destructed, 0u);
+ }
+ }
+ BOOST_REQUIRE_EQUAL(constructed, N);
+ BOOST_REQUIRE_EQUAL(destructed, N);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_construct_fail) {
+ // Check that if we fail to construct the item pushed, the queue remains
+ // empty.
+ class my_exception {};
+ struct typ {
+ typ() {
+ throw my_exception();
+ }
+ };
+ chunked_fifo<typ> fifo;
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ try {
+ fifo.emplace_back();
+ } catch(my_exception) {
+ // expected, ignore
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_construct_fail2) {
+ // A slightly more elaborate test, with a chunk size of 2
+ // items, and the third addition failing, so the question is
+ // not whether empty() is wrong immediately, but whether after
+ // we pop the two items, it will become true or we'll be left
+ // with an empty chunk.
+ class my_exception {};
+ struct typ {
+ typ(bool fail) {
+ if (fail) {
+ throw my_exception();
+ }
+ }
+ };
+ chunked_fifo<typ, 2> fifo;
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+ fifo.emplace_back(false);
+ fifo.emplace_back(false);
+ try {
+ fifo.emplace_back(true);
+ } catch(my_exception) {
+ // expected, ignore
+ }
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), false);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE_EQUAL(fifo.empty(), true);
+}
+
+// Enable the following to run some benchmarks on different queue options
+#if 0
+// Unfortunately, C++ lacks the trivial feature of converting a type's name,
+// in compile time, to a string (akin to the C preprocessor's "#" feature).
+// Here is a neat trick to replace it - use typeinfo<T>::name() or
+// type_name<T>() to get a constant string name of the type.
+#include <cxxabi.h>
+template <typename T>
+class typeinfo {
+private:
+ static const char *_name;
+public:
+ static const char *name() {
+ int status;
+ if (!_name)
+ _name = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
+ return _name;
+ }
+};
+template<typename T> const char *typeinfo<T>::_name = nullptr;
+template<typename T> const char *type_name() {
+ return typeinfo<T>::name();
+}
+
+
+template<typename FIFO_TYPE> void
+benchmark_random_push_pop() {
+ // A test involving a random sequence of pushes and pops. Because the
+ // random walk is bounded the 0 end (the queue cannot be popped after
+ // being empty), the queue's expected length at the end of the test is
+ // not zero.
+ // The test uses the same random sequence each time so can be used for
+ // benchmarking different queue implementations on the same sequence.
+ constexpr int N = 1'000'000'000;
+ FIFO_TYPE fifo;
+ unsigned int seed = 0;
+ int entropy = 0;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N; i++) {
+ if (!entropy) {
+ entropy = rand_r(&seed);
+ }
+ if (entropy & 1) {
+ fifo.push_back(i);
+ } else {
+ if (!fifo.empty()) {
+ fifo.pop_front();
+ }
+ }
+ entropy >>= 1;
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " random push-and-pop " << fifo.size() << " " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_push_pop() {
+ // A benchmark involving repeated push and then pop to a queue, which
+ // will have 0 or 1 items at all times.
+ constexpr int N = 1'000'000'000;
+ FIFO_TYPE fifo;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N; i++) {
+ fifo.push_back(1);
+ fifo.pop_front();
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " push-and-pop " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_push_pop_k() {
+ // A benchmark involving repeated pushes of a few items and then popping
+ // to a queue, which will have just one chunk (or 0) at all times.
+ constexpr int N = 1'000'000'000;
+ constexpr int K = 100;
+ FIFO_TYPE fifo;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N/K; i++) {
+ for(int j = 0; j < K; j++) {
+ fifo.push_back(j);
+ }
+ for(int j = 0; j < K; j++) {
+ fifo.pop_front();
+ }
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " push-and-pop-" << K << " " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_pushes_pops() {
+ // A benchmark of pushing a lot of items, and then popping all of them
+ constexpr int N = 100'000'000;
+ FIFO_TYPE fifo;
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N; i++) {
+ fifo.push_back(1);
+ }
+ for (int i = 0; i < N; i++) {
+ fifo.pop_front();
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ std::cerr << type_name<FIFO_TYPE>() << ", " << N << " push-all-then-pop-all " << ms << "ms.\n";
+}
+
+template<typename FIFO_TYPE> void
+benchmark_all() {
+ std::cerr << "\n --- " << type_name<FIFO_TYPE>() << ": \n";
+ benchmark_random_push_pop<FIFO_TYPE>();
+ benchmark_push_pop<FIFO_TYPE>();
+ benchmark_push_pop_k<FIFO_TYPE>();
+ benchmark_pushes_pops<FIFO_TYPE>();
+}
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_benchmark) {
+ benchmark_all<chunked_fifo<int>>();
+ benchmark_all<circular_buffer<int>>();
+ benchmark_all<std::deque<int>>();
+ benchmark_all<std::list<int>>();
+}
+#endif
+
+BOOST_AUTO_TEST_CASE(chunked_fifo_iterator) {
+ constexpr auto items_per_chunk = 8;
+ auto fifo = chunked_fifo<int, items_per_chunk>{};
+ auto reference = std::deque<int>{};
+
+ BOOST_REQUIRE(fifo.begin() == fifo.end());
+
+ for (int i = 0; i < items_per_chunk * 4; ++i) {
+ fifo.push_back(i);
+ reference.push_back(i);
+ BOOST_REQUIRE(std::equal(fifo.begin(), fifo.end(), reference.begin(), reference.end()));
+ }
+
+ for (int i = 0; i < items_per_chunk * 2; ++i) {
+ fifo.pop_front();
+ reference.pop_front();
+ BOOST_REQUIRE(std::equal(fifo.begin(), fifo.end(), reference.begin(), reference.end()));
+ }
+}
diff --git a/src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc b/src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc
new file mode 100644
index 000000000..d5fe6b814
--- /dev/null
+++ b/src/seastar/tests/unit/circular_buffer_fixed_capacity_test.cc
@@ -0,0 +1,173 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <deque>
+#include <random>
+#include <seastar/core/circular_buffer_fixed_capacity.hh>
+
+#include <boost/range/algorithm/sort.hpp>
+#include <boost/range/algorithm/equal.hpp>
+#include <boost/range/algorithm/reverse.hpp>
+
+using namespace seastar;
+
+using cb16_t = circular_buffer_fixed_capacity<int, 16>;
+
+struct int_with_stats {
+ int val;
+ unsigned *num_deleted;
+ unsigned *num_moved;
+ operator int() const { return val; }
+ int_with_stats(int val, unsigned* num_deleted, unsigned* num_moved)
+ : val(val), num_deleted(num_deleted), num_moved(num_moved) {}
+
+ ~int_with_stats() { ++(*num_deleted); }
+ int_with_stats(const int_with_stats&) = delete;
+ int_with_stats(int_with_stats&& o) noexcept : val(o.val), num_deleted(o.num_deleted), num_moved(o.num_moved) {
+ ++(*num_moved);
+ }
+ int_with_stats& operator=(int_with_stats&& o) noexcept {
+ this->~int_with_stats();
+ new (this) int_with_stats(std::move(o));
+ return *this;
+ }
+};
+
+BOOST_AUTO_TEST_CASE(test_edge_cases) {
+ unsigned num_deleted = 0;
+ unsigned num_moved = 0;
+ auto get_val = [&num_deleted, &num_moved] (int val) {
+ return int_with_stats{val, &num_deleted, &num_moved};
+ };
+
+ {
+ circular_buffer_fixed_capacity<int_with_stats, 16> cb;
+ BOOST_REQUIRE(cb.begin() == cb.end());
+ cb.push_front(get_val(3)); // underflows indexes
+ BOOST_REQUIRE_EQUAL(cb[0], 3);
+ BOOST_REQUIRE(cb.begin() < cb.end());
+ cb.push_back(get_val(4));
+ BOOST_REQUIRE_EQUAL(cb.size(), 2u);
+ BOOST_REQUIRE_EQUAL(cb[0], 3);
+ BOOST_REQUIRE_EQUAL(cb[1], 4);
+ cb.pop_back();
+ BOOST_REQUIRE_EQUAL(cb.back(), 3);
+ cb.push_front(get_val(1));
+ cb.pop_back();
+ BOOST_REQUIRE_EQUAL(cb.back(), 1);
+
+ BOOST_REQUIRE_EQUAL(num_deleted, 5);
+ BOOST_REQUIRE_EQUAL(num_moved, 3);
+
+ cb.push_front(get_val(0));
+ cb.push_back(get_val(2));
+ BOOST_REQUIRE_EQUAL(cb.size(), 3);
+ BOOST_REQUIRE_EQUAL(cb[0], 0);
+ BOOST_REQUIRE_EQUAL(cb[1], 1);
+ BOOST_REQUIRE_EQUAL(cb[2], 2);
+ BOOST_REQUIRE_EQUAL(num_deleted, 7);
+ BOOST_REQUIRE_EQUAL(num_moved, 5);
+
+ circular_buffer_fixed_capacity<int_with_stats, 16> cb2 = std::move(cb);
+ BOOST_REQUIRE_EQUAL(cb2.size(), 3);
+ BOOST_REQUIRE_EQUAL(cb2[0], 0);
+ BOOST_REQUIRE_EQUAL(cb2[1], 1);
+ BOOST_REQUIRE_EQUAL(cb2[2], 2);
+ BOOST_REQUIRE_EQUAL(num_deleted, 7);
+ BOOST_REQUIRE_EQUAL(num_moved, 8);
+ }
+ BOOST_REQUIRE_EQUAL(num_deleted, 13);
+ BOOST_REQUIRE_EQUAL(num_moved, 8);
+}
+
+using deque = std::deque<int>;
+
+BOOST_AUTO_TEST_CASE(test_random_walk) {
+ auto rand = std::default_random_engine();
+ auto op_gen = std::uniform_int_distribution<unsigned>(0, 6);
+ deque d;
+ cb16_t c;
+ for (auto i = 0; i != 1000000; ++i) {
+ auto op = op_gen(rand);
+ switch (op) {
+ case 0:
+ if (d.size() < 16) {
+ auto n = rand();
+ c.push_back(n);
+ d.push_back(n);
+ }
+ break;
+ case 1:
+ if (d.size() < 16) {
+ auto n = rand();
+ c.push_front(n);
+ d.push_front(n);
+ }
+ break;
+ case 2:
+ if (!d.empty()) {
+ auto n = d.back();
+ auto m = c.back();
+ BOOST_REQUIRE_EQUAL(n, m);
+ c.pop_back();
+ d.pop_back();
+ }
+ break;
+ case 3:
+ if (!d.empty()) {
+ auto n = d.front();
+ auto m = c.front();
+ BOOST_REQUIRE_EQUAL(n, m);
+ c.pop_front();
+ d.pop_front();
+ }
+ break;
+ case 4:
+ boost::sort(c);
+ boost::sort(d);
+ break;
+ case 5:
+ if (!d.empty()) {
+ auto u = std::uniform_int_distribution<size_t>(0, d.size() - 1);
+ auto idx = u(rand);
+ auto m = c[idx];
+ auto n = c[idx];
+ BOOST_REQUIRE_EQUAL(m, n);
+ }
+ break;
+ case 6:
+ c.clear();
+ d.clear();
+ break;
+ case 7:
+ boost::reverse(c);
+ boost::reverse(d);
+ default:
+ abort();
+ }
+ BOOST_REQUIRE_EQUAL(c.size(), d.size());
+ BOOST_REQUIRE(boost::equal(c, d));
+ }
+}
diff --git a/src/seastar/tests/unit/circular_buffer_test.cc b/src/seastar/tests/unit/circular_buffer_test.cc
new file mode 100644
index 000000000..9de0af368
--- /dev/null
+++ b/src/seastar/tests/unit/circular_buffer_test.cc
@@ -0,0 +1,109 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <stdlib.h>
+#include <chrono>
+#include <deque>
+#include <seastar/core/circular_buffer.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(test_erasing) {
+ circular_buffer<int> buf;
+
+ buf.push_back(3);
+ buf.erase(buf.begin(), buf.end());
+
+ BOOST_REQUIRE(buf.size() == 0);
+ BOOST_REQUIRE(buf.empty());
+
+ buf.push_back(1);
+ buf.push_back(2);
+ buf.push_back(3);
+ buf.push_back(4);
+ buf.push_back(5);
+
+ buf.erase(std::remove_if(buf.begin(), buf.end(), [] (int v) { return (v & 1) == 0; }), buf.end());
+
+ BOOST_REQUIRE(buf.size() == 3);
+ BOOST_REQUIRE(!buf.empty());
+ {
+ auto i = buf.begin();
+ BOOST_REQUIRE_EQUAL(*i++, 1);
+ BOOST_REQUIRE_EQUAL(*i++, 3);
+ BOOST_REQUIRE_EQUAL(*i++, 5);
+ BOOST_REQUIRE(i == buf.end());
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_erasing_at_beginning_or_end_does_not_invalidate_iterators) {
+ // This guarantee comes from std::deque, which circular_buffer is supposed to mimic.
+
+ circular_buffer<int> buf;
+
+ buf.push_back(1);
+ buf.push_back(2);
+ buf.push_back(3);
+ buf.push_back(4);
+ buf.push_back(5);
+
+ int* ptr_to_3 = &buf[2];
+ auto iterator_to_3 = buf.begin() + 2;
+ assert(*ptr_to_3 == 3);
+ assert(*iterator_to_3 == 3);
+
+ buf.erase(buf.begin(), buf.begin() + 2);
+
+ BOOST_REQUIRE(*ptr_to_3 == 3);
+ BOOST_REQUIRE(*iterator_to_3 == 3);
+
+ buf.erase(buf.begin() + 1, buf.end());
+
+ BOOST_REQUIRE(*ptr_to_3 == 3);
+ BOOST_REQUIRE(*iterator_to_3 == 3);
+
+ BOOST_REQUIRE(buf.size() == 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_erasing_in_the_middle) {
+ circular_buffer<int> buf;
+
+ for (int i = 0; i < 10; ++i) {
+ buf.push_back(i);
+ }
+
+ auto i = buf.erase(buf.begin() + 3, buf.begin() + 6);
+ BOOST_REQUIRE_EQUAL(*i, 6);
+
+ i = buf.begin();
+ BOOST_REQUIRE_EQUAL(*i++, 0);
+ BOOST_REQUIRE_EQUAL(*i++, 1);
+ BOOST_REQUIRE_EQUAL(*i++, 2);
+ BOOST_REQUIRE_EQUAL(*i++, 6);
+ BOOST_REQUIRE_EQUAL(*i++, 7);
+ BOOST_REQUIRE_EQUAL(*i++, 8);
+ BOOST_REQUIRE_EQUAL(*i++, 9);
+ BOOST_REQUIRE(i == buf.end());
+}
diff --git a/src/seastar/tests/unit/connect_test.cc b/src/seastar/tests/unit/connect_test.cc
new file mode 100644
index 000000000..e4b3b4728
--- /dev/null
+++ b/src/seastar/tests/unit/connect_test.cc
@@ -0,0 +1,74 @@
+#include <seastar/core/reactor.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/net/ip.hh>
+
+using namespace seastar;
+using namespace net;
+
+SEASTAR_TEST_CASE(test_connection_attempt_is_shutdown) {
+ ipv4_addr server_addr("172.16.0.1");
+ auto unconn = make_socket();
+ auto f = unconn
+ .connect(make_ipv4_address(server_addr))
+ .then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+ unconn.shutdown();
+ return f.finally([unconn = std::move(unconn)] {});
+}
+
+SEASTAR_TEST_CASE(test_unconnected_socket_shutsdown_established_connection) {
+ // Use a random port to reduce chance of conflict.
+ // TODO: retry a few times on failure.
+ std::default_random_engine& rnd = testing::local_random_engine;
+ auto distr = std::uniform_int_distribution<uint16_t>(12000, 65000);
+ auto sa = make_ipv4_address({"127.0.0.1", distr(rnd)});
+ return do_with(engine().net().listen(sa, listen_options()), [sa] (auto& listener) {
+ auto f = listener.accept();
+ auto unconn = make_socket();
+ auto connf = unconn.connect(sa);
+ return connf.then([unconn = std::move(unconn)] (auto&& conn) mutable {
+ unconn.shutdown();
+ return do_with(std::move(conn), [] (auto& conn) {
+ return do_with(conn.output(1), [] (auto& out) {
+ return out.write("ping").then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+ });
+ });
+ }).finally([f = std::move(f)] () mutable {
+ return std::move(f);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_accept_after_abort) {
+ std::default_random_engine& rnd = testing::local_random_engine;
+ auto distr = std::uniform_int_distribution<uint16_t>(12000, 65000);
+ auto sa = make_ipv4_address({"127.0.0.1", distr(rnd)});
+ return do_with(seastar::server_socket(engine().net().listen(sa, listen_options())), [] (auto& listener) {
+ using ftype = future<accept_result>;
+ promise<ftype> p;
+ future<ftype> done = p.get_future();
+ auto f = listener.accept().then_wrapped([&listener, p = std::move(p)] (auto f) mutable {
+ f.ignore_ready_future();
+ p.set_value(listener.accept());
+ });
+ listener.abort_accept();
+ return done.then([] (ftype f) {
+ return f.then_wrapped([] (ftype f) {
+ BOOST_REQUIRE(f.failed());
+ if (f.available()) {
+ f.ignore_ready_future();
+ }
+ });
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/coroutines_test.cc b/src/seastar/tests/unit/coroutines_test.cc
new file mode 100644
index 000000000..b4b3bb2c2
--- /dev/null
+++ b/src/seastar/tests/unit/coroutines_test.cc
@@ -0,0 +1,153 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 ScyllaDB Ltd.
+ */
+
+#include <seastar/core/future-util.hh>
+#include <seastar/testing/test_case.hh>
+
+using namespace seastar;
+
+#ifndef SEASTAR_COROUTINES_ENABLED
+
+SEASTAR_TEST_CASE(test_coroutines_not_compiled_in) {
+ return make_ready_future<>();
+}
+
+#else
+
+#include <seastar/core/coroutine.hh>
+
+namespace {
+
+future<int> old_fashioned_continuations() {
+ return later().then([] {
+ return 42;
+ });
+}
+
+future<int> simple_coroutine() {
+ co_await later();
+ co_return 53;
+}
+
+future<int> ready_coroutine() {
+ co_return 64;
+}
+
+future<std::tuple<int, double>> tuple_coroutine() {
+ co_return std::tuple(1, 2.);
+}
+
+future<int> failing_coroutine() {
+ co_await later();
+ throw 42;
+}
+
+}
+
+SEASTAR_TEST_CASE(test_simple_coroutines) {
+ BOOST_REQUIRE_EQUAL(co_await old_fashioned_continuations(), 42);
+ BOOST_REQUIRE_EQUAL(co_await simple_coroutine(), 53);
+ BOOST_REQUIRE_EQUAL(ready_coroutine().get0(), 64);
+ BOOST_REQUIRE(co_await tuple_coroutine() == std::tuple(1, 2.));
+ BOOST_REQUIRE_EXCEPTION((void)co_await failing_coroutine(), int, [] (auto v) { return v == 42; });
+}
+
+
+future<> forwarding_return_coroutine_1(bool& x) {
+ co_return
+// Clang complains if both return_value and return_void are defined
+#if defined(__clang__)
+ co_await
+#endif
+ later().then([&x] {
+ x = true;
+ });
+}
+
+future<int> forwarding_return_coroutine_2() {
+ co_return later().then([] {
+ return 3;
+ });
+}
+
+SEASTAR_TEST_CASE(test_forwarding_return) {
+ bool x = false;
+ co_await forwarding_return_coroutine_1(x);
+ BOOST_REQUIRE(x);
+ auto y = co_await forwarding_return_coroutine_2();
+ BOOST_REQUIRE_EQUAL(y, 3);
+}
+
+SEASTAR_TEST_CASE(test_abandond_coroutine) {
+ std::optional<future<int>> f;
+ {
+ auto p1 = promise<>();
+ auto p2 = promise<>();
+ auto p3 = promise<>();
+ f = p1.get_future().then([&] () -> future<int> {
+ p2.set_value();
+ BOOST_CHECK_THROW(co_await p3.get_future(), broken_promise);
+ co_return 1;
+ });
+ p1.set_value();
+ co_await p2.get_future();
+ }
+ BOOST_CHECK_EQUAL(co_await std::move(*f), 1);
+}
+
+SEASTAR_TEST_CASE(test_scheduling_group) {
+ auto other_sg = co_await create_scheduling_group("the other group", 10.f);
+
+ auto p1 = promise<>();
+ auto p2 = promise<>();
+
+ auto p1b = promise<>();
+ auto p2b = promise<>();
+ auto f1 = p1b.get_future();
+ auto f2 = p2b.get_future();
+
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+ auto f_ret = with_scheduling_group(other_sg,
+ [other_sg_cap = other_sg] (future<> f1, future<> f2, promise<> p1, promise<> p2) -> future<int> {
+ // Make a copy in the coroutine before the lambda is destroyed.
+ auto other_sg = other_sg_cap;
+ BOOST_REQUIRE(current_scheduling_group() == other_sg);
+ BOOST_REQUIRE(other_sg == other_sg);
+ p1.set_value();
+ co_await std::move(f1);
+ BOOST_REQUIRE(current_scheduling_group() == other_sg);
+ p2.set_value();
+ co_await std::move(f2);
+ BOOST_REQUIRE(current_scheduling_group() == other_sg);
+ co_return 42;
+ }, p1.get_future(), p2.get_future(), std::move(p1b), std::move(p2b));
+
+ co_await std::move(f1);
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+ p1.set_value();
+ co_await std::move(f2);
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+ p2.set_value();
+ BOOST_REQUIRE_EQUAL(co_await std::move(f_ret), 42);
+ BOOST_REQUIRE(current_scheduling_group() == default_scheduling_group());
+}
+
+#endif
diff --git a/src/seastar/tests/unit/defer_test.cc b/src/seastar/tests/unit/defer_test.cc
new file mode 100644
index 000000000..b79245694
--- /dev/null
+++ b/src/seastar/tests/unit/defer_test.cc
@@ -0,0 +1,76 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/util/defer.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(test_defer_does_not_run_when_canceled) {
+ bool ran = false;
+ {
+ auto d = defer([&] {
+ ran = true;
+ });
+ d.cancel();
+ }
+ BOOST_REQUIRE(!ran);
+}
+
+BOOST_AUTO_TEST_CASE(test_defer_runs) {
+ bool ran = false;
+ {
+ auto d = defer([&] {
+ ran = true;
+ });
+ }
+ BOOST_REQUIRE(ran);
+}
+
+BOOST_AUTO_TEST_CASE(test_defer_runs_once_when_moved) {
+ int ran = 0;
+ {
+ auto d = defer([&] {
+ ++ran;
+ });
+ {
+ auto d2 = std::move(d);
+ }
+ BOOST_REQUIRE_EQUAL(1, ran);
+ }
+ BOOST_REQUIRE_EQUAL(1, ran);
+}
+
+BOOST_AUTO_TEST_CASE(test_defer_does_not_run_when_moved_after_cancelled) {
+ int ran = 0;
+ {
+ auto d = defer([&] {
+ ++ran;
+ });
+ d.cancel();
+ {
+ auto d2 = std::move(d);
+ }
+ }
+ BOOST_REQUIRE_EQUAL(0, ran);
+}
diff --git a/src/seastar/tests/unit/deleter_test.cc b/src/seastar/tests/unit/deleter_test.cc
new file mode 100644
index 000000000..a9a0c5795
--- /dev/null
+++ b/src/seastar/tests/unit/deleter_test.cc
@@ -0,0 +1,79 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Lightbits Labs Ltd. - All Rights Reserved
+*/
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/deleter.hh>
+
+using namespace seastar;
+
+struct TestObject {
+ TestObject() : has_ref(true){}
+ TestObject(TestObject&& other) {
+ has_ref = true;
+ other.has_ref = false;
+ }
+ ~TestObject() {
+ if (has_ref) {
+ ++deletions_called;
+ }
+ }
+ static int deletions_called;
+ int has_ref;
+};
+int TestObject::deletions_called = 0;
+
+BOOST_AUTO_TEST_CASE(test_deleter_append_does_not_free_shared_object) {
+ {
+ deleter tested;
+ {
+ auto obj1 = TestObject();
+ deleter del1 = make_object_deleter(std::move(obj1));
+ auto obj2 = TestObject();
+ deleter del2 = make_object_deleter(std::move(obj2));
+ del1.append(std::move(del2));
+ tested = del1.share();
+ auto obj3 = TestObject();
+ deleter del3 = make_object_deleter(std::move(obj3));
+ del1.append(std::move(del3));
+ }
+ // since deleter tested still holds references to first two objects, last objec should be deleted
+ BOOST_REQUIRE(TestObject::deletions_called == 1);
+ }
+ BOOST_REQUIRE(TestObject::deletions_called == 3);
+}
+
+BOOST_AUTO_TEST_CASE(test_deleter_append_same_shared_object_twice) {
+ TestObject::deletions_called = 0;
+ {
+ deleter tested;
+ {
+ deleter del1 = make_object_deleter(TestObject());
+ auto del2 = del1.share();
+
+ tested.append(std::move(del1));
+ tested.append(std::move(del2));
+ }
+ BOOST_REQUIRE(TestObject::deletions_called == 0);
+ }
+ BOOST_REQUIRE(TestObject::deletions_called == 1);
+}
diff --git a/src/seastar/tests/unit/directory_test.cc b/src/seastar/tests/unit/directory_test.cc
new file mode 100644
index 000000000..3f7f540d2
--- /dev/null
+++ b/src/seastar/tests/unit/directory_test.cc
@@ -0,0 +1,86 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/shared_ptr.hh>
+
+using namespace seastar;
+
+const char* de_type_desc(directory_entry_type t)
+{
+ switch (t) {
+ case directory_entry_type::unknown:
+ return "unknown";
+ case directory_entry_type::block_device:
+ return "block_device";
+ case directory_entry_type::char_device:
+ return "char_device";
+ case directory_entry_type::directory:
+ return "directory";
+ case directory_entry_type::fifo:
+ return "fifo";
+ case directory_entry_type::link:
+ return "link";
+ case directory_entry_type::regular:
+ return "regular";
+ case directory_entry_type::socket:
+ return "socket";
+ }
+ assert(0 && "should not get here");
+ return nullptr;
+}
+
+int main(int ac, char** av) {
+ class lister {
+ file _f;
+ subscription<directory_entry> _listing;
+ public:
+ lister(file f)
+ : _f(std::move(f))
+ , _listing(_f.list_directory([this] (directory_entry de) { return report(de); })) {
+ }
+ future<> done() { return _listing.done(); }
+ private:
+ future<> report(directory_entry de) {
+ return file_stat(de.name, follow_symlink::no).then([de = std::move(de)] (stat_data sd) {
+ if (de.type) {
+ assert(*de.type == sd.type);
+ } else {
+ assert(sd.type == directory_entry_type::unknown);
+ }
+ fmt::print("{} (type={})\n", de.name, de_type_desc(sd.type));
+ return make_ready_future<>();
+ });
+ }
+ };
+ return app_template().run(ac, av, [] {
+ return engine().open_directory(".").then([] (file f) {
+ return do_with(lister(std::move(f)), [] (lister& l) {
+ return l.done().then([] {
+ return 0;
+ });
+ });
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/distributed_test.cc b/src/seastar/tests/unit/distributed_test.cc
new file mode 100644
index 000000000..497bf3c7d
--- /dev/null
+++ b/src/seastar/tests/unit/distributed_test.cc
@@ -0,0 +1,318 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/distributed.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/print.hh>
+#include <seastar/util/defer.hh>
+#include <mutex>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+struct async_service : public seastar::async_sharded_service<async_service> {
+ thread_local static bool deleted;
+ ~async_service() {
+ deleted = true;
+ }
+ void run() {
+ auto ref = shared_from_this();
+ // Wait a while and check.
+ (void)sleep(std::chrono::milliseconds(100 + 100 * this_shard_id())).then([this, ref] {
+ check();
+ });
+ }
+ virtual void check() {
+ assert(!deleted);
+ }
+ future<> stop() { return make_ready_future<>(); }
+};
+
+thread_local bool async_service::deleted = false;
+
+struct X {
+ sstring echo(sstring arg) {
+ return arg;
+ }
+ int cpu_id_squared() const {
+ auto id = this_shard_id();
+ return id * id;
+ }
+ future<> stop() { return make_ready_future<>(); }
+};
+
+template <typename T, typename Func>
+future<> do_with_distributed(Func&& func) {
+ auto x = make_shared<distributed<T>>();
+ return func(*x).finally([x] {
+ return x->stop();
+ }).finally([x]{});
+}
+
+SEASTAR_TEST_CASE(test_that_each_core_gets_the_arguments) {
+ return do_with_distributed<X>([] (auto& x) {
+ return x.start().then([&x] {
+ return x.map_reduce([] (sstring msg){
+ if (msg != "hello") {
+ throw std::runtime_error("wrong message");
+ }
+ }, &X::echo, sstring("hello"));
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_functor_version) {
+ return do_with_distributed<X>([] (auto& x) {
+ return x.start().then([&x] {
+ return x.map_reduce([] (sstring msg){
+ if (msg != "hello") {
+ throw std::runtime_error("wrong message");
+ }
+ }, [] (X& x) { return x.echo("hello"); });
+ });
+ });
+}
+
+struct Y {
+ sstring s;
+ Y(sstring s) : s(std::move(s)) {}
+ future<> stop() { return make_ready_future<>(); }
+};
+
+SEASTAR_TEST_CASE(test_constructor_argument_is_passed_to_each_core) {
+ return do_with_distributed<Y>([] (auto& y) {
+ return y.start(sstring("hello")).then([&y] {
+ return y.invoke_on_all([] (Y& y) {
+ if (y.s != "hello") {
+ throw std::runtime_error(format("expected message mismatch, is \"%s\"", y.s));
+ }
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce) {
+ return do_with_distributed<X>([] (distributed<X>& x) {
+ return x.start().then([&x] {
+ return x.map_reduce0(std::mem_fn(&X::cpu_id_squared),
+ 0,
+ std::plus<int>()).then([] (int result) {
+ int n = smp::count - 1;
+ if (result != (n * (n + 1) * (2*n + 1)) / 6) {
+ throw std::runtime_error("map_reduce failed");
+ }
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_async) {
+ return do_with_distributed<async_service>([] (distributed<async_service>& x) {
+ return x.start().then([&x] {
+ return x.invoke_on_all(&async_service::run);
+ });
+ }).then([] {
+ return sleep(std::chrono::milliseconds(100 * (smp::count + 1)));
+ });
+}
+
+SEASTAR_TEST_CASE(test_invoke_on_others) {
+ return seastar::async([] {
+ struct my_service {
+ int counter = 0;
+ void up() { ++counter; }
+ future<> stop() { return make_ready_future<>(); }
+ };
+ for (unsigned c = 0; c < smp::count; ++c) {
+ smp::submit_to(c, [c] {
+ return seastar::async([c] {
+ sharded<my_service> s;
+ s.start().get();
+ s.invoke_on_others([](auto& s) { s.up(); }).get();
+ if (s.local().counter != 0) {
+ throw std::runtime_error("local modified");
+ }
+ s.invoke_on_all([c](auto& remote) {
+ if (this_shard_id() != c) {
+ if (remote.counter != 1) {
+ throw std::runtime_error("remote not modified");
+ }
+ }
+ }).get();
+ s.stop().get();
+ });
+ }).get();
+ }
+ });
+}
+
+
+struct remote_worker {
+ unsigned current = 0;
+ unsigned max_concurrent_observed = 0;
+ unsigned expected_max;
+ semaphore sem{0};
+ remote_worker(unsigned expected_max) : expected_max(expected_max) {
+ }
+ future<> do_work() {
+ ++current;
+ max_concurrent_observed = std::max(current, max_concurrent_observed);
+ if (max_concurrent_observed >= expected_max && sem.current() == 0) {
+ sem.signal(semaphore::max_counter());
+ }
+ return sem.wait().then([this] {
+ // Sleep a bit to check if the concurrency goes over the max
+ return sleep(100ms).then([this] {
+ max_concurrent_observed = std::max(current, max_concurrent_observed);
+ --current;
+ });
+ });
+ }
+ future<> do_remote_work(shard_id t, smp_service_group ssg) {
+ return smp::submit_to(t, ssg, [this] {
+ return do_work();
+ });
+ }
+};
+
+SEASTAR_TEST_CASE(test_smp_service_groups) {
+ return async([] {
+ smp_service_group_config ssgc1;
+ ssgc1.max_nonlocal_requests = 1;
+ auto ssg1 = create_smp_service_group(ssgc1).get0();
+ smp_service_group_config ssgc2;
+ ssgc2.max_nonlocal_requests = 1000;
+ auto ssg2 = create_smp_service_group(ssgc2).get0();
+ shard_id other_shard = smp::count - 1;
+ remote_worker rm1(1);
+ remote_worker rm2(1000);
+ auto bunch1 = parallel_for_each(boost::irange(0, 20), [&] (int ignore) { return rm1.do_remote_work(other_shard, ssg1); });
+ auto bunch2 = parallel_for_each(boost::irange(0, 2000), [&] (int ignore) { return rm2.do_remote_work(other_shard, ssg2); });
+ bunch1.get();
+ bunch2.get();
+ if (smp::count > 1) {
+ assert(rm1.max_concurrent_observed == 1);
+ assert(rm2.max_concurrent_observed == 1000);
+ }
+ destroy_smp_service_group(ssg1).get();
+ destroy_smp_service_group(ssg2).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_smp_service_groups_re_construction) {
+ // During development of the feature, we saw a bug where the vector
+ // holding the groups did not expand correctly. This test triggers the
+ // bug.
+ return async([] {
+ auto ssg1 = create_smp_service_group({}).get0();
+ auto ssg2 = create_smp_service_group({}).get0();
+ destroy_smp_service_group(ssg1).get();
+ auto ssg3 = create_smp_service_group({}).get0();
+ destroy_smp_service_group(ssg2).get();
+ destroy_smp_service_group(ssg3).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_smp_timeout) {
+ return async([] {
+ smp_service_group_config ssgc1;
+ ssgc1.max_nonlocal_requests = 1;
+ auto ssg1 = create_smp_service_group(ssgc1).get0();
+
+ auto _ = defer([ssg1] {
+ destroy_smp_service_group(ssg1).get();
+ });
+
+ const shard_id other_shard = smp::count - 1;
+
+ // Ugly but beats using sleeps.
+ std::mutex mut;
+ std::unique_lock<std::mutex> lk(mut);
+
+ // Submitted to the remote shard.
+ auto fut1 = smp::submit_to(other_shard, ssg1, [&mut] {
+ std::cout << "Running request no. 1" << std::endl;
+ std::unique_lock<std::mutex> lk(mut);
+ std::cout << "Request no. 1 done" << std::endl;
+ });
+ // Consume the only unit from the semaphore.
+ auto fut2 = smp::submit_to(other_shard, ssg1, [] {
+ std::cout << "Running request no. 2 - done" << std::endl;
+ });
+
+ auto fut_timedout = smp::submit_to(other_shard, smp_submit_to_options(ssg1, smp_timeout_clock::now() + 10ms), [] {
+ std::cout << "Running timed-out request - done" << std::endl;
+ });
+
+ {
+ auto notify = defer([lk = std::move(lk)] { });
+
+ try {
+ fut_timedout.get();
+ throw std::runtime_error("smp::submit_to() didn't timeout as expected");
+ } catch (semaphore_timed_out& e) {
+ std::cout << "Expected timeout received: " << e.what() << std::endl;
+ } catch (...) {
+ std::throw_with_nested(std::runtime_error("smp::submit_to() failed with unexpected exception"));
+ }
+ }
+
+ fut1.get();
+ fut2.get();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_sharded_parameter) {
+ struct dependency {
+ unsigned val = this_shard_id() * 7;
+ };
+ struct some_service {
+ bool ok = false;
+ some_service(unsigned non_shard_dependent, unsigned shard_dependent, dependency& dep, unsigned shard_dependent_2) {
+ ok =
+ non_shard_dependent == 43
+ && shard_dependent == this_shard_id() * 3
+ && dep.val == this_shard_id() * 7
+ && shard_dependent_2 == -dep.val;
+ }
+ };
+ sharded<dependency> s_dep;
+ s_dep.start().get();
+ auto undo1 = defer([&] { s_dep.stop().get(); });
+
+ sharded<some_service> s_service;
+ s_service.start(
+ 43, // should be copied verbatim
+ sharded_parameter([] { return this_shard_id() * 3; }),
+ std::ref(s_dep),
+ sharded_parameter([] (dependency& d) { return -d.val; }, std::ref(s_dep))
+ ).get();
+ auto undo2 = defer([&] { s_service.stop().get(); });
+
+ auto all_ok = s_service.map_reduce0(std::mem_fn(&some_service::ok), true, std::multiplies<>()).get0();
+ BOOST_REQUIRE(all_ok);
+}
diff --git a/src/seastar/tests/unit/dns_test.cc b/src/seastar/tests/unit/dns_test.cc
new file mode 100644
index 000000000..ff25c453c
--- /dev/null
+++ b/src/seastar/tests/unit/dns_test.cc
@@ -0,0 +1,131 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB.
+ */
+#include <vector>
+#include <algorithm>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/net/dns.hh>
+#include <seastar/net/inet_address.hh>
+
+using namespace seastar;
+using namespace seastar::net;
+
+static const sstring seastar_name = "seastar.io";
+
+static future<> test_resolve(dns_resolver::options opts) {
+ auto d = ::make_lw_shared<dns_resolver>(std::move(opts));
+ return d->get_host_by_name(seastar_name, inet_address::family::INET).then([d](hostent e) {
+ return d->get_host_by_addr(e.addr_list.front()).then([d, a = e.addr_list.front()](hostent e) {
+ return d->get_host_by_name(e.names.front(), inet_address::family::INET).then([a](hostent e) {
+ BOOST_REQUIRE(std::count(e.addr_list.begin(), e.addr_list.end(), a));
+ });
+ });
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+static future<> test_bad_name(dns_resolver::options opts) {
+ auto d = ::make_lw_shared<dns_resolver>(std::move(opts));
+ return d->get_host_by_name("apa.ninja.gnu", inet_address::family::INET).then_wrapped([d](future<hostent> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not succeed");
+ } catch (...) {
+ // ok.
+ }
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+SEASTAR_TEST_CASE(test_resolve_udp) {
+ return test_resolve(dns_resolver::options());
+}
+
+SEASTAR_TEST_CASE(test_bad_name_udp) {
+ return test_bad_name(dns_resolver::options());
+}
+
+SEASTAR_TEST_CASE(test_timeout_udp) {
+ dns_resolver::options opts;
+ opts.servers = std::vector<inet_address>({ inet_address("1.2.3.4") }); // not a server
+ opts.udp_port = 29953; // not a dns port
+ opts.timeout = std::chrono::milliseconds(500);
+
+ auto d = ::make_lw_shared<dns_resolver>(engine().net(), opts);
+ return d->get_host_by_name(seastar_name, inet_address::family::INET).then_wrapped([d](future<hostent> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not succeed");
+ } catch (...) {
+ // ok.
+ }
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+// Currently failing, disable until fixed (#521)
+#if 0
+SEASTAR_TEST_CASE(test_resolve_tcp) {
+ dns_resolver::options opts;
+ opts.use_tcp_query = true;
+ return test_resolve(opts);
+}
+#endif
+
+SEASTAR_TEST_CASE(test_bad_name_tcp) {
+ dns_resolver::options opts;
+ opts.use_tcp_query = true;
+ return test_bad_name(opts);
+}
+
+static const sstring imaps_service = "imaps";
+static const sstring gmail_domain = "gmail.com";
+
+static future<> test_srv() {
+ auto d = ::make_lw_shared<dns_resolver>();
+ return d->get_srv_records(dns_resolver::srv_proto::tcp,
+ imaps_service,
+ gmail_domain).then([d](dns_resolver::srv_records records) {
+ BOOST_REQUIRE(!records.empty());
+ for (auto& record : records) {
+ // record.target should end with "gmail.com"
+ BOOST_REQUIRE_GT(record.target.size(), gmail_domain.size());
+ BOOST_REQUIRE_EQUAL(record.target.compare(record.target.size() - gmail_domain.size(),
+ gmail_domain.size(),
+ gmail_domain),
+ 0);
+ }
+ }).finally([d]{
+ return d->close();
+ });
+}
+
+SEASTAR_TEST_CASE(test_srv_tcp) {
+ return test_srv();
+}
diff --git a/src/seastar/tests/unit/execution_stage_test.cc b/src/seastar/tests/unit/execution_stage_test.cc
new file mode 100644
index 000000000..e74219c89
--- /dev/null
+++ b/src/seastar/tests/unit/execution_stage_test.cc
@@ -0,0 +1,334 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+#include <algorithm>
+#include <vector>
+#include <chrono>
+
+#include <seastar/core/thread.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/execution_stage.hh>
+#include <seastar/core/sleep.hh>
+
+using namespace std::chrono_literals;
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(test_create_stage_from_lvalue_function_object) {
+ return seastar::async([] {
+ auto dont_move = [obj = make_shared<int>(53)] { return *obj; };
+ auto stage = seastar::make_execution_stage("test", dont_move);
+ BOOST_REQUIRE_EQUAL(stage().get0(), 53);
+ BOOST_REQUIRE_EQUAL(dont_move(), 53);
+ });
+}
+
+SEASTAR_TEST_CASE(test_create_stage_from_rvalue_function_object) {
+ return seastar::async([] {
+ auto dont_copy = [obj = std::make_unique<int>(42)] { return *obj; };
+ auto stage = seastar::make_execution_stage("test", std::move(dont_copy));
+ BOOST_REQUIRE_EQUAL(stage().get0(), 42);
+ });
+}
+
+int func() {
+ return 64;
+}
+
+SEASTAR_TEST_CASE(test_create_stage_from_function) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", func);
+ BOOST_REQUIRE_EQUAL(stage().get0(), 64);
+ });
+}
+
+template<typename Function, typename Verify>
+void test_simple_execution_stage(Function&& func, Verify&& verify) {
+ auto stage = seastar::make_execution_stage("test", std::forward<Function>(func));
+
+ std::vector<int> vs;
+ std::default_random_engine& gen = testing::local_random_engine;
+ std::uniform_int_distribution<> dist(0, 100'000);
+ std::generate_n(std::back_inserter(vs), 1'000, [&] { return dist(gen); });
+
+ std::vector<future<int>> fs;
+ for (auto v : vs) {
+ fs.emplace_back(stage(v));
+ }
+
+ for (auto i = 0u; i < fs.size(); i++) {
+ verify(vs[i], std::move(fs[i]));
+ }
+}
+
+SEASTAR_TEST_CASE(test_simple_stage_returning_int) {
+ return seastar::async([] {
+ test_simple_execution_stage([] (int x) {
+ if (x % 2) {
+ return x * 2;
+ } else {
+ throw x;
+ }
+ }, [] (int original, future<int> result) {
+ if (original % 2) {
+ BOOST_REQUIRE_EQUAL(original * 2, result.get0());
+ } else {
+ BOOST_REQUIRE_EXCEPTION(result.get0(), int, [&] (int v) { return original == v; });
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_simple_stage_returning_future_int) {
+ return seastar::async([] {
+ test_simple_execution_stage([] (int x) {
+ if (x % 2) {
+ return make_ready_future<int>(x * 2);
+ } else {
+ return make_exception_future<int>(x);
+ }
+ }, [] (int original, future<int> result) {
+ if (original % 2) {
+ BOOST_REQUIRE_EQUAL(original * 2, result.get0());
+ } else {
+ BOOST_REQUIRE_EXCEPTION(result.get0(), int, [&] (int v) { return original == v; });
+ }
+ });
+ });
+}
+
+template<typename T>
+void test_execution_stage_avoids_copy() {
+ auto stage = seastar::make_execution_stage("test", [] (T obj) {
+ return std::move(obj);
+ });
+
+ auto f = stage(T());
+ T obj = f.get0();
+ (void)obj;
+}
+
+SEASTAR_TEST_CASE(test_stage_moves_when_cannot_copy) {
+ return seastar::async([] {
+ struct noncopyable_but_movable {
+ noncopyable_but_movable() = default;
+ noncopyable_but_movable(const noncopyable_but_movable&) = delete;
+ noncopyable_but_movable(noncopyable_but_movable&&) = default;
+ };
+
+ test_execution_stage_avoids_copy<noncopyable_but_movable>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_stage_prefers_move_to_copy) {
+ return seastar::async([] {
+ struct copyable_and_movable {
+ copyable_and_movable() = default;
+ copyable_and_movable(const copyable_and_movable&) {
+ BOOST_FAIL("should not copy");
+ }
+ copyable_and_movable(copyable_and_movable&&) = default;
+ };
+
+ test_execution_stage_avoids_copy<copyable_and_movable>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_rref_decays_to_value) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] (std::vector<int>&& vec) {
+ return vec.size();
+ });
+
+ std::vector<int> tmp;
+ std::vector<future<size_t>> fs;
+ for (auto i = 0; i < 100; i++) {
+ tmp.resize(i);
+ fs.emplace_back(stage(std::move(tmp)));
+ tmp = std::vector<int>();
+ }
+
+ for (size_t i = 0; i < 100; i++) {
+ BOOST_REQUIRE_EQUAL(fs[i].get0(), i);
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_lref_does_not_decay) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] (int& v) {
+ v++;
+ });
+
+ int value = 0;
+ std::vector<future<>> fs;
+ for (auto i = 0; i < 100; i++) {
+ //fs.emplace_back(stage(value)); // should fail to compile
+ fs.emplace_back(stage(seastar::ref(value)));
+ }
+
+ for (auto&& f : fs) {
+ f.get();
+ }
+ BOOST_REQUIRE_EQUAL(value, 100);
+ });
+}
+
+SEASTAR_TEST_CASE(test_explicit_reference_wrapper_is_not_unwrapped) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] (seastar::reference_wrapper<int> v) {
+ v.get()++;
+ });
+
+ int value = 0;
+ std::vector<future<>> fs;
+ for (auto i = 0; i < 100; i++) {
+ //fs.emplace_back(stage(value)); // should fail to compile
+ fs.emplace_back(stage(seastar::ref(value)));
+ }
+
+ for (auto&& f : fs) {
+ f.get();
+ }
+ BOOST_REQUIRE_EQUAL(value, 100);
+ });
+}
+
+SEASTAR_TEST_CASE(test_function_is_class_member) {
+ return seastar::async([] {
+ struct foo {
+ int value = -1;
+ int member(int x) {
+ return std::exchange(value, x);
+ }
+ };
+
+ auto stage = seastar::make_execution_stage("test", &foo::member);
+
+ foo object;
+ std::vector<future<int>> fs;
+ for (auto i = 0; i < 100; i++) {
+ fs.emplace_back(stage(&object, i));
+ }
+
+ for (auto i = 0; i < 100; i++) {
+ BOOST_REQUIRE_EQUAL(fs[i].get0(), i - 1);
+ }
+ BOOST_REQUIRE_EQUAL(object.value, 99);
+ });
+}
+
+SEASTAR_TEST_CASE(test_function_is_const_class_member) {
+ return seastar::async([] {
+ struct foo {
+ int value = 999;
+ int member() const {
+ return value;
+ }
+ };
+ auto stage = seastar::make_execution_stage("test", &foo::member);
+
+ const foo object;
+ BOOST_REQUIRE_EQUAL(stage(&object).get0(), 999);
+ });
+}
+
+SEASTAR_TEST_CASE(test_stage_stats) {
+ return seastar::async([] {
+ auto stage = seastar::make_execution_stage("test", [] { });
+
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_enqueued, 0u);
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_executed, 0u);
+
+ auto fs = std::vector<future<>>();
+ static constexpr auto call_count = 53u;
+ for (auto i = 0u; i < call_count; i++) {
+ fs.emplace_back(stage());
+ }
+
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_enqueued, call_count);
+
+ for (auto i = 0u; i < call_count; i++) {
+ fs[i].get();
+ BOOST_REQUIRE_GE(stage.get_stats().tasks_scheduled, 1u);
+ BOOST_REQUIRE_GE(stage.get_stats().function_calls_executed, i);
+ }
+ BOOST_REQUIRE_EQUAL(stage.get_stats().function_calls_executed, call_count);
+ });
+}
+
+SEASTAR_TEST_CASE(test_unique_stage_names_are_enforced) {
+ return seastar::async([] {
+ {
+ auto stage = seastar::make_execution_stage("test", [] {});
+ BOOST_REQUIRE_THROW(seastar::make_execution_stage("test", [] {}), std::invalid_argument);
+ stage().get();
+ }
+
+ auto stage = seastar::make_execution_stage("test", [] {});
+ stage().get();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_inheriting_concrete_execution_stage) {
+ auto sg1 = seastar::create_scheduling_group("sg1", 300).get0();
+ auto ksg1 = seastar::defer([&] { seastar::destroy_scheduling_group(sg1).get(); });
+ auto sg2 = seastar::create_scheduling_group("sg2", 100).get0();
+ auto ksg2 = seastar::defer([&] { seastar::destroy_scheduling_group(sg2).get(); });
+ auto check_sg = [] (seastar::scheduling_group sg) {
+ BOOST_REQUIRE(seastar::current_scheduling_group() == sg);
+ };
+ auto es = seastar::inheriting_concrete_execution_stage<void, seastar::scheduling_group>("stage", check_sg);
+ auto make_attr = [] (scheduling_group sg) {
+ seastar::thread_attributes a;
+ a.sched_group = sg;
+ return a;
+ };
+ bool done = false;
+ auto make_test_thread = [&] (scheduling_group sg) {
+ return seastar::thread(make_attr(sg), [&, sg] {
+ while (!done) {
+ es(sg).get(); // will check if executed with same sg
+ };
+ });
+ };
+ auto th1 = make_test_thread(sg1);
+ auto th2 = make_test_thread(sg2);
+ seastar::sleep(10ms).get();
+ done = true;
+ th1.join().get();
+ th2.join().get();
+}
+
+struct a_struct {};
+
+SEASTAR_THREAD_TEST_CASE(test_inheriting_concrete_execution_stage_reference_parameters) {
+ // mostly a compile test, but take the opportunity to test that passing
+ // by reference preserves the address
+ auto check_ref = [] (a_struct& ref, a_struct* ptr) {
+ BOOST_REQUIRE_EQUAL(&ref, ptr);
+ };
+ auto es = seastar::inheriting_concrete_execution_stage<void, a_struct&, a_struct*>("stage", check_ref);
+ a_struct obj;
+ es(seastar::ref(obj), &obj).get();
+}
diff --git a/src/seastar/tests/unit/expiring_fifo_test.cc b/src/seastar/tests/unit/expiring_fifo_test.cc
new file mode 100644
index 000000000..0d33df9e7
--- /dev/null
+++ b/src/seastar/tests/unit/expiring_fifo_test.cc
@@ -0,0 +1,190 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/manual_clock.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/expiring_fifo.hh>
+#include <seastar/util/later.hh>
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_no_expiry_operations) {
+ expiring_fifo<int> fifo;
+
+ BOOST_REQUIRE(fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE(!bool(fifo));
+
+ fifo.push_back(1);
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+
+ fifo.push_back(2);
+ fifo.push_back(3);
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 3u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+
+ fifo.pop_front();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 2);
+
+ fifo.pop_front();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+
+ fifo.pop_front();
+
+ BOOST_REQUIRE(fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE(!bool(fifo));
+
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_expiry_operations) {
+ return seastar::async([] {
+ std::vector<int> expired;
+ struct my_expiry {
+ std::vector<int>& e;
+ void operator()(int& v) { e.push_back(v); }
+ };
+
+ expiring_fifo<int, my_expiry, manual_clock> fifo(my_expiry{expired});
+
+ fifo.push_back(1, manual_clock::now() + 1s);
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ BOOST_REQUIRE(!bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 1u);
+ BOOST_REQUIRE_EQUAL(expired[0], 1);
+
+ expired.clear();
+
+ fifo.push_back(1);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 1u);
+ BOOST_REQUIRE_EQUAL(expired[0], 2);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+
+ expired.clear();
+
+ fifo.push_back(1, manual_clock::now() + 1s);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3);
+ fifo.push_back(4, manual_clock::now() + 2s);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 2u);
+ std::sort(expired.begin(), expired.end());
+ BOOST_REQUIRE_EQUAL(expired[0], 1);
+ BOOST_REQUIRE_EQUAL(expired[1], 2);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 3);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 4);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+
+ expired.clear();
+
+ fifo.push_back(1);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3, manual_clock::now() + 1s);
+ fifo.push_back(4, manual_clock::now() + 1s);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!fifo.empty());
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE(bool(fifo));
+ BOOST_REQUIRE_EQUAL(expired.size(), 3u);
+ std::sort(expired.begin(), expired.end());
+ BOOST_REQUIRE_EQUAL(expired[0], 2);
+ BOOST_REQUIRE_EQUAL(expired[1], 3);
+ BOOST_REQUIRE_EQUAL(expired[2], 4);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+
+ expired.clear();
+
+ fifo.push_back(1);
+ fifo.push_back(2, manual_clock::now() + 1s);
+ fifo.push_back(3, manual_clock::now() + 1s);
+ fifo.push_back(4, manual_clock::now() + 1s);
+ fifo.push_back(5);
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE_EQUAL(fifo.size(), 2u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 1);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 1u);
+ BOOST_REQUIRE_EQUAL(fifo.front(), 5);
+ fifo.pop_front();
+ BOOST_REQUIRE_EQUAL(fifo.size(), 0u);
+ });
+}
diff --git a/src/seastar/tests/unit/fair_queue_test.cc b/src/seastar/tests/unit/fair_queue_test.cc
new file mode 100644
index 000000000..02d63b161
--- /dev/null
+++ b/src/seastar/tests/unit/fair_queue_test.cc
@@ -0,0 +1,413 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/fair_queue.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/util/later.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/print.hh>
+#include <boost/range/irange.hpp>
+#include <chrono>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+struct request {
+ fair_queue_ticket fqdesc;
+ unsigned index;
+
+ request(unsigned weight, unsigned index)
+ : fqdesc({weight, 0})
+ , index(index)
+ {}
+};
+
+
+class test_env {
+ fair_queue _fq;
+ std::vector<int> _results;
+ std::vector<std::vector<std::exception_ptr>> _exceptions;
+ std::vector<priority_class_ptr> _classes;
+ std::vector<request> _inflight;
+
+ void drain() {
+ do {} while (tick() != 0);
+ }
+public:
+ test_env(unsigned capacity) : _fq(capacity)
+ {}
+
+ // As long as there is a request sitting in the queue, tick() will process
+ // at least one request. The only situation in which tick() will return nothing
+ // is if no requests were sent to the fair_queue (obviously).
+ //
+ // Because of this property, one useful use of tick() is to implement a drain()
+ // method (see above) in which all requests currently sent to the queue are drained
+ // before the queue is destroyed.
+ unsigned tick(unsigned n = 1) {
+ unsigned processed = 0;
+ _fq.dispatch_requests();
+
+ for (unsigned i = 0; i < n; ++i) {
+ std::vector<request> curr;
+ curr.swap(_inflight);
+
+ for (auto& req : curr) {
+ processed++;
+ _results[req.index]++;
+ _fq.notify_requests_finished(req.fqdesc);
+ }
+
+ _fq.dispatch_requests();
+ }
+ return processed;
+ }
+
+ ~test_env() {
+ drain();
+ for (auto& p: _classes) {
+ _fq.unregister_priority_class(p);
+ }
+ }
+
+ size_t register_priority_class(uint32_t shares) {
+ _results.push_back(0);
+ _exceptions.push_back(std::vector<std::exception_ptr>());
+ _classes.push_back(_fq.register_priority_class(shares));
+ return _classes.size() - 1;
+ }
+
+ void do_op(unsigned index, unsigned weight) {
+ auto cl = _classes[index];
+ auto req = request(weight, index);
+
+ _fq.queue(cl, req.fqdesc, [this, index, req] () mutable noexcept {
+ try {
+ _inflight.push_back(std::move(req));
+ } catch (...) {
+ auto eptr = std::current_exception();
+ _exceptions[index].push_back(eptr);
+ _fq.notify_requests_finished(req.fqdesc);
+ }
+ });
+ }
+
+ void update_shares(unsigned index, uint32_t shares) {
+ auto cl = _classes[index];
+ cl->update_shares(shares);
+ }
+
+ void reset_results(unsigned index) {
+ _results[index] = 0;
+ }
+
+ // Verify if the ratios are what we expect. Because we can't be sure about
+ // precise timing issues, we can always be off by some percentage. In simpler
+ // tests we really expect it to very low, but in more complex tests, with share
+ // changes, for instance, they can accumulate
+ //
+ // The ratios argument is the ratios towards the first class
+ void verify(sstring name, std::vector<unsigned> ratios, unsigned expected_error = 1) {
+ assert(ratios.size() == _results.size());
+ auto str = name + ":";
+ for (auto i = 0ul; i < _results.size(); ++i) {
+ str += format(" r[{:d}] = {:d}", i, _results[i]);
+ }
+ std::cout << str << std::endl;
+ for (auto i = 0ul; i < ratios.size(); ++i) {
+ int min_expected = ratios[i] * (_results[0] - expected_error);
+ int max_expected = ratios[i] * (_results[0] + expected_error);
+ BOOST_REQUIRE(_results[i] >= min_expected);
+ BOOST_REQUIRE(_results[i] <= max_expected);
+ BOOST_REQUIRE(_exceptions[i].size() == 0);
+ }
+ }
+};
+
+// Equal ratios. Expected equal results.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_equal_2classes) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ later().get();
+ // allow half the requests in
+ env.tick(100);
+ env.verify("equal_2classes", {1, 1});
+}
+
+// Equal results, spread among 4 classes.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_equal_4classes) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+ auto c = env.register_priority_class(10);
+ auto d = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ env.do_op(c, 1);
+ env.do_op(d, 1);
+ }
+ later().get();
+ // allow half the requests in
+ env.tick(200);
+ env.verify("equal_4classes", {1, 1, 1, 1});
+}
+
+// Class2 twice as powerful. Expected class2 to have 2 x more requests.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_different_shares) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(20);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+ // allow half the requests in
+ env.tick(100);
+ return env.verify("different_shares", {1, 2});
+}
+
+// Equal ratios, high capacity queue. Should still divide equally.
+//
+// Note that we sleep less because now more requests will be going through the
+// queue.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_equal_hi_capacity_2classes) {
+ test_env env(10);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // queue has capacity 10, 10 x 10 = 100, allow half the requests in
+ env.tick(10);
+ env.verify("hi_capacity_2classes", {1, 1});
+}
+
+// Class2 twice as powerful, queue is high capacity. Still expected class2 to
+// have 2 x more requests.
+//
+// Note that we sleep less because now more requests will be going through the
+// queue.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_different_shares_hi_capacity) {
+ test_env env(10);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(20);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+ // queue has capacity 10, 10 x 10 = 100, allow half the requests in
+ env.tick(10);
+ env.verify("different_shares_hi_capacity", {1, 2});
+}
+
+// Classes equally powerful. But Class1 issues twice as expensive requests. Expected Class2 to have 2 x more requests.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_different_weights) {
+ test_env env(2);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 2);
+ env.do_op(b, 1);
+ }
+ later().get();
+ // allow half the requests in
+ env.tick(100);
+ env.verify("different_weights", {1, 2});
+}
+
+// Class2 pushes many requests over. Right after, don't expect Class2 to be able to push anything else.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_dominant_queue) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // consume all requests
+ env.tick(100);
+ // zero statistics.
+ env.reset_results(b);
+ for (int i = 0; i < 20; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ // allow half the requests in
+ env.tick(20);
+ env.verify("dominant_queue", {1, 0});
+}
+
+// Class2 pushes many requests at first. After enough time, this shouldn't matter anymore.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_forgiving_queue) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // consume all requests
+ env.tick(100);
+ sleep(500ms).get();
+ env.reset_results(b);
+ for (int i = 0; i < 100; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ later().get();
+
+ // allow half the requests in
+ env.tick(100);
+ env.verify("forgiving_queue", {1, 1});
+}
+
+// Classes push requests and then update swap their shares. In the end, should have executed
+// the same number of requests.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_update_shares) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(20);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 500; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ later().get();
+ // allow 25% of the requests in
+ env.tick(250);
+ env.update_shares(a, 10);
+ env.update_shares(b, 20);
+
+ later().get();
+ // allow 25% of the requests in
+ env.tick(250);
+ env.verify("update_shares", {1, 1}, 2);
+}
+
+// Classes run for a longer period of time. Balance must be kept over many timer
+// periods.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_longer_run) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(10);
+
+ for (int i = 0; i < 20000; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+ // In total allow half the requests in, but do it over a
+ // long period of time, ticking slowly
+ for (int i = 0; i < 1000; ++i) {
+ sleep(1ms).get();
+ env.tick(2);
+ }
+ env.verify("longer_run", {1, 1}, 2);
+}
+
+// Classes run for a longer period of time. Proportional balance must be kept over many timer
+// periods, despite unequal shares..
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_longer_run_different_shares) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(10);
+ auto b = env.register_priority_class(20);
+
+ for (int i = 0; i < 20000; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ // In total allow half the requests in, but do it over a
+ // long period of time, ticking slowly
+ for (int i = 0; i < 1000; ++i) {
+ sleep(1ms).get();
+ env.tick(2);
+ }
+ env.verify("longer_run_different_shares", {1, 2}, 2);
+}
+
+// Classes run for a random period of time. Equal operations expected.
+SEASTAR_THREAD_TEST_CASE(test_fair_queue_random_run) {
+ test_env env(1);
+
+ auto a = env.register_priority_class(1);
+ auto b = env.register_priority_class(1);
+
+ std::default_random_engine& generator = testing::local_random_engine;
+ // multiples of 100usec - which is the approximate length of the request. We will
+ // put a minimum of 10. Below that, it is hard to guarantee anything. The maximum is
+ // about 50 seconds.
+ std::uniform_int_distribution<uint32_t> distribution(10, 500 * 1000);
+ auto reqs = distribution(generator);
+
+ // Enough requests for the maximum run (half per queue, + leeway)
+ for (uint32_t i = 0; i < reqs; ++i) {
+ env.do_op(a, 1);
+ env.do_op(b, 1);
+ }
+
+ later().get();
+ // In total allow half the requests in
+ env.tick(reqs);
+
+ // Accept 5 % error.
+ auto expected_error = std::max(1, int(round(reqs * 0.05)));
+ env.verify(format("random_run ({:d} requests)", reqs), {1, 1}, expected_error);
+}
diff --git a/src/seastar/tests/unit/file_io_test.cc b/src/seastar/tests/unit/file_io_test.cc
new file mode 100644
index 000000000..abece7c6b
--- /dev/null
+++ b/src/seastar/tests/unit/file_io_test.cc
@@ -0,0 +1,756 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014-2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+
+#include <seastar/core/seastar.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/condition-variable.hh>
+#include <seastar/core/file.hh>
+#include <seastar/core/layered_file.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/stall_sampler.hh>
+#include <seastar/core/aligned_buffer.hh>
+#include <seastar/util/tmp_file.hh>
+#include <seastar/util/alloc_failure_injector.hh>
+
+#include <boost/range/adaptor/transformed.hpp>
+#include <iostream>
+#include <sys/statfs.h>
+
+#include "core/file-impl.hh"
+
+using namespace seastar;
+namespace fs = std::filesystem;
+
+SEASTAR_TEST_CASE(open_flags_test) {
+ open_flags flags = open_flags::rw | open_flags::create | open_flags::exclusive;
+ BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
+ (std::underlying_type_t<open_flags>(open_flags::rw) |
+ std::underlying_type_t<open_flags>(open_flags::create) |
+ std::underlying_type_t<open_flags>(open_flags::exclusive)));
+
+ open_flags mask = open_flags::create | open_flags::exclusive;
+ BOOST_REQUIRE((flags & mask) == mask);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(access_flags_test) {
+ access_flags flags = access_flags::read | access_flags::write | access_flags::execute;
+ BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
+ (std::underlying_type_t<open_flags>(access_flags::read) |
+ std::underlying_type_t<open_flags>(access_flags::write) |
+ std::underlying_type_t<open_flags>(access_flags::execute)));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(file_exists_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.close().get();
+ auto exists = file_exists(filename).get0();
+ BOOST_REQUIRE(exists);
+ remove_file(filename).get();
+ exists = file_exists(filename).get0();
+ BOOST_REQUIRE(!exists);
+ });
+}
+
+SEASTAR_TEST_CASE(handle_bad_alloc_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.close().get();
+ bool exists = false;
+ memory::with_allocation_failures([&] {
+ exists = file_exists(filename).get0();
+ });
+ BOOST_REQUIRE(exists);
+ });
+}
+
+SEASTAR_TEST_CASE(file_access_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.close().get();
+ auto is_accessible = file_accessible(filename, access_flags::read | access_flags::write).get0();
+ BOOST_REQUIRE(is_accessible);
+ });
+}
+
+struct file_test {
+ file_test(file&& f) : f(std::move(f)) {}
+ file f;
+ semaphore sem = { 0 };
+ semaphore par = { 1000 };
+};
+
+SEASTAR_TEST_CASE(test1) {
+ // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB).
+ return tmp_dir::do_with([] (tmp_dir& t) {
+ static constexpr auto max = 10000;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename, open_flags::rw | open_flags::create).then([filename] (file f) {
+ auto ft = new file_test{std::move(f)};
+ for (size_t i = 0; i < max; ++i) {
+ // Don't wait for future, use semaphore to signal when done instead.
+ (void)ft->par.wait().then([ft, i] {
+ auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(wbuf.get(), wbuf.get() + 4096, i);
+ auto wb = wbuf.get();
+ (void)ft->f.dma_write(i * 4096, wb, 4096).then(
+ [ft, i, wbuf = std::move(wbuf)] (size_t ret) mutable {
+ BOOST_REQUIRE(ret == 4096);
+ auto rbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ auto rb = rbuf.get();
+ (void)ft->f.dma_read(i * 4096, rb, 4096).then(
+ [ft, rbuf = std::move(rbuf), wbuf = std::move(wbuf)] (size_t ret) mutable {
+ BOOST_REQUIRE(ret == 4096);
+ BOOST_REQUIRE(std::equal(rbuf.get(), rbuf.get() + 4096, wbuf.get()));
+ ft->sem.signal(1);
+ ft->par.signal();
+ });
+ });
+ });
+ }
+ return ft->sem.wait(max).then([ft] () mutable {
+ return ft->f.flush();
+ }).then([ft] {
+ return ft->f.close();
+ }).then([ft] () mutable {
+ delete ft;
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(parallel_write_fsync) {
+ return internal::report_reactor_stalls([] {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ // Plan: open a file and write to it like crazy. In parallel fsync() it all the time.
+ auto fname = (t.get_path() / "testfile.tmp").native();
+ auto sz = uint64_t(32*1024*1024);
+ auto buffer_size = 32768;
+ auto write_concurrency = 16;
+ auto fsync_every = 1024*1024;
+ auto max_write_ahead_of_fsync = 4*1024*1024; // ensures writes don't complete too quickly
+ auto written = uint64_t(0);
+ auto fsynced_at = uint64_t(0);
+
+ file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ // Avoid filesystem problems with size-extending operations
+ f.truncate(sz).get();
+
+ auto fsync_semaphore = semaphore(0);
+ auto may_write_condvar = condition_variable();
+ auto fsync_thread = thread([&] {
+ auto fsynced = uint64_t(0);
+ while (fsynced < sz) {
+ fsync_semaphore.wait(fsync_every).get();
+ fsynced_at = written;
+ // Signal the condition variable now so that writes proceed
+ // in parallel with the fsync
+ may_write_condvar.broadcast();
+ f.flush().get();
+ fsynced += fsync_every;
+ }
+ });
+
+ auto write_semaphore = semaphore(write_concurrency);
+ while (written < sz) {
+ write_semaphore.wait().get();
+ may_write_condvar.wait([&] {
+ return written <= fsynced_at + max_write_ahead_of_fsync;
+ }).get();
+ auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), buffer_size);
+ memset(buf.get_write(), 0, buf.size());
+ // Write asynchronously, signal when done.
+ (void)f.dma_write(written, buf.get(), buf.size()).then([&fsync_semaphore, &write_semaphore, buf = std::move(buf)] (size_t w) {
+ fsync_semaphore.signal(buf.size());
+ write_semaphore.signal();
+ });
+ written += buffer_size;
+ }
+ write_semaphore.wait(write_concurrency).get();
+
+ fsync_thread.join().get();
+ f.close().get();
+ remove_file(fname).get();
+ });
+ }).then([] (internal::stall_report sr) {
+ std::cout << "parallel_write_fsync: " << sr << "\n";
+ });
+}
+
+SEASTAR_TEST_CASE(test_iov_max) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ static constexpr size_t buffer_size = 4096;
+ static constexpr size_t buffer_count = IOV_MAX * 2 + 1;
+
+ std::vector<temporary_buffer<char>> original_buffers;
+ std::vector<iovec> iovecs;
+ for (auto i = 0u; i < buffer_count; i++) {
+ original_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
+ std::fill_n(original_buffers.back().get_write(), buffer_size, char(i));
+ iovecs.emplace_back(iovec { original_buffers.back().get_write(), buffer_size });
+ }
+
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ size_t left = buffer_size * buffer_count;
+ size_t position = 0;
+ while (left) {
+ auto written = f.dma_write(position, iovecs).get0();
+ iovecs.erase(iovecs.begin(), iovecs.begin() + written / buffer_size);
+ assert(written % buffer_size == 0);
+ position += written;
+ left -= written;
+ }
+
+ BOOST_CHECK(iovecs.empty());
+
+ std::vector<temporary_buffer<char>> read_buffers;
+ for (auto i = 0u; i < buffer_count; i++) {
+ read_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
+ std::fill_n(read_buffers.back().get_write(), buffer_size, char(0));
+ iovecs.emplace_back(iovec { read_buffers.back().get_write(), buffer_size });
+ }
+
+ left = buffer_size * buffer_count;
+ position = 0;
+ while (left) {
+ auto read = f.dma_read(position, iovecs).get0();
+ iovecs.erase(iovecs.begin(), iovecs.begin() + read / buffer_size);
+ assert(read % buffer_size == 0);
+ position += read;
+ left -= read;
+ }
+
+ for (auto i = 0u; i < buffer_count; i++) {
+ BOOST_CHECK(std::equal(original_buffers[i].get(), original_buffers[i].get() + original_buffers[i].size(),
+ read_buffers[i].get(), read_buffers[i].get() + read_buffers[i].size()));
+ }
+
+ f.close().get();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs) {
+ auto buf = temporary_buffer<char>::aligned(4096, 4096);
+
+ auto iovec_equal = [] (const iovec& a, const iovec& b) {
+ return a.iov_base == b.iov_base && a.iov_len == b.iov_len;
+ };
+
+ { // Single fragment, sanitize is noop
+ auto original_iovecs = std::vector<iovec> { { buf.get_write(), buf.size() } };
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 4096);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), 1);
+ BOOST_CHECK(iovec_equal(original_iovecs.back(), actual_iovecs.back()));
+ }
+
+ { // one 1024 buffer and IOV_MAX+6 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
+ auto original_iovecs = std::vector<iovec>{};
+ for (auto i = 0u; i < IOV_MAX + 7; i++) {
+ original_iovecs.emplace_back(iovec { buf.get_write(), i == 0 ? 1024u : 512u });
+ }
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX - 1);
+
+ original_iovecs.resize(IOV_MAX - 1);
+ BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
+ actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
+ }
+
+ { // IOV_MAX-1 buffers of 512, one 1024 buffer, and 6 512 buffers; 4096 byte disk alignment, sanitize needs to drop and trim buffers
+ auto original_iovecs = std::vector<iovec>{};
+ for (auto i = 0u; i < IOV_MAX + 7; i++) {
+ original_iovecs.emplace_back(iovec { buf.get_write(), i == (IOV_MAX - 1) ? 1024u : 512u });
+ }
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);
+
+ original_iovecs.resize(IOV_MAX);
+ original_iovecs.back().iov_len = 512;
+ BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
+ actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
+ }
+
+ { // IOV_MAX+8 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
+ auto original_iovecs = std::vector<iovec>{};
+ for (auto i = 0u; i < IOV_MAX + 8; i++) {
+ original_iovecs.emplace_back(iovec { buf.get_write(), 512 });
+ }
+ auto actual_iovecs = original_iovecs;
+ auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
+ BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
+ BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);
+
+ original_iovecs.resize(IOV_MAX);
+ BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
+ actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
+ }
+}
+
+SEASTAR_TEST_CASE(test_chmod) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ if (file_exists(filename).get0()) {
+ remove_file(filename).get();
+ }
+
+ auto orig_umask = umask(0);
+
+ // test default_file_permissions
+ auto f = open_file_dma(filename, oflags).get0();
+ f.close().get();
+ auto sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ // test chmod with new_permissions
+ auto new_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(new_permissions != file_permissions::default_file_permissions);
+ BOOST_REQUIRE(file_exists(filename).get0());
+ chmod(filename, new_permissions).get();
+ sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(new_permissions));
+ remove_file(filename).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_open_file_dma_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ if (file_exists(filename).get0()) {
+ remove_file(filename).get();
+ }
+
+ auto orig_umask = umask(0);
+
+ // test default_file_permissions
+ auto f = open_file_dma(filename, oflags).get0();
+ f.close().get();
+ auto sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+ remove_file(filename).get();
+
+ // test options.create_permissions
+ auto options = file_open_options();
+ options.create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(options.create_permissions != file_permissions::default_file_permissions);
+ f = open_file_dma(filename, oflags, options).get0();
+ f.close().get();
+ sd = file_stat(filename).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(options.create_permissions));
+ remove_file(filename).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_make_directory_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring dirname = (t.get_path() / "testdir.tmp").native();
+ auto orig_umask = umask(0);
+
+ // test default_dir_permissions with make_directory
+ make_directory(dirname).get();
+ auto sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ remove_file(dirname).get();
+
+ // test make_directory
+ auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
+ make_directory(dirname, create_permissions).get();
+ sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+ remove_file(dirname).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_touch_directory_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring dirname = (t.get_path() / "testdir.tmp").native();
+ auto orig_umask = umask(0);
+
+ // test default_dir_permissions with touch_directory
+ touch_directory(dirname).get();
+ auto sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ remove_file(dirname).get();
+
+ // test touch_directory, dir creation
+ auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
+ BOOST_REQUIRE(!file_exists(dirname).get0());
+ touch_directory(dirname, create_permissions).get();
+ sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+
+ // test touch_directory of existing dir, dir mode need not change
+ BOOST_REQUIRE(file_exists(dirname).get0());
+ touch_directory(dirname, file_permissions::default_dir_permissions).get();
+ sd = file_stat(dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+ remove_file(dirname).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_recursive_touch_directory_permissions) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring base_dirname = (t.get_path() / "testbasedir.tmp").native();
+ sstring dirpath = base_dirname + "/" + "testsubdir.tmp";
+ if (file_exists(dirpath).get0()) {
+ remove_file(dirpath).get();
+ }
+ if (file_exists(base_dirname).get0()) {
+ remove_file(base_dirname).get();
+ }
+
+ auto orig_umask = umask(0);
+
+ // test default_dir_permissions with recursive_touch_directory
+ recursive_touch_directory(dirpath).get();
+ auto sd = file_stat(base_dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ sd = file_stat(dirpath).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ remove_file(dirpath).get();
+
+ // test recursive_touch_directory, dir creation
+ auto create_permissions = file_permissions::user_read | file_permissions::group_read | file_permissions::others_read;
+ BOOST_REQUIRE(create_permissions != file_permissions::default_dir_permissions);
+ BOOST_REQUIRE(file_exists(base_dirname).get0());
+ BOOST_REQUIRE(!file_exists(dirpath).get0());
+ recursive_touch_directory(dirpath, create_permissions).get();
+ sd = file_stat(base_dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ sd = file_stat(dirpath).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+
+ // test recursive_touch_directory of existing dir, dir mode need not change
+ BOOST_REQUIRE(file_exists(dirpath).get0());
+ recursive_touch_directory(dirpath, file_permissions::default_dir_permissions).get();
+ sd = file_stat(base_dirname).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_dir_permissions));
+ sd = file_stat(dirpath).get0();
+ BOOST_CHECK_EQUAL(sd.mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(create_permissions));
+ remove_file(dirpath).get();
+ remove_file(base_dirname).get();
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_file_stat_method) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+
+ auto orig_umask = umask(0);
+
+ auto f = open_file_dma(filename, oflags).get0();
+ auto st = f.stat().get0();
+ f.close().get();
+ BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ umask(orig_umask);
+ });
+}
+
+
+class test_layered_file : public layered_file_impl {
+public:
+ explicit test_layered_file(file f) : layered_file_impl(std::move(f)) {}
+ virtual future<size_t> write_dma(uint64_t pos, const void* buffer, size_t len, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<size_t> write_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<size_t> read_dma(uint64_t pos, void* buffer, size_t len, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class& pc) override {
+ abort();
+ }
+ virtual future<> flush(void) override {
+ abort();
+ }
+ virtual future<struct stat> stat(void) override {
+ abort();
+ }
+ virtual future<> truncate(uint64_t length) override {
+ abort();
+ }
+ virtual future<> discard(uint64_t offset, uint64_t length) override {
+ abort();
+ }
+ virtual future<> allocate(uint64_t position, uint64_t length) override {
+ abort();
+ }
+ virtual future<uint64_t> size(void) override {
+ abort();
+ }
+ virtual future<> close() override {
+ abort();
+ }
+ virtual std::unique_ptr<file_handle_impl> dup() override {
+ abort();
+ }
+ virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)> next) override {
+ abort();
+ }
+ virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class& pc) override {
+ abort();
+ }
+};
+
+SEASTAR_TEST_CASE(test_underlying_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, oflags).get0();
+ auto lf = file(make_shared<test_layered_file>(f));
+ BOOST_CHECK_EQUAL(f.memory_dma_alignment(), lf.memory_dma_alignment());
+ BOOST_CHECK_EQUAL(f.disk_read_dma_alignment(), lf.disk_read_dma_alignment());
+ BOOST_CHECK_EQUAL(f.disk_write_dma_alignment(), lf.disk_write_dma_alignment());
+ f.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_file_stat_method_with_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create | open_flags::truncate;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ file ref;
+
+ auto orig_umask = umask(0);
+
+ auto st = with_file(open_file_dma(filename, oflags), [&ref] (file& f) {
+ // make a copy of f to verify f is auto-closed when `with_file` returns.
+ ref = f;
+ return f.stat();
+ }).get0();
+ BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ // verify that the file was auto-closed
+ BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
+
+ umask(orig_umask);
+ });
+}
+
+SEASTAR_TEST_CASE(test_open_error_with_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto open_file = [&t] (bool do_open) {
+ auto oflags = open_flags::ro;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ if (do_open) {
+ return open_file_dma(filename, oflags);
+ } else {
+ throw std::runtime_error("expected exception");
+ }
+ };
+ bool got_exception = false;
+
+ BOOST_REQUIRE_NO_THROW(with_file(open_file(true), [] (file& f) {
+ BOOST_REQUIRE(false);
+ }).handle_exception_type([&got_exception] (const std::system_error& e) {
+ got_exception = true;
+ BOOST_REQUIRE(e.code().value() == ENOENT);
+ }).get());
+ BOOST_REQUIRE(got_exception);
+
+ got_exception = false;
+ BOOST_REQUIRE_THROW(with_file(open_file(false), [] (file& f) {
+ BOOST_REQUIRE(false);
+ }).handle_exception_type([&got_exception] (const std::runtime_error& e) {
+ got_exception = true;
+ }).get(), std::runtime_error);
+ BOOST_REQUIRE(!got_exception);
+ });
+}
+
+SEASTAR_TEST_CASE(test_with_file_close_on_failure) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create | open_flags::truncate;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+
+ auto orig_umask = umask(0);
+
+ // error-free case
+ auto ref = with_file_close_on_failure(open_file_dma(filename, oflags), [] (file& f) {
+ return f;
+ }).get0();
+ auto st = ref.stat().get0();
+ ref.close().get();
+ BOOST_CHECK_EQUAL(st.st_mode & static_cast<mode_t>(file_permissions::all_permissions), static_cast<mode_t>(file_permissions::default_file_permissions));
+
+ // close-on-error case
+ BOOST_REQUIRE_THROW(with_file_close_on_failure(open_file_dma(filename, oflags), [&ref] (file& f) {
+ ref = f;
+ throw std::runtime_error("expected exception");
+ }).get(), std::runtime_error);
+
+ // verify that file was auto-closed on error
+ BOOST_REQUIRE_THROW(ref.stat().get(), std::system_error);
+
+ umask(orig_umask);
+ });
+}
+
+namespace seastar {
+ extern bool aio_nowait_supported;
+}
+
+SEASTAR_TEST_CASE(test_nowait_flag_correctness) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto oflags = open_flags::rw | open_flags::create;
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto is_tmpfs = [&] (sstring filename) {
+ struct ::statfs buf;
+ int fd = ::open(filename.c_str(), static_cast<int>(open_flags::ro));
+ assert(fd != -1);
+ auto r = ::fstatfs(fd, &buf);
+ if (r == -1) {
+ return false;
+ }
+ return buf.f_type == 0x01021994; // TMPFS_MAGIC
+ };
+
+ if (!seastar::aio_nowait_supported) {
+ BOOST_TEST_WARN(0, "Skipping this test because RWF_NOWAIT is not supported by the system");
+ return;
+ }
+
+ auto f = open_file_dma(filename, oflags).get0();
+ auto close_f = defer([&] { f.close().get(); });
+
+ if (is_tmpfs(filename)) {
+ BOOST_TEST_WARN(0, "Skipping this test because TMPFS was detected, and RWF_NOWAIT is only supported by disk-based FSes");
+ return;
+ }
+
+ for (auto i = 0; i < 10; i++) {
+ auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(wbuf.get(), wbuf.get() + 4096, i);
+ auto wb = wbuf.get();
+ f.dma_write(i * 4096, wb, 4096).get();
+ f.flush().get0();
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_destruct_just_constructed_append_challenged_file) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto oflags = open_flags::rw | open_flags::create;
+ auto f = open_file_dma(filename, oflags).get0();
+ });
+}
+
+SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_write) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(buf.get(), buf.get() + 4096, 0);
+
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.dma_write(0, buf.get(), 4096).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_destruct_append_challenged_file_after_read) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ sstring filename = (t.get_path() / "testfile.tmp").native();
+ auto buf = allocate_aligned_buffer<unsigned char>(4096, 4096);
+ std::fill(buf.get(), buf.get() + 4096, 0);
+
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ f.dma_write(0, buf.get(), 4096).get();
+ f.flush().get0();
+ f.close().get();
+
+ f = open_file_dma(filename, open_flags::rw).get0();
+ f.dma_read(0, buf.get(), 4096).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_dma_iovec) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ static constexpr size_t alignment = 4096;
+ auto wbuf = allocate_aligned_buffer<char>(alignment, alignment);
+ size_t size = 1234;
+ std::fill_n(wbuf.get(), alignment, char(0));
+ std::fill_n(wbuf.get(), size, char(42));
+ std::vector<iovec> iovecs;
+
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::rw | open_flags::create).get0();
+ iovecs.push_back(iovec{ wbuf.get(), alignment });
+ auto count = f.dma_write(0, iovecs).get0();
+ BOOST_REQUIRE_EQUAL(count, alignment);
+ f.truncate(size).get();
+ f.close().get();
+
+ auto rbuf = allocate_aligned_buffer<char>(alignment, alignment);
+
+ // this tests the posix_file_impl
+ f = open_file_dma(filename, open_flags::ro).get0();
+ std::fill_n(rbuf.get(), alignment, char(0));
+ iovecs.clear();
+ iovecs.push_back(iovec{ rbuf.get(), alignment });
+ count = f.dma_read(0, iovecs).get0();
+ BOOST_REQUIRE_EQUAL(count, size);
+
+ BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
+
+ // this tests the append_challenged_posix_file_impl
+ f = open_file_dma(filename, open_flags::rw).get0();
+ std::fill_n(rbuf.get(), alignment, char(0));
+ iovecs.clear();
+ iovecs.push_back(iovec{ rbuf.get(), alignment });
+ count = f.dma_read(0, iovecs).get0();
+ BOOST_REQUIRE_EQUAL(count, size);
+
+ BOOST_REQUIRE(std::equal(wbuf.get(), wbuf.get() + alignment, rbuf.get(), rbuf.get() + alignment));
+ });
+}
diff --git a/src/seastar/tests/unit/file_utils_test.cc b/src/seastar/tests/unit/file_utils_test.cc
new file mode 100644
index 000000000..3316a7751
--- /dev/null
+++ b/src/seastar/tests/unit/file_utils_test.cc
@@ -0,0 +1,287 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB
+ */
+
+#include <stdlib.h>
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+
+#include <seastar/core/file.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/tmp_file.hh>
+#include <seastar/util/file.hh>
+
+using namespace seastar;
+namespace fs = std::filesystem;
+
+class expected_exception : std::runtime_error {
+public:
+ expected_exception() : runtime_error("expected") {}
+};
+
+SEASTAR_TEST_CASE(test_make_tmp_file) {
+ return make_tmp_file().then([] (tmp_file tf) {
+ return async([tf = std::move(tf)] () mutable {
+ const sstring tmp_path = tf.get_path().native();
+ BOOST_REQUIRE(file_exists(tmp_path).get0());
+ tf.close().get();
+ tf.remove().get();
+ BOOST_REQUIRE(!file_exists(tmp_path).get0());
+ });
+ });
+}
+
+static temporary_buffer<char> get_init_buffer(file& f) {
+ auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), f.memory_dma_alignment());
+ memset(buf.get_write(), 0, buf.size());
+ return buf;
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_file) {
+ size_t expected = ~0;
+ size_t actual = 0;
+
+ tmp_file::do_with([&] (tmp_file& tf) mutable {
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ return do_with(std::move(buf), [&] (auto& buf) mutable {
+ expected = buf.size();
+ return f.dma_write(0, buf.get(), buf.size()).then([&] (size_t written) {
+ actual = written;
+ return make_ready_future<>();
+ });
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(expected , actual);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_non_existing_TMPDIR) {
+ auto old_tmpdir = getenv("TMPDIR");
+ setenv("TMPDIR", "/tmp/non-existing-TMPDIR", true);
+ BOOST_REQUIRE_EXCEPTION(tmp_file::do_with("/tmp/non-existing-TMPDIR", [] (tmp_file& tf) {}).get(),
+ std::system_error, testing::exception_predicate::message_contains("No such file or directory"));
+ if (old_tmpdir) {
+ setenv("TMPDIR", old_tmpdir, true);
+ } else {
+ unsetenv("TMPDIR");
+ }
+}
+
+static future<> touch_file(const sstring& filename, open_flags oflags = open_flags::rw | open_flags::create) noexcept {
+ return open_file_dma(filename, oflags).then([] (file f) {
+ return f.close().finally([f] {});
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_recursive_remove_directory) {
+ struct test_dir {
+ test_dir *parent;
+ sstring name;
+ std::list<sstring> sub_files = {};
+ std::list<test_dir> sub_dirs = {};
+
+ test_dir(test_dir* parent, sstring name)
+ : parent(parent)
+ , name(std::move(name))
+ { }
+
+ fs::path path() const {
+ if (!parent) {
+ return fs::path(name.c_str());
+ }
+ return parent->path() / name.c_str();
+ }
+
+ void fill_random_file(std::uniform_int_distribution<unsigned>& dist, std::default_random_engine& eng) {
+ sub_files.emplace_back(format("file-{}", dist(eng)));
+ }
+
+ test_dir& fill_random_dir(std::uniform_int_distribution<unsigned>& dist, std::default_random_engine& eng) {
+ sub_dirs.emplace_back(this, format("dir-{}", dist(eng)));
+ return sub_dirs.back();
+ }
+
+ void random_fill(int level, int levels, std::uniform_int_distribution<unsigned>& dist, std::default_random_engine& eng) {
+ int num_files = dist(eng) % 10;
+ int num_dirs = (level < levels - 1) ? (1 + dist(eng) % 3) : 0;
+
+ for (int i = 0; i < num_files; i++) {
+ fill_random_file(dist, eng);
+ }
+
+ if (num_dirs) {
+ level++;
+ for (int i = 0; i < num_dirs; i++) {
+ fill_random_dir(dist, eng).random_fill(level, levels, dist, eng);
+ }
+ }
+ }
+
+ future<> populate() {
+ return touch_directory(path().native()).then([this] {
+ return parallel_for_each(sub_files, [this] (auto& name) {
+ return touch_file((path() / name.c_str()).native());
+ }).then([this] {
+ return parallel_for_each(sub_dirs, [] (auto& sub_dir) {
+ return sub_dir.populate();
+ });
+ });
+ });
+ }
+ };
+
+ auto& eng = testing::local_random_engine;
+ auto dist = std::uniform_int_distribution<unsigned>();
+ int levels = 1 + dist(eng) % 3;
+ test_dir root = { nullptr, default_tmpdir().native() };
+ test_dir base = { &root, format("base-{}", dist(eng)) };
+ base.random_fill(0, levels, dist, eng);
+ base.populate().get();
+ recursive_remove_directory(base.path()).get();
+ BOOST_REQUIRE(!file_exists(base.path().native()).get0());
+}
+
+SEASTAR_TEST_CASE(test_make_tmp_dir) {
+ return make_tmp_dir().then([] (tmp_dir td) {
+ return async([td = std::move(td)] () mutable {
+ const sstring tmp_path = td.get_path().native();
+ BOOST_REQUIRE(file_exists(tmp_path).get0());
+ td.remove().get();
+ BOOST_REQUIRE(!file_exists(tmp_path).get0());
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_dir) {
+ size_t expected;
+ size_t actual;
+ tmp_dir::do_with([&] (tmp_dir& td) {
+ return tmp_file::do_with(td.get_path(), [&] (tmp_file& tf) {
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ return do_with(std::move(buf), [&] (auto& buf) mutable {
+ expected = buf.size();
+ return f.dma_write(0, buf.get(), buf.size()).then([&] (size_t written) {
+ actual = written;
+ return make_ready_future<>();
+ });
+ });
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(expected , actual);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_dir_with_path) {
+ size_t expected;
+ size_t actual;
+ tmp_dir::do_with(".", [&] (tmp_dir& td) {
+ return tmp_file::do_with(td.get_path(), [&] (tmp_file& tf) {
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ return do_with(std::move(buf), [&] (auto& buf) mutable {
+ expected = buf.size();
+ return tf.get_file().dma_write(0, buf.get(), buf.size()).then([&] (size_t written) {
+ actual = written;
+ return make_ready_future<>();
+ });
+ });
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(expected , actual);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_tmp_dir_with_non_existing_path) {
+ BOOST_REQUIRE_EXCEPTION(tmp_dir::do_with("/tmp/this_name_should_not_exist", [] (tmp_dir&) {}).get(),
+ std::system_error, testing::exception_predicate::message_contains("No such file or directory"));
+}
+
+SEASTAR_TEST_CASE(tmp_dir_with_thread_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& td) {
+ tmp_file tf = make_tmp_file(td.get_path()).get0();
+ auto& f = tf.get_file();
+ auto buf = get_init_buffer(f);
+ auto expected = buf.size();
+ auto actual = f.dma_write(0, buf.get(), buf.size()).get0();
+ BOOST_REQUIRE_EQUAL(expected, actual);
+ tf.close().get();
+ tf.remove().get();
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_with_leftovers_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& td) {
+ fs::path path = td.get_path() / "testfile.tmp";
+ touch_file(path.native()).get();
+ BOOST_REQUIRE(file_exists(path.native()).get0());
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_fail_func_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ BOOST_REQUIRE_THROW(tmp_dir::do_with([] (tmp_dir& inner) mutable {
+ return make_exception_future<>(expected_exception());
+ }).get(), expected_exception);
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_fail_remove_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ auto saved_default_tmpdir = default_tmpdir();
+ sstring outer_path = outer.get_path().native();
+ sstring inner_path;
+ set_default_tmpdir(outer_path.c_str());
+ BOOST_REQUIRE_THROW(tmp_dir::do_with([outer_path, &inner_path] (tmp_dir& inner) mutable {
+ inner_path = inner.get_path().native();
+ return chmod(outer_path, file_permissions::user_read | file_permissions::user_execute);
+ }).get(), std::system_error);
+ BOOST_REQUIRE(file_exists(inner_path).get0());
+ chmod(outer_path, file_permissions::default_dir_permissions).get();
+ set_default_tmpdir(saved_default_tmpdir.c_str());
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_thread_fail_func_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ BOOST_REQUIRE_THROW(tmp_dir::do_with_thread([] (tmp_dir& inner) mutable {
+ throw expected_exception();
+ }).get(), expected_exception);
+ });
+}
+
+SEASTAR_TEST_CASE(tmp_dir_do_with_thread_fail_remove_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& outer) {
+ auto saved_default_tmpdir = default_tmpdir();
+ sstring outer_path = outer.get_path().native();
+ sstring inner_path;
+ set_default_tmpdir(outer_path.c_str());
+ BOOST_REQUIRE_THROW(tmp_dir::do_with_thread([outer_path, &inner_path] (tmp_dir& inner) mutable {
+ inner_path = inner.get_path().native();
+ chmod(outer_path, file_permissions::user_read | file_permissions::user_execute).get();
+ }).get(), std::system_error);
+ BOOST_REQUIRE(file_exists(inner_path).get0());
+ chmod(outer_path, file_permissions::default_dir_permissions).get();
+ set_default_tmpdir(saved_default_tmpdir.c_str());
+ });
+}
diff --git a/src/seastar/tests/unit/foreign_ptr_test.cc b/src/seastar/tests/unit/foreign_ptr_test.cc
new file mode 100644
index 000000000..6eccb482e
--- /dev/null
+++ b/src/seastar/tests/unit/foreign_ptr_test.cc
@@ -0,0 +1,132 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/distributed.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+#include <iostream>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(make_foreign_ptr_from_lw_shared_ptr) {
+ auto p = make_foreign(make_lw_shared<sstring>("foo"));
+ BOOST_REQUIRE(p->size() == 3);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(make_foreign_ptr_from_shared_ptr) {
+ auto p = make_foreign(make_shared<sstring>("foo"));
+ BOOST_REQUIRE(p->size() == 3);
+ return make_ready_future<>();
+}
+
+
+SEASTAR_TEST_CASE(foreign_ptr_copy_test) {
+ return seastar::async([] {
+ auto ptr = make_foreign(make_shared<sstring>("foo"));
+ BOOST_REQUIRE(ptr->size() == 3);
+ auto ptr2 = ptr.copy().get0();
+ BOOST_REQUIRE(ptr2->size() == 3);
+ });
+}
+
+SEASTAR_TEST_CASE(foreign_ptr_get_test) {
+ auto p = make_foreign(std::make_unique<sstring>("foo"));
+ BOOST_REQUIRE_EQUAL(p.get(), &*p);
+ return make_ready_future<>();
+};
+
+SEASTAR_TEST_CASE(foreign_ptr_release_test) {
+ auto p = make_foreign(std::make_unique<sstring>("foo"));
+ auto raw_ptr = p.get();
+ BOOST_REQUIRE(bool(p));
+ BOOST_REQUIRE(p->size() == 3);
+ auto released_p = p.release();
+ BOOST_REQUIRE(!bool(p));
+ BOOST_REQUIRE(released_p->size() == 3);
+ BOOST_REQUIRE_EQUAL(raw_ptr, released_p.get());
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(foreign_ptr_reset_test) {
+ auto fp = make_foreign(std::make_unique<sstring>("foo"));
+ BOOST_REQUIRE(bool(fp));
+ BOOST_REQUIRE(fp->size() == 3);
+
+ fp.reset(std::make_unique<sstring>("foobar"));
+ BOOST_REQUIRE(bool(fp));
+ BOOST_REQUIRE(fp->size() == 6);
+
+ fp.reset();
+ BOOST_REQUIRE(!bool(fp));
+ return make_ready_future<>();
+}
+
+class dummy {
+ unsigned _cpu;
+public:
+ dummy() : _cpu(this_shard_id()) { }
+ ~dummy() { BOOST_REQUIRE_EQUAL(_cpu, this_shard_id()); }
+};
+
+SEASTAR_TEST_CASE(foreign_ptr_cpu_test) {
+ if (smp::count == 1) {
+ std::cerr << "Skipping multi-cpu foreign_ptr tests. Run with --smp=2 to test multi-cpu delete and reset.";
+ return make_ready_future<>();
+ }
+
+ using namespace std::chrono_literals;
+
+ return seastar::async([] {
+ auto p = smp::submit_to(1, [] {
+ return make_foreign(std::make_unique<dummy>());
+ }).get0();
+
+ p.reset(std::make_unique<dummy>());
+ }).then([] {
+ // Let ~foreign_ptr() take its course. RIP dummy.
+ return seastar::sleep(100ms);
+ });
+}
+
+SEASTAR_TEST_CASE(foreign_ptr_move_assignment_test) {
+ if (smp::count == 1) {
+ std::cerr << "Skipping multi-cpu foreign_ptr tests. Run with --smp=2 to test multi-cpu delete and reset.";
+ return make_ready_future<>();
+ }
+
+ using namespace std::chrono_literals;
+
+ return seastar::async([] {
+ auto p = smp::submit_to(1, [] {
+ return make_foreign(std::make_unique<dummy>());
+ }).get0();
+
+ p = foreign_ptr<std::unique_ptr<dummy>>();
+ }).then([] {
+ // Let ~foreign_ptr() take its course. RIP dummy.
+ return seastar::sleep(100ms);
+ });
+}
+
diff --git a/src/seastar/tests/unit/fsnotifier_test.cc b/src/seastar/tests/unit/fsnotifier_test.cc
new file mode 100644
index 000000000..b94953afc
--- /dev/null
+++ b/src/seastar/tests/unit/fsnotifier_test.cc
@@ -0,0 +1,227 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2020 ScyllaDB Ltd.
+ */
+
+#include <random>
+#include <algorithm>
+
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/fstream.hh>
+#include <seastar/core/file.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/util/std-compat.hh>
+
+#include "../../src/core/fsnotify.hh"
+#include "tmpdir.hh"
+
+namespace fs = std::filesystem;
+using namespace seastar;
+
+static bool find_event(const std::vector<fsnotifier::event>& events, const fsnotifier::watch& w, fsnotifier::flags mask, std::optional<sstring> path = {}) {
+ auto i = std::find_if(events.begin(), events.end(), [&](const fsnotifier::event& e) {
+ return (e.mask & mask) != fsnotifier::flags{}
+ && e.id == w
+ && (!path || *path == e.name)
+ ;
+ });
+ return i != events.end();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_modify_close_delete) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+ auto w = fsn.create_watch(p.native(), fsnotifier::flags::delete_self
+ | fsnotifier::flags::modify
+ | fsnotifier::flags::close
+ ).get0();
+
+ auto os = api_v3::and_newer::make_file_output_stream(f).get0();
+ os.write("kossa").get();
+ os.flush().get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::modify));
+ }
+
+ os.close().get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::close_write));
+ }
+
+ remove_file(p.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::delete_self));
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::ignored));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_overwrite) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+
+ auto write_file = [](fs::path& p, sstring content) {
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+ auto os = api_v3::and_newer::make_file_output_stream(f).get0();
+ os.write(content).get();
+ os.flush().get();
+ os.close().get();
+ };
+
+ write_file(p, "kossa");
+
+ auto w = fsn.create_watch(p.native(), fsnotifier::flags::delete_self
+ | fsnotifier::flags::modify
+ | fsnotifier::flags::close
+ ).get0();
+
+ write_file(p, "kossabello");
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::modify));
+ }
+
+ write_file(p, "kossaruffalobill");
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::modify));
+ }
+
+ auto p2 = tmp.path() / "tmp.apa";
+ write_file(p2, "le apa");
+
+ auto w2 = fsn.create_watch(tmp.path().native(), fsnotifier::flags::move_to).get0();
+
+ rename_file(p2.native(), p.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::delete_self));
+ BOOST_REQUIRE(find_event(events, w2, fsnotifier::flags::move_to, p.filename().native()));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_create_delete_child) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto w = fsn.create_watch(tmp.path().native(), fsnotifier::flags::create_child
+ | fsnotifier::flags::delete_child
+ ).get0();
+
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::create_child));
+ }
+
+ f.close().get();
+ remove_file(p.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::delete_child));
+ BOOST_REQUIRE(!find_event(events, w, fsnotifier::flags::ignored));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_open) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+ f.close().get();
+
+ auto w = fsn.create_watch(p.native(), fsnotifier::flags::open).get0();
+
+ auto f2 = open_file_dma(p.native(), open_flags::ro).get0();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::open));
+ }
+
+ f2.close().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_notify_move) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+
+ f.close().get();
+
+ auto w = fsn.create_watch(tmp.path().native(), fsnotifier::flags::move).get0();
+ auto p2 = tmp.path() / "kossa.mu";
+
+ rename_file(p.native(), p2.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::move_from, p.filename().native()));
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::move_to, p2.filename().native()));
+ }
+
+ tmpdir tmp2;
+ auto p3 = tmp2.path() / "ninja.mission";
+ auto w2 = fsn.create_watch(tmp2.path().native(), fsnotifier::flags::move).get0();
+
+ rename_file(p2.native(), p3.native()).get();
+
+ {
+ auto events = fsn.wait().get0();
+ BOOST_REQUIRE(find_event(events, w, fsnotifier::flags::move_from, p2.filename().native()));
+ BOOST_REQUIRE(find_event(events, w2, fsnotifier::flags::move_to, p3.filename().native()));
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shutdown_notifier) {
+ tmpdir tmp;
+ fsnotifier fsn;
+
+ auto p = tmp.path() / "kossa.dat";
+ auto f = open_file_dma(p.native(), open_flags::create|open_flags::rw).get0();
+
+ f.close().get();
+
+ auto w = fsn.create_watch(tmp.path().native(), fsnotifier::flags::delete_child).get0();
+ auto fut = fsn.wait();
+
+ fsn.shutdown();
+
+ auto events = fut.get0();
+ BOOST_REQUIRE(events.empty());
+}
diff --git a/src/seastar/tests/unit/fstream_test.cc b/src/seastar/tests/unit/fstream_test.cc
new file mode 100644
index 000000000..9f15b27d3
--- /dev/null
+++ b/src/seastar/tests/unit/fstream_test.cc
@@ -0,0 +1,538 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <numeric>
+#include <seastar/core/fstream.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/print.hh>
+#include <seastar/util/defer.hh>
+#include <seastar/util/tmp_file.hh>
+#include <boost/range/adaptor/transformed.hpp>
+#include <boost/algorithm/cxx11/any_of.hpp>
+#include "mock_file.hh"
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+namespace fs = std::filesystem;
+
+struct writer {
+ output_stream<char> out;
+ static future<shared_ptr<writer>> make(file f) {
+ return api_v3::and_newer::make_file_output_stream(std::move(f)).then([] (output_stream<char>&& os) {
+ return make_shared<writer>(writer{std::move(os)});
+ });
+ }
+};
+
+struct reader {
+ input_stream<char> in;
+ reader(file f) : in(make_file_input_stream(std::move(f))) {}
+ reader(file f, file_input_stream_options options) : in(make_file_input_stream(std::move(f), std::move(options))) {}
+};
+
+SEASTAR_TEST_CASE(test_fstream) {
+ return tmp_dir::do_with([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).then([filename] (file f) {
+ return writer::make(std::move(f)).then([filename] (shared_ptr<writer> w) {
+ auto buf = static_cast<char*>(::malloc(4096));
+ memset(buf, 0, 4096);
+ buf[0] = '[';
+ buf[1] = 'A';
+ buf[4095] = ']';
+ return w->out.write(buf, 4096).then([buf, w] {
+ ::free(buf);
+ return make_ready_future<>();
+ }).then([w] {
+ auto buf = static_cast<char*>(::malloc(8192));
+ memset(buf, 0, 8192);
+ buf[0] = '[';
+ buf[1] = 'B';
+ buf[8191] = ']';
+ return w->out.write(buf, 8192).then([buf, w] {
+ ::free(buf);
+ return w->out.close().then([w] {});
+ });
+ }).then([filename] {
+ return open_file_dma(filename, open_flags::ro);
+ }).then([] (file f) {
+ /* file content after running the above:
+ * 00000000 5b 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |[A..............|
+ * 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+ * *
+ * 00000ff0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5d |...............]|
+ * 00001000 5b 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |[B..............|
+ * 00001010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+ * *
+ * 00002ff0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5d |...............]|
+ * 00003000
+ */
+ auto r = make_shared<reader>(std::move(f));
+ return r->in.read_exactly(4096 + 8192).then([r] (temporary_buffer<char> buf) {
+ auto p = buf.get();
+ BOOST_REQUIRE(p[0] == '[' && p[1] == 'A' && p[4095] == ']');
+ BOOST_REQUIRE(p[4096] == '[' && p[4096 + 1] == 'B' && p[4096 + 8191] == ']');
+ return make_ready_future<>();
+ }).then([r] {
+ return r->in.close();
+ }).finally([r] {});
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_consume_skip_bytes) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ auto w = writer::make(std::move(f)).get0();
+ auto write_block = [w] (char c, size_t size) {
+ std::vector<char> vec(size, c);
+ w->out.write(&vec.front(), vec.size()).get();
+ };
+ write_block('a', 8192);
+ write_block('b', 8192);
+ w->out.close().get();
+ /* file content after running the above:
+ * 00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa|
+ * *
+ * 00002000 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 |bbbbbbbbbbbbbbbb|
+ * *
+ * 00004000
+ */
+ f = open_file_dma(filename, open_flags::ro).get0();
+ auto r = make_lw_shared<reader>(std::move(f), file_input_stream_options{512});
+ struct consumer {
+ uint64_t _count = 0;
+ using consumption_result_type = typename input_stream<char>::consumption_result_type;
+ using stop_consuming_type = typename consumption_result_type::stop_consuming_type;
+ using tmp_buf = stop_consuming_type::tmp_buf;
+
+ /*
+ * Consumer reads the file as follows:
+ * - first 8000 bytes are read in 512-byte chunks and checked
+ * - next 2000 bytes are skipped (jumping over both read buffer size and DMA block)
+ * - the remaining 6384 bytes are read and checked
+ */
+ future<consumption_result_type> operator()(tmp_buf buf) {
+ if (_count < 8000) {
+ auto delta = std::min(buf.size(), 8000 - _count);
+ for (auto c : buf.share(0, delta)) {
+ BOOST_REQUIRE_EQUAL(c, 'a');
+ }
+ buf.trim_front(delta);
+ _count += delta;
+
+ if (_count == 8000) {
+ return make_ready_future<consumption_result_type>(skip_bytes{2000 - buf.size()});
+ } else {
+ assert(buf.empty());
+ return make_ready_future<consumption_result_type>(continue_consuming{});
+ }
+ return make_ready_future<consumption_result_type>(continue_consuming{});
+ } else {
+ for (auto c : buf) {
+ BOOST_REQUIRE_EQUAL(c, 'b');
+ }
+ _count += buf.size();
+ if (_count < 14384) {
+ return make_ready_future<consumption_result_type>(continue_consuming{});
+ } else if (_count > 14384) {
+ BOOST_FAIL("Read more than expected");
+ }
+ return make_ready_future<consumption_result_type>(stop_consuming_type({}));
+ }
+ }
+ };
+ r->in.consume(consumer{}).get();
+ r->in.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_fstream_unaligned) {
+ return tmp_dir::do_with([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).then([filename] (file f) {
+ return writer::make(std::move(f)).then([filename] (shared_ptr<writer> w) {
+ auto buf = static_cast<char*>(::malloc(40));
+ memset(buf, 0, 40);
+ buf[0] = '[';
+ buf[1] = 'A';
+ buf[39] = ']';
+ return w->out.write(buf, 40).then([buf, w] {
+ ::free(buf);
+ return w->out.close().then([w] {});
+ }).then([filename] {
+ return open_file_dma(filename, open_flags::ro);
+ }).then([] (file f) {
+ return do_with(std::move(f), [] (file& f) {
+ return f.size().then([] (size_t size) {
+ // assert that file was indeed truncated to the amount of bytes written.
+ BOOST_REQUIRE(size == 40);
+ return make_ready_future<>();
+ });
+ });
+ }).then([filename] {
+ return open_file_dma(filename, open_flags::ro);
+ }).then([] (file f) {
+ auto r = make_shared<reader>(std::move(f));
+ return r->in.read_exactly(40).then([r] (temporary_buffer<char> buf) {
+ auto p = buf.get();
+ BOOST_REQUIRE(p[0] == '[' && p[1] == 'A' && p[39] == ']');
+ return make_ready_future<>();
+ }).then([r] {
+ return r->in.close();
+ }).finally([r] {});
+ });
+ });
+ });
+ });
+}
+
+future<> test_consume_until_end(uint64_t size) {
+ return tmp_dir::do_with([size] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ return open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).then([size] (file f) {
+ return api_v3::and_newer::make_file_output_stream(f).then([size] (output_stream<char>&& os) {
+ return do_with(std::move(os), [size] (output_stream<char>& out) {
+ std::vector<char> buf(size);
+ std::iota(buf.begin(), buf.end(), 0);
+ return out.write(buf.data(), buf.size()).then([&out] {
+ return out.flush();
+ });
+ });
+ }).then([f] {
+ return f.size();
+ }).then([size, f] (size_t real_size) {
+ BOOST_REQUIRE_EQUAL(size, real_size);
+ }).then([size, f] {
+ auto consumer = [offset = uint64_t(0), size] (temporary_buffer<char> buf) mutable -> future<input_stream<char>::unconsumed_remainder> {
+ if (!buf) {
+ return make_ready_future<input_stream<char>::unconsumed_remainder>(temporary_buffer<char>());
+ }
+ BOOST_REQUIRE(offset + buf.size() <= size);
+ std::vector<char> expected(buf.size());
+ std::iota(expected.begin(), expected.end(), offset);
+ offset += buf.size();
+ BOOST_REQUIRE(std::equal(buf.begin(), buf.end(), expected.begin()));
+ return make_ready_future<input_stream<char>::unconsumed_remainder>(std::nullopt);
+ };
+ return do_with(make_file_input_stream(f), std::move(consumer), [] (input_stream<char>& in, auto& consumer) {
+ return in.consume(consumer).then([&in] {
+ return in.close();
+ });
+ });
+ }).finally([f] () mutable {
+ return f.close();
+ });
+ });
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_consume_aligned_file) {
+ return test_consume_until_end(4096);
+}
+
+SEASTAR_TEST_CASE(test_consume_empty_file) {
+ return test_consume_until_end(0);
+}
+
+SEASTAR_TEST_CASE(test_consume_unaligned_file) {
+ return test_consume_until_end(1);
+}
+
+SEASTAR_TEST_CASE(test_consume_unaligned_file_large) {
+ return test_consume_until_end((1 << 20) + 1);
+}
+
+SEASTAR_TEST_CASE(test_input_stream_esp_around_eof) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto flen = uint64_t(5341);
+ auto rdist = std::uniform_int_distribution<char>();
+ auto reng = testing::local_random_engine;
+ auto data = boost::copy_range<std::vector<uint8_t>>(
+ boost::irange<uint64_t>(0, flen)
+ | boost::adaptors::transformed([&] (int x) { return rdist(reng); }));
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ auto out = api_v3::and_newer::make_file_output_stream(f).get0();
+ out.write(reinterpret_cast<const char*>(data.data()), data.size()).get();
+ out.flush().get();
+ //out.close().get(); // FIXME: closes underlying stream:?!
+ struct range { uint64_t start; uint64_t end; };
+ auto ranges = std::vector<range>{{
+ range{0, flen},
+ range{0, flen * 2},
+ range{0, flen + 1},
+ range{0, flen - 1},
+ range{0, 1},
+ range{1, 2},
+ range{flen - 1, flen},
+ range{flen - 1, flen + 1},
+ range{flen, flen + 1},
+ range{flen + 1, flen + 2},
+ range{1023, flen-1},
+ range{1023, flen},
+ range{1023, flen + 2},
+ range{8193, 8194},
+ range{1023, 1025},
+ range{1023, 1024},
+ range{1024, 1025},
+ range{1023, 4097},
+ }};
+ auto opt = file_input_stream_options();
+ opt.buffer_size = 512;
+ for (auto&& r : ranges) {
+ auto start = r.start;
+ auto end = r.end;
+ auto len = end - start;
+ auto in = make_file_input_stream(f, start, len, opt);
+ std::vector<uint8_t> readback;
+ auto more = true;
+ while (more) {
+ auto rdata = in.read().get0();
+ for (size_t i = 0; i < rdata.size(); ++i) {
+ readback.push_back(rdata.get()[i]);
+ }
+ more = !rdata.empty();
+ }
+ //in.close().get();
+ auto xlen = std::min(end, flen) - std::min(flen, start);
+ if (xlen != readback.size()) {
+ BOOST_FAIL(format("Expected {:d} bytes but got {:d}, start={:d}, end={:d}", xlen, readback.size(), start, end));
+ }
+ BOOST_REQUIRE(std::equal(readback.begin(), readback.end(), data.begin() + std::min(start, flen)));
+ }
+ f.close().get();
+ });
+}
+
+#if SEASTAR_API_LEVEL >= 3
+SEASTAR_TEST_CASE(without_api_prefix) {
+ return tmp_dir::do_with_thread([](tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename,
+ open_flags::rw | open_flags::create | open_flags::truncate).get0();
+ output_stream<char> out = make_file_output_stream(f).get0();
+ out.close().get();
+ });
+}
+#endif
+
+SEASTAR_TEST_CASE(file_handle_test) {
+ return tmp_dir::do_with_thread([] (tmp_dir& t) {
+ auto filename = (t.get_path() / "testfile.tmp").native();
+ auto f = open_file_dma(filename, open_flags::create | open_flags::truncate | open_flags::rw).get0();
+ auto buf = static_cast<char*>(aligned_alloc(4096, 4096));
+ auto del = defer([&] { ::free(buf); });
+ for (unsigned i = 0; i < 4096; ++i) {
+ buf[i] = i;
+ }
+ f.dma_write(0, buf, 4096).get();
+ auto bad = std::vector<unsigned>(smp::count); // std::vector<bool> is special and unsuitable because it uses bitfields
+ smp::invoke_on_all([fh = f.dup(), &bad] {
+ return seastar::async([fh, &bad] {
+ auto f = fh.to_file();
+ auto buf = static_cast<char*>(aligned_alloc(4096, 4096));
+ auto del = defer([&] { ::free(buf); });
+ f.dma_read(0, buf, 4096).get();
+ for (unsigned i = 0; i < 4096; ++i) {
+ bad[this_shard_id()] |= buf[i] != char(i);
+ }
+ });
+ }).get();
+ BOOST_REQUIRE(!boost::algorithm::any_of_equal(bad, 1u));
+ f.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_fstream_slow_start) {
+ return seastar::async([] {
+ static constexpr size_t file_size = 128 * 1024 * 1024;
+ static constexpr size_t buffer_size = 260 * 1024;
+ static constexpr size_t read_ahead = 1;
+
+ auto mock_file = make_shared<mock_read_only_file>(file_size);
+
+ auto history = make_lw_shared<file_input_stream_history>();
+
+ file_input_stream_options options{};
+ options.buffer_size = buffer_size;
+ options.read_ahead = read_ahead;
+ options.dynamic_adjustments = history;
+
+ static constexpr size_t requests_at_slow_start = 2; // 1 request + 1 read-ahead
+ static constexpr size_t requests_at_full_speed = read_ahead + 1; // 1 request + read_ahead
+
+ std::optional<size_t> initial_read_size;
+
+ auto read_whole_file_with_slow_start = [&] (auto fstr) {
+ uint64_t total_read = 0;
+ size_t previous_buffer_length = 0;
+
+ // We don't want to assume too much about fstream internals, but with
+ // no history we should start with a buffer sizes somewhere in
+ // (0, buffer_size) range.
+ mock_file->set_read_size_verifier([&] (size_t length) {
+ BOOST_CHECK_LE(length, initial_read_size.value_or(buffer_size - 1));
+ BOOST_CHECK_GE(length, initial_read_size.value_or(1));
+ previous_buffer_length = length;
+ if (!initial_read_size) {
+ initial_read_size = length;
+ }
+ });
+
+ // Slow start phase
+ while (true) {
+ // We should leave slow start before reading the whole file.
+ BOOST_CHECK_LT(total_read, file_size);
+
+ mock_file->set_allowed_read_requests(requests_at_slow_start);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_GT(buf.size(), 0u);
+
+ mock_file->set_read_size_verifier([&] (size_t length) {
+ // There is no reason to reduce buffer size.
+ BOOST_CHECK_LE(length, std::min(previous_buffer_length * 2, buffer_size));
+ BOOST_CHECK_GE(length, previous_buffer_length);
+ previous_buffer_length = length;
+ });
+
+ BOOST_TEST_MESSAGE(format("Size {:d}", buf.size()));
+ total_read += buf.size();
+ if (buf.size() == buffer_size) {
+ BOOST_TEST_MESSAGE("Leaving slow start phase.");
+ break;
+ }
+ }
+
+ // Reading at full speed now
+ mock_file->set_expected_read_size(buffer_size);
+ while (total_read != file_size) {
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ total_read += buf.size();
+ }
+
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_EQUAL(buf.size(), 0u);
+ assert(buf.size() == 0);
+ };
+
+ auto read_while_file_at_full_speed = [&] (auto fstr) {
+ uint64_t total_read = 0;
+
+ mock_file->set_expected_read_size(buffer_size);
+ while (total_read != file_size) {
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ total_read += buf.size();
+ }
+
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_EQUAL(buf.size(), 0u);
+ };
+
+ auto read_and_skip_a_lot = [&] (auto fstr) {
+ uint64_t total_read = 0;
+ size_t previous_buffer_size = buffer_size;
+
+ mock_file->set_allowed_read_requests(std::numeric_limits<size_t>::max());
+ mock_file->set_read_size_verifier([&] (size_t length) {
+ // There is no reason to reduce buffer size.
+ BOOST_CHECK_LE(length, previous_buffer_size);
+ BOOST_CHECK_GE(length, initial_read_size.value_or(1));
+ previous_buffer_size = length;
+ });
+ while (total_read != file_size) {
+ auto buf = fstr.read().get0();
+ total_read += buf.size();
+
+ buf = fstr.read().get0();
+ total_read += buf.size();
+
+ auto skip_by = std::min(file_size - total_read, buffer_size * 2);
+ fstr.skip(skip_by).get();
+ total_read += skip_by;
+ }
+
+ // We should be back at slow start at this stage.
+ BOOST_CHECK_LT(previous_buffer_size, buffer_size);
+ if (initial_read_size) {
+ BOOST_CHECK_EQUAL(previous_buffer_size, *initial_read_size);
+ }
+
+ mock_file->set_allowed_read_requests(requests_at_full_speed);
+ auto buf = fstr.read().get0();
+ BOOST_CHECK_EQUAL(buf.size(), 0u);
+
+ };
+
+ auto make_fstream = [&] {
+ struct fstream_wrapper {
+ input_stream<char> s;
+ explicit fstream_wrapper(input_stream<char>&& s) : s(std::move(s)) {}
+ fstream_wrapper(fstream_wrapper&&) = default;
+ fstream_wrapper& operator=(fstream_wrapper&&) = default;
+ future<temporary_buffer<char>> read() {
+ return s.read();
+ }
+ future<> skip(uint64_t n) {
+ return s.skip(n);
+ }
+ ~fstream_wrapper() {
+ s.close().get();
+ }
+ };
+ return fstream_wrapper(make_file_input_stream(file(mock_file), 0, file_size, options));
+ };
+
+ BOOST_TEST_MESSAGE("Reading file, no history, expectiong a slow start");
+ read_whole_file_with_slow_start(make_fstream());
+ BOOST_TEST_MESSAGE("Reading file again, everything good so far, read at full speed");
+ read_while_file_at_full_speed(make_fstream());
+ BOOST_TEST_MESSAGE("Reading and skipping a lot");
+ read_and_skip_a_lot(make_fstream());
+ BOOST_TEST_MESSAGE("Reading file, bad history, we are back at slow start...");
+ read_whole_file_with_slow_start(make_fstream());
+ BOOST_TEST_MESSAGE("Reading file yet again, should've recovered by now");
+ read_while_file_at_full_speed(make_fstream());
+ });
+}
diff --git a/src/seastar/tests/unit/futures_test.cc b/src/seastar/tests/unit/futures_test.cc
new file mode 100644
index 000000000..200e33ac9
--- /dev/null
+++ b/src/seastar/tests/unit/futures_test.cc
@@ -0,0 +1,1617 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/stream.hh>
+#include <seastar/util/backtrace.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/shared_future.hh>
+#include <seastar/core/manual_clock.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/util/log.hh>
+#include <boost/iterator/counting_iterator.hpp>
+#include <seastar/testing/thread_test_case.hh>
+
+#include <boost/range/iterator_range.hpp>
+#include <boost/range/irange.hpp>
+
+#include <seastar/core/internal/api-level.hh>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+static_assert(std::is_nothrow_default_constructible_v<gate>,
+ "seastar::gate constructor must not throw");
+static_assert(std::is_nothrow_move_constructible_v<gate>,
+ "seastar::gate move constructor must not throw");
+
+static_assert(std::is_nothrow_default_constructible_v<shared_future<>>);
+static_assert(std::is_nothrow_copy_constructible_v<shared_future<>>);
+static_assert(std::is_nothrow_move_constructible_v<shared_future<>>);
+
+static_assert(std::is_nothrow_move_constructible_v<shared_promise<>>);
+
+class expected_exception : public std::runtime_error {
+public:
+ expected_exception() : runtime_error("expected") {}
+};
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wself-move"
+#endif
+SEASTAR_TEST_CASE(test_self_move) {
+ future_state<std::tuple<std::unique_ptr<int>>> s1;
+ s1.set(std::make_unique<int>(42));
+ s1 = std::move(s1); // no crash, but the value of s1 is not defined.
+
+#if SEASTAR_API_LEVEL < 5
+ future_state<std::tuple<std::unique_ptr<int>>> s2;
+#else
+ future_state<std::unique_ptr<int>> s2;
+#endif
+ s2.set(std::make_unique<int>(42));
+ std::swap(s2, s2);
+ BOOST_REQUIRE_EQUAL(*std::move(s2).get0(), 42);
+
+ promise<std::unique_ptr<int>> p1;
+ p1.set_value(std::make_unique<int>(42));
+ p1 = std::move(p1); // no crash, but the value of p1 is not defined.
+
+ promise<std::unique_ptr<int>> p2;
+ p2.set_value(std::make_unique<int>(42));
+ std::swap(p2, p2);
+ BOOST_REQUIRE_EQUAL(*p2.get_future().get0(), 42);
+
+ auto f1 = make_ready_future<std::unique_ptr<int>>(std::make_unique<int>(42));
+ f1 = std::move(f1); // no crash, but the value of f1 is not defined.
+
+ auto f2 = make_ready_future<std::unique_ptr<int>>(std::make_unique<int>(42));
+ std::swap(f2, f2);
+ BOOST_REQUIRE_EQUAL(*f2.get0(), 42);
+
+ return make_ready_future<>();
+}
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+static subscription<int> get_empty_subscription(std::function<future<> (int)> func) {
+ stream<int> s;
+ auto ret = s.listen(func);
+ s.close();
+ return ret;
+}
+
+SEASTAR_TEST_CASE(test_stream) {
+ auto sub = get_empty_subscription([](int x) {
+ return make_ready_future<>();
+ });
+ return sub.done();
+}
+
+SEASTAR_TEST_CASE(test_stream_drop_sub) {
+ auto s = make_lw_shared<stream<int>>();
+ std::optional<future<>> ret;
+ {
+ auto sub = s->listen([](int x) {
+ return make_ready_future<>();
+ });
+ ret = sub.done();
+ // It is ok to drop the subscription when we only want the competition future.
+ }
+ return s->produce(42).then([ret = std::move(*ret), s] () mutable {
+ s->close();
+ return std::move(ret);
+ });
+}
+
+SEASTAR_TEST_CASE(test_reference) {
+ int a = 42;
+ future<int&> orig = make_ready_future<int&>(a);
+ future<int&> fut = std::move(orig);
+ int& r = fut.get0();
+ r = 43;
+ BOOST_REQUIRE_EQUAL(a, 43);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_set_future_state_with_tuple) {
+ future_state<std::tuple<int>> s1;
+ promise<int> p1;
+ const std::tuple<int> v1(42);
+ s1.set(v1);
+ p1.set_value(v1);
+
+ return make_ready_future<>();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_set_value_make_exception_in_copy) {
+ struct throw_in_copy {
+ throw_in_copy() noexcept = default;
+ throw_in_copy(throw_in_copy&& x) noexcept {
+ }
+ throw_in_copy(const throw_in_copy& x) {
+ throw 42;
+ }
+ };
+ promise<throw_in_copy> p1;
+ throw_in_copy v;
+ p1.set_value(v);
+ BOOST_REQUIRE_THROW(p1.get_future().get(), int);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_set_exception_in_constructor) {
+ struct throw_in_constructor {
+ throw_in_constructor() {
+ throw 42;
+ }
+ };
+ future<throw_in_constructor> f = make_ready_future<throw_in_constructor>();
+ BOOST_REQUIRE(f.failed());
+ BOOST_REQUIRE_THROW(f.get(), int);
+}
+
+SEASTAR_TEST_CASE(test_finally_is_called_on_success_and_failure) {
+ auto finally1 = make_shared<bool>();
+ auto finally2 = make_shared<bool>();
+
+ return make_ready_future().then([] {
+ }).finally([=] {
+ *finally1 = true;
+ }).then([] {
+ throw std::runtime_error("");
+ }).finally([=] {
+ *finally2 = true;
+ }).then_wrapped([=] (auto&& f) {
+ BOOST_REQUIRE(*finally1);
+ BOOST_REQUIRE(*finally2);
+
+ // Should be failed.
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_get_on_promise) {
+ auto p = promise<uint32_t>();
+ p.set_value(10);
+ BOOST_REQUIRE_EQUAL(10u, p.get_future().get0());
+ return make_ready_future();
+}
+
+// An exception class with a controlled what() overload
+class test_exception : public std::exception {
+ sstring _what;
+public:
+ explicit test_exception(sstring what) : _what(std::move(what)) {}
+ virtual const char* what() const noexcept override {
+ return _what.c_str();
+ }
+};
+
+static void check_finally_exception(std::exception_ptr ex) {
+ BOOST_REQUIRE_EQUAL(fmt::format("{}", ex),
+ "seastar::nested_exception: test_exception (bar) (while cleaning up after test_exception (foo))");
+ try {
+ // convert to the concrete type nested_exception
+ std::rethrow_exception(ex);
+ } catch (seastar::nested_exception& ex) {
+ try {
+ std::rethrow_exception(ex.inner);
+ } catch (test_exception& inner) {
+ BOOST_REQUIRE_EQUAL(inner.what(), "bar");
+ }
+ try {
+ ex.rethrow_nested();
+ } catch (test_exception& outer) {
+ BOOST_REQUIRE_EQUAL(outer.what(), "foo");
+ }
+ }
+}
+
+SEASTAR_TEST_CASE(test_finally_exception) {
+ return make_ready_future<>().then([] {
+ throw test_exception("foo");
+ }).finally([] {
+ throw test_exception("bar");
+ }).handle_exception(check_finally_exception);
+}
+
+SEASTAR_TEST_CASE(test_finally_exceptional_future) {
+ return make_ready_future<>().then([] {
+ throw test_exception("foo");
+ }).finally([] {
+ return make_exception_future<>(test_exception("bar"));
+ }).handle_exception(check_finally_exception);
+}
+
+SEASTAR_TEST_CASE(test_finally_waits_for_inner) {
+ auto finally = make_shared<bool>();
+ auto p = make_shared<promise<>>();
+
+ auto f = make_ready_future().then([] {
+ }).finally([=] {
+ return p->get_future().then([=] {
+ *finally = true;
+ });
+ }).then([=] {
+ BOOST_REQUIRE(*finally);
+ });
+ BOOST_REQUIRE(!*finally);
+ p->set_value();
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_finally_is_called_on_success_and_failure__not_ready_to_armed) {
+ auto finally1 = make_shared<bool>();
+ auto finally2 = make_shared<bool>();
+
+ promise<> p;
+ auto f = p.get_future().finally([=] {
+ *finally1 = true;
+ }).then([] {
+ throw std::runtime_error("");
+ }).finally([=] {
+ *finally2 = true;
+ }).then_wrapped([=] (auto &&f) {
+ BOOST_REQUIRE(*finally1);
+ BOOST_REQUIRE(*finally2);
+ try {
+ f.get();
+ } catch (...) {} // silence exceptional future ignored messages
+ });
+
+ p.set_value();
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_exception_from_finally_fails_the_target) {
+ promise<> pr;
+ auto f = pr.get_future().finally([=] {
+ throw std::runtime_error("");
+ }).then([] {
+ BOOST_REQUIRE(false);
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ } catch (...) {} // silence exceptional future ignored messages
+ });
+
+ pr.set_value();
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_exception_from_finally_fails_the_target_on_already_resolved) {
+ return make_ready_future().finally([=] {
+ throw std::runtime_error("");
+ }).then([] {
+ BOOST_REQUIRE(false);
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ } catch (...) {} // silence exceptional future ignored messages
+ });
+}
+
+SEASTAR_TEST_CASE(test_exception_thrown_from_then_wrapped_causes_future_to_fail) {
+ return make_ready_future().then_wrapped([] (auto&& f) {
+ throw std::runtime_error("");
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_exception_thrown_from_then_wrapped_causes_future_to_fail__async_case) {
+ promise<> p;
+
+ auto f = p.get_future().then_wrapped([] (auto&& f) {
+ throw std::runtime_error("");
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+
+ p.set_value();
+
+ return f;
+}
+
+SEASTAR_TEST_CASE(test_failing_intermediate_promise_should_fail_the_master_future) {
+ promise<> p1;
+ promise<> p2;
+
+ auto f = p1.get_future().then([f = p2.get_future()] () mutable {
+ return std::move(f);
+ }).then([] {
+ BOOST_REQUIRE(false);
+ });
+
+ p1.set_value();
+ p2.set_exception(std::runtime_error("boom"));
+
+ return std::move(f).then_wrapped([](auto&& f) {
+ try {
+ f.get();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__not_ready_to_unarmed) {
+ promise<> p1;
+ promise<> p2;
+
+ auto f1 = p1.get_future();
+ auto f2 = p2.get_future();
+
+ f1.forward_to(std::move(p2));
+
+ BOOST_REQUIRE(!f2.available());
+
+ auto called = f2.then([] {});
+
+ p1.set_value();
+ return called;
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__not_ready_to_armed) {
+ promise<> p1;
+ promise<> p2;
+
+ auto f1 = p1.get_future();
+ auto f2 = p2.get_future();
+
+ auto called = f2.then([] {});
+
+ f1.forward_to(std::move(p2));
+
+ BOOST_REQUIRE(!f2.available());
+
+ p1.set_value();
+
+ return called;
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__ready_to_unarmed) {
+ promise<> p2;
+
+ auto f1 = make_ready_future<>();
+ auto f2 = p2.get_future();
+
+ std::move(f1).forward_to(std::move(p2));
+ BOOST_REQUIRE(f2.available());
+
+ return std::move(f2).then_wrapped([] (future<> f) {
+ BOOST_REQUIRE(!f.failed());
+ });
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__ready_to_armed) {
+ promise<> p2;
+
+ auto f1 = make_ready_future<>();
+ auto f2 = p2.get_future();
+
+ auto called = std::move(f2).then([] {});
+
+ BOOST_REQUIRE(f1.available());
+
+ f1.forward_to(std::move(p2));
+ return called;
+}
+
+static void forward_dead_unarmed_promise_with_dead_future_to(promise<>& p) {
+ promise<> p2;
+ p.get_future().forward_to(std::move(p2));
+}
+
+SEASTAR_TEST_CASE(test_future_forwarding__ready_to_unarmed_soon_to_be_dead) {
+ promise<> p1;
+ forward_dead_unarmed_promise_with_dead_future_to(p1);
+ make_ready_future<>().forward_to(std::move(p1));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_exception_can_be_thrown_from_do_until_body) {
+ return do_until([] { return false; }, [] {
+ throw expected_exception();
+ return now();
+ }).then_wrapped([] (auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have failed");
+ } catch (const expected_exception& e) {
+ // expected
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_bare_value_can_be_returned_from_callback) {
+ return now().then([] {
+ return 3;
+ }).then([] (int x) {
+ BOOST_REQUIRE(x == 3);
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_all_iterator_range) {
+ std::vector<future<size_t>> futures;
+ for (size_t i = 0; i != 1000000; ++i) {
+ // Use a mix of available and unavailable futures to exercise
+ // both paths in when_all().
+ auto fut = (i % 2) == 0 ? make_ready_future<>() : later();
+ futures.push_back(fut.then([i] { return i; }));
+ }
+ // Verify the above statement is correct
+ BOOST_REQUIRE(!std::all_of(futures.begin(), futures.end(),
+ [] (auto& f) { return f.available(); }));
+ auto p = make_shared(std::move(futures));
+ return when_all(p->begin(), p->end()).then([p] (std::vector<future<size_t>> ret) {
+ BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [] (auto& f) { return f.available(); }));
+ BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [&ret] (auto& f) { return f.get0() == size_t(&f - ret.data()); }));
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce) {
+ auto square = [] (long x) { return make_ready_future<long>(x*x); };
+ long n = 1000;
+ return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+ square, long(0), std::plus<long>()).then([n] (auto result) {
+ auto m = n - 1; // counting does not include upper bound
+ BOOST_REQUIRE_EQUAL(result, (m * (m + 1) * (2*m + 1)) / 6);
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce_simple) {
+ return do_with(0L, [] (auto& res) {
+ long n = 10;
+ return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+ [] (long x) { return x; },
+ [&res] (long x) { res += x; }).then([n, &res] {
+ long expected = (n * (n - 1)) / 2;
+ BOOST_REQUIRE_EQUAL(res, expected);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_map_reduce_tuple) {
+ return do_with(0L, 0L, [] (auto& res0, auto& res1) {
+ long n = 10;
+ return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
+ [] (long x) { return std::tuple<long, long>(x, -x); },
+ [&res0, &res1] (std::tuple<long, long> t) { res0 += std::get<0>(t); res1 += std::get<1>(t); }).then([n, &res0, &res1] {
+ long expected = (n * (n - 1)) / 2;
+ BOOST_REQUIRE_EQUAL(res0, expected);
+ BOOST_REQUIRE_EQUAL(res1, -expected);
+ });
+ });
+}
+
+// This test doesn't actually test anything - it just waits for the future
+// returned by sleep to complete. However, a bug we had in sleep() caused
+// this test to fail the sanitizer in the debug build, so this is a useful
+// regression test.
+SEASTAR_TEST_CASE(test_sleep) {
+ return sleep(std::chrono::milliseconds(100));
+}
+
+SEASTAR_TEST_CASE(test_do_with_1) {
+ return do_with(1, [] (int& one) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_2) {
+ return do_with(1, 2L, [] (int& one, long two) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ BOOST_REQUIRE_EQUAL(two, 2);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_3) {
+ return do_with(1, 2L, 3, [] (int& one, long two, int three) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ BOOST_REQUIRE_EQUAL(two, 2);
+ BOOST_REQUIRE_EQUAL(three, 3);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_4) {
+ return do_with(1, 2L, 3, 4, [] (int& one, long two, int three, int four) {
+ BOOST_REQUIRE_EQUAL(one, 1);
+ BOOST_REQUIRE_EQUAL(two, 2);
+ BOOST_REQUIRE_EQUAL(three, 3);
+ BOOST_REQUIRE_EQUAL(four, 4);
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_5) {
+ using func = noncopyable_function<void()>;
+ return do_with(func([] {}), [] (func&) {
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_6) {
+ const int x = 42;
+ return do_with(int(42), x, [](int&, int&) {
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_with_7) {
+ const int x = 42;
+ return do_with(x, [](int&) {
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_stopping_immediately) {
+ return do_with(int(0), [] (int& count) {
+ return repeat([&count] {
+ ++count;
+ return stop_iteration::yes;
+ }).then([&count] {
+ BOOST_REQUIRE(count == 1);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_stopping_after_two_iterations) {
+ return do_with(int(0), [] (int& count) {
+ return repeat([&count] {
+ ++count;
+ return count == 2 ? stop_iteration::yes : stop_iteration::no;
+ }).then([&count] {
+ BOOST_REQUIRE(count == 2);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_failing_in_the_first_step) {
+ return repeat([] {
+ throw expected_exception();
+ return stop_iteration::no;
+ }).then_wrapped([](auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not happen");
+ } catch (const expected_exception&) {
+ // expected
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_do_while_failing_in_the_second_step) {
+ return do_with(int(0), [] (int& count) {
+ return repeat([&count] {
+ ++count;
+ if (count > 1) {
+ throw expected_exception();
+ }
+ return later().then([] { return stop_iteration::no; });
+ }).then_wrapped([&count](auto&& f) {
+ try {
+ f.get();
+ BOOST_FAIL("should not happen");
+ } catch (const expected_exception&) {
+ BOOST_REQUIRE(count == 2);
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_parallel_for_each) {
+ return async([] {
+ // empty
+ parallel_for_each(std::vector<int>(), [] (int) -> future<> {
+ BOOST_FAIL("should not reach");
+ abort();
+ }).get();
+
+ // immediate result
+ auto range = boost::copy_range<std::vector<int>>(boost::irange(1, 6));
+ auto sum = 0;
+ parallel_for_each(range, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 15);
+
+ // all suspend
+ sum = 0;
+ parallel_for_each(range, [&sum] (int v) {
+ return later().then([&sum, v] {
+ sum += v;
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 15);
+
+ // throws immediately
+ BOOST_CHECK_EXCEPTION(parallel_for_each(range, [] (int) -> future<> {
+ throw 5;
+ }).get(), int, [] (int v) { return v == 5; });
+
+ // throws after suspension
+ BOOST_CHECK_EXCEPTION(parallel_for_each(range, [] (int) {
+ return later().then([] {
+ throw 5;
+ });
+ }).get(), int, [] (int v) { return v == 5; });
+ });
+}
+
+SEASTAR_TEST_CASE(test_parallel_for_each_early_failure) {
+ return do_with(0, [] (int& counter) {
+ return parallel_for_each(boost::irange(0, 11000), [&counter] (int i) {
+ using namespace std::chrono_literals;
+ // force scheduling
+ return sleep((i % 31 + 1) * 1ms).then([&counter, i] {
+ ++counter;
+ if (i % 1777 == 1337) {
+ return make_exception_future<>(i);
+ }
+ return make_ready_future<>();
+ });
+ }).then_wrapped([&counter] (future<> f) {
+ BOOST_REQUIRE_EQUAL(counter, 11000);
+ BOOST_REQUIRE(f.failed());
+ try {
+ f.get();
+ BOOST_FAIL("wanted an exception");
+ } catch (int i) {
+ BOOST_REQUIRE(i % 1777 == 1337);
+ } catch (...) {
+ BOOST_FAIL("bad exception type");
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_parallel_for_each_waits_for_all_fibers_even_if_one_of_them_failed) {
+ auto can_exit = make_lw_shared<bool>(false);
+ return parallel_for_each(boost::irange(0, 2), [can_exit] (int i) {
+ return later().then([i, can_exit] {
+ if (i == 1) {
+ throw expected_exception();
+ } else {
+ using namespace std::chrono_literals;
+ return sleep(300ms).then([can_exit] {
+ *can_exit = true;
+ });
+ }
+ });
+ }).then_wrapped([can_exit] (auto&& f) {
+ try {
+ f.get();
+ } catch (...) {
+ // expected
+ }
+ BOOST_REQUIRE(*can_exit);
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_parallel_for_each_broken_promise) {
+ auto fut = [] {
+ std::vector<promise<>> v(2);
+ return parallel_for_each(v, [] (promise<>& p) {
+ return p.get_future();
+ });
+ }();
+ BOOST_CHECK_THROW(fut.get(), broken_promise);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_repeat_broken_promise) {
+ auto get_fut = [] {
+ promise<stop_iteration> pr;
+ return pr.get_future();
+ };
+
+ future<> r = repeat([fut = get_fut()] () mutable {
+ return std::move(fut);
+ });
+
+ BOOST_CHECK_THROW(r.get(), broken_promise);
+}
+
+#ifndef SEASTAR_SHUFFLE_TASK_QUEUE
+SEASTAR_TEST_CASE(test_high_priority_task_runs_in_the_middle_of_loops) {
+ auto counter = make_lw_shared<int>(0);
+ auto flag = make_lw_shared<bool>(false);
+ return repeat([counter, flag] {
+ if (*counter == 1) {
+ BOOST_REQUIRE(*flag);
+ return stop_iteration::yes;
+ }
+ engine().add_high_priority_task(make_task([flag] {
+ *flag = true;
+ }));
+ ++(*counter);
+ return stop_iteration::no;
+ });
+}
+#endif
+
+SEASTAR_TEST_CASE(futurize_invoke_val_exception) {
+ return futurize_invoke([] (int arg) { throw expected_exception(); return arg; }, 1).then_wrapped([] (future<int> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) {}
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_val_ok) {
+ return futurize_invoke([] (int arg) { return arg * 2; }, 2).then_wrapped([] (future<int> f) {
+ try {
+ auto x = f.get0();
+ BOOST_REQUIRE_EQUAL(x, 4);
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_val_future_exception) {
+ return futurize_invoke([] (int a) {
+ return sleep(std::chrono::milliseconds(100)).then([] {
+ throw expected_exception();
+ return make_ready_future<int>(0);
+ });
+ }, 0).then_wrapped([] (future<int> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) { }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_val_future_ok) {
+ return futurize_invoke([] (int a) {
+ return sleep(std::chrono::milliseconds(100)).then([a] {
+ return make_ready_future<int>(a * 100);
+ });
+ }, 2).then_wrapped([] (future<int> f) {
+ try {
+ auto x = f.get0();
+ BOOST_REQUIRE_EQUAL(x, 200);
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+SEASTAR_TEST_CASE(futurize_invoke_void_exception) {
+ return futurize_invoke([] (auto arg) { throw expected_exception(); }, 0).then_wrapped([] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) {}
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_void_ok) {
+ return futurize_invoke([] (auto arg) { }, 0).then_wrapped([] (future<> f) {
+ try {
+ f.get();
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_void_future_exception) {
+ return futurize_invoke([] (auto a) {
+ return sleep(std::chrono::milliseconds(100)).then([] {
+ throw expected_exception();
+ });
+ }, 0).then_wrapped([] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("should have thrown");
+ } catch (expected_exception& e) { }
+ });
+}
+
+SEASTAR_TEST_CASE(futurize_invoke_void_future_ok) {
+ auto a = make_lw_shared<int>(1);
+ return futurize_invoke([] (int& a) {
+ return sleep(std::chrono::milliseconds(100)).then([&a] {
+ a *= 100;
+ });
+ }, *a).then_wrapped([a] (future<> f) {
+ try {
+ f.get();
+ BOOST_REQUIRE_EQUAL(*a, 100);
+ } catch (expected_exception& e) {
+ BOOST_FAIL("should not have thrown");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_unused_shared_future_is_not_a_broken_future) {
+ promise<> p;
+ shared_future<> s(p.get_future());
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_shared_future_propagates_value_to_all) {
+ return seastar::async([] {
+ promise<shared_ptr<int>> p; // shared_ptr<> to check it deals with emptyable types
+ shared_future<shared_ptr<int>> f(p.get_future());
+
+ auto f1 = f.get_future();
+ auto f2 = f.get_future();
+
+ p.set_value(make_shared<int>(1));
+ BOOST_REQUIRE(*f1.get0() == 1);
+ BOOST_REQUIRE(*f2.get0() == 1);
+ });
+}
+
+template<typename... T>
+void check_fails_with_expected(future<T...> f) {
+ try {
+ f.get();
+ BOOST_FAIL("Should have failed");
+ } catch (expected_exception&) {
+ // expected
+ }
+}
+
+SEASTAR_TEST_CASE(test_shared_future_propagates_value_to_copies) {
+ return seastar::async([] {
+ promise<int> p;
+ auto sf1 = shared_future<int>(p.get_future());
+ auto sf2 = sf1;
+
+ auto f1 = sf1.get_future();
+ auto f2 = sf2.get_future();
+
+ p.set_value(1);
+
+ BOOST_REQUIRE(f1.get0() == 1);
+ BOOST_REQUIRE(f2.get0() == 1);
+ });
+}
+
+SEASTAR_TEST_CASE(test_obtaining_future_from_shared_future_after_it_is_resolved) {
+ promise<int> p1;
+ promise<int> p2;
+ auto sf1 = shared_future<int>(p1.get_future());
+ auto sf2 = shared_future<int>(p2.get_future());
+ p1.set_value(1);
+ p2.set_exception(expected_exception());
+ return sf2.get_future().then_wrapped([f1 = sf1.get_future()] (auto&& f) mutable {
+ check_fails_with_expected(std::move(f));
+ return std::move(f1);
+ }).then_wrapped([] (auto&& f) {
+ BOOST_REQUIRE(f.get0() == 1);
+ });
+}
+
+SEASTAR_TEST_CASE(test_valueless_shared_future) {
+ return seastar::async([] {
+ promise<> p;
+ shared_future<> f(p.get_future());
+
+ auto f1 = f.get_future();
+ auto f2 = f.get_future();
+
+ p.set_value();
+
+ f1.get();
+ f2.get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_future_propagates_errors_to_all) {
+ promise<int> p;
+ shared_future<int> f(p.get_future());
+
+ auto f1 = f.get_future();
+ auto f2 = f.get_future();
+
+ p.set_exception(expected_exception());
+
+ return f1.then_wrapped([f2 = std::move(f2)] (auto&& f) mutable {
+ check_fails_with_expected(std::move(f));
+ return std::move(f2);
+ }).then_wrapped([] (auto&& f) mutable {
+ check_fails_with_expected(std::move(f));
+ });
+}
+
+SEASTAR_TEST_CASE(test_ignored_future_warning) {
+ // This doesn't warn:
+ promise<> p;
+ p.set_exception(expected_exception());
+ future<> f = p.get_future();
+ f.ignore_ready_future();
+
+ // And by analogy, neither should this
+ shared_promise<> p2;
+ p2.set_exception(expected_exception());
+ future<> f2 = p2.get_shared_future();
+ f2.ignore_ready_future();
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_futurize_from_tuple) {
+ std::tuple<int> v1 = std::make_tuple(3);
+ std::tuple<> v2 = {};
+ future<int> fut1 = futurize<int>::from_tuple(v1);
+ future<> fut2 = futurize<void>::from_tuple(v2);
+ BOOST_REQUIRE(fut1.get0() == std::get<0>(v1));
+#if SEASTAR_API_LEVEL < 5
+ BOOST_REQUIRE(fut2.get() == v2);
+#endif
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_repeat_until_value) {
+ return do_with(int(), [] (int& counter) {
+ return repeat_until_value([&counter] () -> future<std::optional<int>> {
+ if (counter == 10000) {
+ return make_ready_future<std::optional<int>>(counter);
+ } else {
+ ++counter;
+ return make_ready_future<std::optional<int>>(std::nullopt);
+ }
+ }).then([&counter] (int result) {
+ BOOST_REQUIRE(counter == 10000);
+ BOOST_REQUIRE(result == counter);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_repeat_until_value_implicit_future) {
+ // Same as above, but returning std::optional<int> instead of future<std::optional<int>>
+ return do_with(int(), [] (int& counter) {
+ return repeat_until_value([&counter] {
+ if (counter == 10000) {
+ return std::optional<int>(counter);
+ } else {
+ ++counter;
+ return std::optional<int>(std::nullopt);
+ }
+ }).then([&counter] (int result) {
+ BOOST_REQUIRE(counter == 10000);
+ BOOST_REQUIRE(result == counter);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_repeat_until_value_exception) {
+ return repeat_until_value([] {
+ throw expected_exception();
+ return std::optional<int>(43);
+ }).then_wrapped([] (future<int> f) {
+ check_fails_with_expected(std::move(f));
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_allx) {
+ return when_all(later(), later(), make_ready_future()).discard_result();
+}
+
+// A noncopyable and nonmovable struct
+struct non_copy_non_move {
+ non_copy_non_move() = default;
+ non_copy_non_move(non_copy_non_move&&) = delete;
+ non_copy_non_move(const non_copy_non_move&) = delete;
+};
+
+SEASTAR_TEST_CASE(test_when_all_functions) {
+ auto f = [x = non_copy_non_move()] {
+ (void)x;
+ return make_ready_future<int>(42);
+ };
+ return when_all(f, [] {
+ throw 42;
+ return make_ready_future<>();
+ }, later()).then([] (std::tuple<future<int>, future<>, future<>> res) {
+ BOOST_REQUIRE_EQUAL(std::get<0>(res).get0(), 42);
+
+ BOOST_REQUIRE(std::get<1>(res).available());
+ BOOST_REQUIRE(std::get<1>(res).failed());
+ std::get<1>(res).ignore_ready_future();
+
+ BOOST_REQUIRE(std::get<2>(res).available());
+ BOOST_REQUIRE(!std::get<2>(res).failed());
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_all_succeed_functions) {
+ auto f = [x = non_copy_non_move()] {
+ (void)x;
+ return make_ready_future<int>(42);
+ };
+ return when_all_succeed(f, [] {
+ throw 42;
+ return make_ready_future<>();
+ }, later()).then_wrapped([] (auto res) { // type of `res` changes when SESTAR_API_LEVEL < 3
+ BOOST_REQUIRE(res.available());
+ BOOST_REQUIRE(res.failed());
+ res.ignore_ready_future();
+ return make_ready_future<>();
+ });
+}
+
+template<typename E, typename... T>
+static void check_failed_with(future<T...>&& f) {
+ BOOST_REQUIRE(f.failed());
+ try {
+ f.get();
+ BOOST_FAIL("exception expected");
+ } catch (const E& e) {
+ // expected
+ } catch (...) {
+ BOOST_FAIL(format("wrong exception: {}", std::current_exception()));
+ }
+}
+
+template<typename... T>
+static void check_timed_out(future<T...>&& f) {
+ check_failed_with<timed_out_error>(std::move(f));
+}
+
+SEASTAR_TEST_CASE(test_with_timeout_when_it_times_out) {
+ return seastar::async([] {
+ promise<> pr;
+ auto f = with_timeout(manual_clock::now() + 2s, pr.get_future());
+
+ BOOST_REQUIRE(!f.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ BOOST_REQUIRE(!f.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_timed_out(std::move(f));
+
+ pr.set_value();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_future_get_future_after_timeout) {
+ // This used to crash because shared_future checked if the list of
+ // pending futures was empty to decide if it had already called
+ // then_wrapped. If all pending futures timed out, it would call
+ // it again.
+ promise<> pr;
+ shared_future<with_clock<manual_clock>> sfut(pr.get_future());
+ future<> fut1 = sfut.get_future(manual_clock::now() + 1s);
+
+ manual_clock::advance(1s);
+
+ check_timed_out(std::move(fut1));
+
+ future<> fut2 = sfut.get_future(manual_clock::now() + 1s);
+ manual_clock::advance(1s);
+ check_timed_out(std::move(fut2));
+
+ future<> fut3 = sfut.get_future(manual_clock::now() + 1s);
+ pr.set_value();
+ fut3.get();
+}
+
+SEASTAR_TEST_CASE(test_custom_exception_factory_in_with_timeout) {
+ return seastar::async([] {
+ class custom_error : public std::exception {
+ public:
+ virtual const char* what() const noexcept {
+ return "timedout";
+ }
+ };
+ struct my_exception_factory {
+ static auto timeout() {
+ return custom_error();
+ }
+ };
+ promise<> pr;
+ auto f = with_timeout<my_exception_factory>(manual_clock::now() + 1s, pr.get_future());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_failed_with<custom_error>(std::move(f));
+ });
+}
+
+SEASTAR_TEST_CASE(test_with_timeout_when_it_does_not_time_out) {
+ return seastar::async([] {
+ {
+ promise<int> pr;
+ auto f = with_timeout(manual_clock::now() + 1s, pr.get_future());
+
+ pr.set_value(42);
+
+ BOOST_REQUIRE_EQUAL(f.get0(), 42);
+ }
+
+ // Check that timer was indeed cancelled
+ manual_clock::advance(1s);
+ later().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_future_with_timeout) {
+ return seastar::async([] {
+ shared_promise<with_clock<manual_clock>, int> pr;
+ auto f1 = pr.get_shared_future(manual_clock::now() + 1s);
+ auto f2 = pr.get_shared_future(manual_clock::now() + 2s);
+ auto f3 = pr.get_shared_future();
+
+ BOOST_REQUIRE(!f1.available());
+ BOOST_REQUIRE(!f2.available());
+ BOOST_REQUIRE(!f3.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_timed_out(std::move(f1));
+ BOOST_REQUIRE(!f2.available());
+ BOOST_REQUIRE(!f3.available());
+
+ manual_clock::advance(1s);
+ later().get();
+
+ check_timed_out(std::move(f2));
+ BOOST_REQUIRE(!f3.available());
+
+ pr.set_value(42);
+
+ BOOST_REQUIRE_EQUAL(42, f3.get0());
+ });
+}
+
+#if SEASTAR_API_LEVEL < 4
+#define THEN_UNPACK then
+#else
+#define THEN_UNPACK then_unpack
+#endif
+
+SEASTAR_TEST_CASE(test_when_all_succeed_tuples) {
+ return seastar::when_all_succeed(
+ make_ready_future<>(),
+ make_ready_future<sstring>("hello world"),
+ make_ready_future<int>(42),
+ make_ready_future<>(),
+ make_ready_future<std::tuple<int, sstring>>(std::tuple(84, "hi")),
+ make_ready_future<bool>(true)
+ ).THEN_UNPACK([] (sstring msg, int v, std::tuple<int, sstring> t, bool b) {
+ BOOST_REQUIRE_EQUAL(msg, "hello world");
+ BOOST_REQUIRE_EQUAL(v, 42);
+ BOOST_REQUIRE_EQUAL(std::get<0>(t), 84);
+ BOOST_REQUIRE_EQUAL(std::get<1>(t), "hi");
+ BOOST_REQUIRE_EQUAL(b, true);
+
+ return seastar::when_all_succeed(
+ make_exception_future<>(42),
+ make_ready_future<sstring>("hello world"),
+ make_exception_future<int>(43),
+ make_ready_future<>()
+ ).THEN_UNPACK([] (sstring, int) {
+ BOOST_FAIL("shouldn't reach");
+ return false;
+ }).handle_exception([] (auto excp) {
+ try {
+ std::rethrow_exception(excp);
+ } catch (int v) {
+ BOOST_REQUIRE(v == 42 || v == 43);
+ return true;
+ } catch (...) { }
+ return false;
+ }).then([] (auto ret) {
+ BOOST_REQUIRE(ret);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_when_all_succeed_vector) {
+ std::vector<future<>> vecs;
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ return seastar::when_all_succeed(vecs.begin(), vecs.end()).then([] {
+ std::vector<future<>> vecs;
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_ready_future<>());
+ vecs.emplace_back(make_exception_future<>(42));
+ vecs.emplace_back(make_exception_future<>(43));
+ return seastar::when_all_succeed(vecs.begin(), vecs.end());
+ }).then([] {
+ BOOST_FAIL("shouldn't reach");
+ return false;
+ }).handle_exception([] (auto excp) {
+ try {
+ std::rethrow_exception(excp);
+ } catch (int v) {
+ BOOST_REQUIRE(v == 42 || v == 43);
+ return true;
+ } catch (...) { }
+ return false;
+ }).then([] (auto ret) {
+ BOOST_REQUIRE(ret);
+
+ std::vector<future<int>> vecs;
+ vecs.emplace_back(make_ready_future<int>(1));
+ vecs.emplace_back(make_ready_future<int>(2));
+ vecs.emplace_back(make_ready_future<int>(3));
+ return seastar::when_all_succeed(vecs.begin(), vecs.end());
+ }).then([] (std::vector<int> vals) {
+ BOOST_REQUIRE_EQUAL(vals.size(), 3u);
+ BOOST_REQUIRE_EQUAL(vals[0], 1);
+ BOOST_REQUIRE_EQUAL(vals[1], 2);
+ BOOST_REQUIRE_EQUAL(vals[2], 3);
+
+ std::vector<future<int>> vecs;
+ vecs.emplace_back(make_ready_future<int>(1));
+ vecs.emplace_back(make_ready_future<int>(2));
+ vecs.emplace_back(make_exception_future<int>(42));
+ vecs.emplace_back(make_exception_future<int>(43));
+ return seastar::when_all_succeed(vecs.begin(), vecs.end());
+ }).then([] (std::vector<int>) {
+ BOOST_FAIL("shouldn't reach");
+ return false;
+ }).handle_exception([] (auto excp) {
+ try {
+ std::rethrow_exception(excp);
+ } catch (int v) {
+ BOOST_REQUIRE(v == 42 || v == 43);
+ return true;
+ } catch (...) { }
+ return false;
+ }).then([] (auto ret) {
+ BOOST_REQUIRE(ret);
+ });
+}
+
+SEASTAR_TEST_CASE(test_futurize_mutable) {
+ int count = 0;
+ return seastar::repeat([count]() mutable {
+ ++count;
+ if (count == 3) {
+ return seastar::stop_iteration::yes;
+ }
+ return seastar::stop_iteration::no;
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_broken_promises) {
+ std::optional<future<>> f;
+ std::optional<future<>> f2;
+ { // Broken after attaching a continuation
+ auto p = promise<>();
+ f = p.get_future();
+ f2 = f->then_wrapped([&] (future<> f3) {
+ BOOST_CHECK(f3.failed());
+ BOOST_CHECK_THROW(f3.get(), broken_promise);
+ f = { };
+ });
+ }
+ f2->get();
+ BOOST_CHECK(!f);
+
+ { // Broken before attaching a continuation
+ auto p = promise<>();
+ f = p.get_future();
+ }
+ f->then_wrapped([&] (future<> f3) {
+ BOOST_CHECK(f3.failed());
+ BOOST_CHECK_THROW(f3.get(), broken_promise);
+ f = { };
+ }).get();
+ BOOST_CHECK(!f);
+
+ { // Broken before suspending a thread
+ auto p = promise<>();
+ f = p.get_future();
+ }
+ BOOST_CHECK_THROW(f->get(), broken_promise);
+}
+
+SEASTAR_TEST_CASE(test_warn_on_broken_promise_with_no_future) {
+ // Example code where we expect a "Exceptional future ignored"
+ // warning.
+ promise<> p;
+ // Intentionally destroy the future
+ (void)p.get_future();
+
+ with_allow_abandoned_failed_futures(1, [&] {
+ p.set_exception(std::runtime_error("foo"));
+ });
+
+ return make_ready_future<>();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_exception_future_with_backtrace) {
+ int counter = 0;
+ auto inner = [&] (bool return_exception) mutable {
+ if (!return_exception) {
+ return make_ready_future<int>(++counter);
+ } else {
+ return make_exception_future_with_backtrace<int>(expected_exception());
+ }
+ };
+ auto outer = [&] (bool return_exception) {
+ return inner(return_exception).then([] (int i) {
+ return make_ready_future<int>(-i);
+ });
+ };
+
+ BOOST_REQUIRE_EQUAL(outer(false).get0(), -1);
+ BOOST_REQUIRE_EQUAL(counter, 1);
+
+ BOOST_CHECK_THROW(outer(true).get0(), expected_exception);
+ BOOST_REQUIRE_EQUAL(counter, 1);
+
+ // Example code where we expect a "Exceptional future ignored"
+ // warning.
+ (void)outer(true).then_wrapped([](future<int> fut) {
+ with_allow_abandoned_failed_futures(1, [fut = std::move(fut)]() mutable {
+ auto foo = std::move(fut);
+ });
+ });
+}
+
+class throw_on_move {
+ int _i;
+public:
+ throw_on_move(int i = 0) noexcept {
+ _i = i;
+ }
+ throw_on_move(const throw_on_move&) = delete;
+ throw_on_move(throw_on_move&&) {
+ _i = -1;
+ throw expected_exception();
+ }
+
+ int value() const {
+ return _i;
+ }
+};
+
+SEASTAR_TEST_CASE(test_async_throw_on_move) {
+ return async([] (throw_on_move t) {
+ BOOST_CHECK(false);
+ }, throw_on_move()).handle_exception_type([] (const expected_exception&) {
+ return make_ready_future<>();
+ });
+}
+
+future<> func4() {
+ return later().then([] {
+ seastar_logger.info("backtrace: {}", current_backtrace());
+ });
+}
+
+void func3() {
+ seastar::async([] {
+ func4().get();
+ }).get();
+}
+
+future<> func2() {
+ return seastar::async([] {
+ func3();
+ });
+}
+
+future<> func1() {
+ return later().then([] {
+ return func2();
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_backtracing) {
+ func1().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_then_unpack) {
+ make_ready_future<std::tuple<>>().then_unpack([] () {
+ BOOST_REQUIRE(true);
+ }).get();
+ make_ready_future<std::tuple<int>>(std::tuple<int>(1)).then_unpack([] (int x) {
+ BOOST_REQUIRE(x == 1);
+ }).get();
+ make_ready_future<std::tuple<int, long>>(std::tuple<int, long>(1, 2)).then_unpack([] (int x, long y) {
+ BOOST_REQUIRE(x == 1 && y == 2);
+ }).get();
+ make_ready_future<std::tuple<std::unique_ptr<int>>>(std::tuple(std::make_unique<int>(42))).then_unpack([] (std::unique_ptr<int> p1) {
+ BOOST_REQUIRE(*p1 == 42);
+ }).get();
+}
+
+future<> test_then_function_f() {
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_then_function) {
+ return make_ready_future<>().then(test_then_function_f);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_with_gate) {
+ gate g;
+ int counter = 0;
+ int gate_closed_errors = 0;
+ int other_errors = 0;
+
+ // test normal operation when gate is opened
+ BOOST_CHECK_NO_THROW(with_gate(g, [&] { counter++; }).get());
+ BOOST_REQUIRE_EQUAL(counter, 1);
+
+ // test that an exception returned by the calling func
+ // is propagated to with_gate future
+ counter = gate_closed_errors = other_errors = 0;
+ BOOST_CHECK_NO_THROW(with_gate(g, [&] {
+ counter++;
+ return make_exception_future<>(expected_exception());
+ }).handle_exception_type([&] (gate_closed_exception& e) {
+ gate_closed_errors++;
+ }).handle_exception([&] (std::exception_ptr) {
+ other_errors++;
+ }).get());
+ BOOST_REQUIRE(counter);
+ BOOST_REQUIRE(!gate_closed_errors);
+ BOOST_REQUIRE(other_errors);
+
+ g.close().get();
+
+ // test that with_gate.get() throws when the gate is closed
+ counter = gate_closed_errors = other_errors = 0;
+ BOOST_CHECK_THROW(with_gate(g, [&] { counter++; }).get(), gate_closed_exception);
+ BOOST_REQUIRE(!counter);
+
+ // test that with_gate throws when the gate is closed
+ counter = gate_closed_errors = other_errors = 0;
+ BOOST_CHECK_THROW(with_gate(g, [&] {
+ counter++;
+ }).then_wrapped([&] (future<> f) {
+ auto eptr = f.get_exception();
+ try {
+ std::rethrow_exception(eptr);
+ } catch (gate_closed_exception& e) {
+ gate_closed_errors++;
+ } catch (...) {
+ other_errors++;
+ }
+ }).get(), gate_closed_exception);
+ BOOST_REQUIRE(!counter);
+ BOOST_REQUIRE(!gate_closed_errors);
+ BOOST_REQUIRE(!other_errors);
+
+ // test that try_with_gate returns gate_closed_exception when the gate is closed
+ counter = gate_closed_errors = other_errors = 0;
+ try_with_gate(g, [&] { counter++; }).handle_exception_type([&] (gate_closed_exception& e) {
+ gate_closed_errors++;
+ }).handle_exception([&] (std::exception_ptr) {
+ other_errors++;
+ }).get();
+ BOOST_REQUIRE(!counter);
+ BOOST_REQUIRE(gate_closed_errors);
+ BOOST_REQUIRE(!other_errors);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_max_concurrent_for_each) {
+ BOOST_TEST_MESSAGE("empty range");
+ max_concurrent_for_each(std::vector<int>(), 3, [] (int) {
+ BOOST_FAIL("should not reach");
+ return make_exception_future<>(std::bad_function_call());
+ }).get();
+
+ auto range = boost::copy_range<std::vector<int>>(boost::irange(1, 8));
+
+ BOOST_TEST_MESSAGE("iterator");
+ auto sum = 0;
+ max_concurrent_for_each(range.begin(), range.end(), 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("const iterator");
+ sum = 0;
+ max_concurrent_for_each(range.cbegin(), range.cend(), 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("reverse iterator");
+ sum = 0;
+ max_concurrent_for_each(range.rbegin(), range.rend(), 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("immediate result");
+ sum = 0;
+ max_concurrent_for_each(range, 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("suspend");
+ sum = 0;
+ max_concurrent_for_each(range, 3, [&sum] (int v) {
+ return later().then([&sum, v] {
+ sum += v;
+ });
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("throw immediately");
+ sum = 0;
+ BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3, [&sum] (int v) {
+ sum += v;
+ if (v == 1) {
+ throw 5;
+ }
+ return make_ready_future<>();
+ }).get(), int, [] (int v) { return v == 5; });
+ BOOST_REQUIRE_EQUAL(sum, 28);
+
+ BOOST_TEST_MESSAGE("throw after suspension");
+ sum = 0;
+ BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3, [&sum] (int v) {
+ return later().then([&sum, v] {
+ sum += v;
+ if (v == 2) {
+ throw 5;
+ }
+ });
+ }).get(), int, [] (int v) { return v == 5; });
+
+ BOOST_TEST_MESSAGE("concurrency higher than vector length");
+ sum = 0;
+ max_concurrent_for_each(range, range.size() + 3, [&sum] (int v) {
+ sum += v;
+ return make_ready_future<>();
+ }).get();
+ BOOST_REQUIRE_EQUAL(sum, 28);
+}
diff --git a/src/seastar/tests/unit/httpd_test.cc b/src/seastar/tests/unit/httpd_test.cc
new file mode 100644
index 000000000..2428f038d
--- /dev/null
+++ b/src/seastar/tests/unit/httpd_test.cc
@@ -0,0 +1,889 @@
+/*
+ * Copyright 2015 Cloudius Systems
+ */
+
+#include <seastar/http/httpd.hh>
+#include <seastar/http/handlers.hh>
+#include <seastar/http/matcher.hh>
+#include <seastar/http/matchrules.hh>
+#include <seastar/json/formatter.hh>
+#include <seastar/http/routes.hh>
+#include <seastar/http/exception.hh>
+#include <seastar/http/transformers.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/when_all.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include "loopback_socket.hh"
+#include <boost/algorithm/string.hpp>
+#include <seastar/core/thread.hh>
+#include <seastar/util/noncopyable_function.hh>
+#include <seastar/http/json_path.hh>
+#include <sstream>
+
+using namespace seastar;
+using namespace httpd;
+
+class handl : public httpd::handler_base {
+public:
+ virtual future<std::unique_ptr<reply> > handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
+ rep->done("html");
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+};
+
+SEASTAR_TEST_CASE(test_reply)
+{
+ reply r;
+ r.set_content_type("txt");
+ BOOST_REQUIRE_EQUAL(r._headers["Content-Type"], sstring("text/plain"));
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_str_matcher)
+{
+
+ str_matcher m("/hello");
+ parameters param;
+ BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_param_matcher)
+{
+
+ param_matcher m("param");
+ parameters param;
+ BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
+ BOOST_REQUIRE_EQUAL(param.path("param"), "/hello");
+ BOOST_REQUIRE_EQUAL(param["param"], "hello");
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_match_rule)
+{
+
+ parameters param;
+ handl* h = new handl();
+ match_rule mr(h);
+ mr.add_str("/hello").add_param("param");
+ httpd::handler_base* res = mr.get("/hello/val1", param);
+ BOOST_REQUIRE_EQUAL(res, h);
+ BOOST_REQUIRE_EQUAL(param["param"], "val1");
+ res = mr.get("/hell/val1", param);
+ httpd::handler_base* nl = nullptr;
+ BOOST_REQUIRE_EQUAL(res, nl);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_match_rule_order)
+{
+ parameters param;
+ routes route;
+
+ handl* h1 = new handl();
+ route.add(operation_type::GET, url("/hello"), h1);
+
+ handl* h2 = new handl();
+ route.add(operation_type::GET, url("/hello"), h2);
+
+ auto rh = route.get_handler(GET, "/hello", param);
+ BOOST_REQUIRE_EQUAL(rh, h1);
+
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_put_drop_rule)
+{
+ routes rts;
+ auto h = std::make_unique<handl>();
+ parameters params;
+
+ {
+ auto reg = handler_registration(rts, *h, "/hello", operation_type::GET);
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ BOOST_REQUIRE_EQUAL(res, h.get());
+ }
+
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ httpd::handler_base* nl = nullptr;
+ BOOST_REQUIRE_EQUAL(res, nl);
+ return make_ready_future<>();
+}
+
+// Putting a duplicated exact rule would result
+// in a memory leak due to the fact that rules are implemented
+// as raw pointers. In order to prevent such leaks,
+// an exception is thrown if somebody tries to put
+// a duplicated rule without removing the old one first.
+// The interface demands that the callee allocates the handle,
+// so it should also expect the callee to free it before
+// overwriting.
+SEASTAR_TEST_CASE(test_duplicated_exact_rule)
+{
+ parameters param;
+ routes route;
+
+ handl* h1 = new handl;
+ route.put(operation_type::GET, "/hello", h1);
+
+ handl* h2 = new handl;
+ BOOST_REQUIRE_THROW(route.put(operation_type::GET, "/hello", h2), std::runtime_error);
+
+ delete route.drop(operation_type::GET, "/hello");
+ route.put(operation_type::GET, "/hello", h2);
+
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_add_del_cookie)
+{
+ routes rts;
+ handl* h = new handl();
+ match_rule mr(h);
+ mr.add_str("/hello");
+ parameters params;
+
+ {
+ auto reg = rule_registration(rts, mr, operation_type::GET);
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ BOOST_REQUIRE_EQUAL(res, h);
+ }
+
+ auto res = rts.get_handler(operation_type::GET, "/hello", params);
+ httpd::handler_base* nl = nullptr;
+ BOOST_REQUIRE_EQUAL(res, nl);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_formatter)
+{
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(true), "true");
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(false), "false");
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(1), "1");
+ const char* txt = "efg";
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(txt), "\"efg\"");
+ sstring str = "abc";
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(str), "\"abc\"");
+ float f = 1;
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(f), "1");
+ f = 1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
+ f = -1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
+ f = 0.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(f), std::invalid_argument);
+ double d = -1;
+ BOOST_REQUIRE_EQUAL(json::formatter::to_json(d), "-1");
+ d = 1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
+ d = -1.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
+ d = 0.0/0.0;
+ BOOST_CHECK_THROW(json::formatter::to_json(d), std::invalid_argument);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_decode_url) {
+ request req;
+ req._url = "/a?q=%23%24%23";
+ sstring url = http_server::connection::set_query_param(req);
+ BOOST_REQUIRE_EQUAL(url, "/a");
+ BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#");
+ req._url = "/a?a=%23%24%23&b=%22%26%22";
+ http_server::connection::set_query_param(req);
+ BOOST_REQUIRE_EQUAL(req.get_query_param("a"), "#$#");
+ BOOST_REQUIRE_EQUAL(req.get_query_param("b"), "\"&\"");
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_routes) {
+ handl* h1 = new handl();
+ handl* h2 = new handl();
+ routes route;
+ route.add(operation_type::GET, url("/api").remainder("path"), h1);
+ route.add(operation_type::GET, url("/"), h2);
+ std::unique_ptr<request> req = std::make_unique<request>();
+ std::unique_ptr<reply> rep = std::make_unique<reply>();
+
+ auto f1 =
+ route.handle("/api", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
+ });
+ req.reset(new request);
+ rep.reset(new reply);
+
+ auto f2 =
+ route.handle("/", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
+ });
+ req.reset(new request);
+ rep.reset(new reply);
+ auto f3 =
+ route.handle("/api/abc", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ });
+ req.reset(new request);
+ rep.reset(new reply);
+ auto f4 =
+ route.handle("/ap", std::move(req), std::move(rep)).then(
+ [] (std::unique_ptr<reply> rep) {
+ BOOST_REQUIRE_EQUAL((int )rep->_status,
+ (int )reply::status_type::not_found);
+ });
+ return when_all(std::move(f1), std::move(f2), std::move(f3), std::move(f4))
+ .then([] (std::tuple<future<>, future<>, future<>, future<>> fs) {
+ std::get<0>(fs).get();
+ std::get<1>(fs).get();
+ std::get<2>(fs).get();
+ std::get<3>(fs).get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_json_path) {
+ shared_ptr<bool> res1 = make_shared<bool>(false);
+ shared_ptr<bool> res2 = make_shared<bool>(false);
+ shared_ptr<bool> res3 = make_shared<bool>(false);
+ shared_ptr<routes> route = make_shared<routes>();
+ path_description path1("/my/path",GET,"path1",
+ {{"param1", path_description::url_component_type::PARAM}
+ ,{"/text", path_description::url_component_type::FIXED_STRING}},{});
+ path_description path2("/my/path",GET,"path2",
+ {{"param1", path_description::url_component_type::PARAM}
+ ,{"param2", path_description::url_component_type::PARAM}},{});
+ path_description path3("/my/path",GET,"path3",
+ {{"param1", path_description::url_component_type::PARAM}
+ ,{"param2", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH}},{});
+
+ path1.set(*route, [res1] (const_req req) {
+ (*res1) = true;
+ BOOST_REQUIRE_EQUAL(req.param["param1"], "value1");
+ return "";
+ });
+
+ path2.set(*route, [res2] (const_req req) {
+ (*res2) = true;
+ BOOST_REQUIRE_EQUAL(req.param["param1"], "value2");
+ BOOST_REQUIRE_EQUAL(req.param["param2"], "text1");
+ return "";
+ });
+
+ path3.set(*route, [res3] (const_req req) {
+ (*res3) = true;
+ BOOST_REQUIRE_EQUAL(req.param["param1"], "value3");
+ BOOST_REQUIRE_EQUAL(req.param["param2"], "text2/text3");
+ return "";
+ });
+
+ auto f1 = route->handle("/my/path/value1/text", std::make_unique<request>(), std::make_unique<reply>()).then([res1, route] (auto f) {
+ BOOST_REQUIRE_EQUAL(*res1, true);
+ });
+
+ auto f2 = route->handle("/my/path/value2/text1", std::make_unique<request>(), std::make_unique<reply>()).then([res2, route] (auto f) {
+ BOOST_REQUIRE_EQUAL(*res2, true);
+ });
+
+ auto f3 = route->handle("/my/path/value3/text2/text3", std::make_unique<request>(), std::make_unique<reply>()).then([res3, route] (auto f) {
+ BOOST_REQUIRE_EQUAL(*res3, true);
+ });
+
+ return when_all(std::move(f1), std::move(f2), std::move(f3))
+ .then([] (std::tuple<future<>, future<>, future<>> fs) {
+ std::get<0>(fs).get();
+ std::get<1>(fs).get();
+ std::get<2>(fs).get();
+ });
+}
+
+/*!
+ * \brief a helper data sink that stores everything it gets in a stringstream
+ */
+class memory_data_sink_impl : public data_sink_impl {
+ std::stringstream& _ss;
+public:
+ memory_data_sink_impl(std::stringstream& ss) : _ss(ss) {
+ }
+ virtual future<> put(net::packet data) override {
+ abort();
+ return make_ready_future<>();
+ }
+ virtual future<> put(temporary_buffer<char> buf) override {
+ _ss.write(buf.get(), buf.size());
+ return make_ready_future<>();
+ }
+ virtual future<> flush() override {
+ return make_ready_future<>();
+ }
+
+ virtual future<> close() override {
+ return make_ready_future<>();
+ }
+};
+
+class memory_data_sink : public data_sink {
+public:
+ memory_data_sink(std::stringstream& ss)
+ : data_sink(std::make_unique<memory_data_sink_impl>(ss)) {}
+};
+
+future<> test_transformer_stream(std::stringstream& ss, content_replace& cr, std::vector<sstring>&& buffer_parts) {
+ std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
+ ss.str("");
+ req->_headers["Host"] = "localhost";
+ return do_with(output_stream<char>(cr.transform(std::move(req), "json", output_stream<char>(memory_data_sink(ss), 32000, true))),
+ std::vector<sstring>(std::move(buffer_parts)), [] (output_stream<char>& os, std::vector<sstring>& parts) {
+ return do_for_each(parts, [&os](auto& p) {
+ return os.write(p);
+ }).then([&os] {
+ return os.close();
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_transformer) {
+ return do_with(std::stringstream(), content_replace("json"), [] (std::stringstream& ss, content_replace& cr) {
+ return do_with(output_stream<char>(cr.transform(std::make_unique<seastar::httpd::request>(), "html", output_stream<char>(memory_data_sink(ss), 32000, true))),
+ [] (output_stream<char>& os) {
+ return os.write(sstring("hello-{{Protocol}}-xyz-{{Host}}")).then([&os] {
+ return os.close();
+ });
+ }).then([&ss, &cr] () {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Protocol}}-xyz-{{Host}}");
+ return test_transformer_stream(ss, cr, {"hell", "o-{", "{Pro", "tocol}}-xyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-http-xyz-localhost{{Pr");
+ return test_transformer_stream(ss, cr, {"hell", "o-{{", "Pro", "tocol}}{{Protocol}}-{{Protoxyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-httphttp-{{Protoxyz-localhost{{Pr");
+ return test_transformer_stream(ss, cr, {"hell", "o-{{Pro", "t{{Protocol}}ocol}}", "{{Host}}"}).then([&ss] {
+ BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Prothttpocol}}localhost");
+ });
+ });
+ });
+ });
+ });
+}
+
+struct http_consumer {
+ std::map<sstring, std::string> _headers;
+ std::string _body;
+ uint32_t _remain = 0;
+ std::string _current;
+ char last = '\0';
+ uint32_t _size = 0;
+ bool _concat = true;
+
+ enum class status_type {
+ READING_HEADERS,
+ CHUNK_SIZE,
+ CHUNK_BODY,
+ CHUNK_END,
+ READING_BODY_BY_SIZE,
+ DONE
+ };
+ status_type status = status_type::READING_HEADERS;
+
+ bool read(const temporary_buffer<char>& b) {
+ for (auto c : b) {
+ if (last =='\r' && c == '\n') {
+ if (_current == "") {
+ if (status == status_type::READING_HEADERS || (status == status_type::CHUNK_BODY && _remain == 0)) {
+ if (status == status_type::READING_HEADERS && _headers.find("Content-Length") != _headers.end()) {
+ _remain = stoi(_headers["Content-Length"], nullptr, 16);
+ if (_remain == 0) {
+ status = status_type::DONE;
+ break;
+ }
+ status = status_type::READING_BODY_BY_SIZE;
+ } else {
+ status = status_type::CHUNK_SIZE;
+ }
+ } else if (status == status_type::CHUNK_END) {
+ status = status_type::DONE;
+ break;
+ }
+ } else {
+ switch (status) {
+ case status_type::READING_HEADERS: add_header(_current);
+ break;
+ case status_type::CHUNK_SIZE: set_chunk(_current);
+ break;
+ default:
+ break;
+ }
+ _current = "";
+ }
+ last = '\0';
+ } else {
+ if (last != '\0') {
+ if (status == status_type::CHUNK_BODY || status == status_type::READING_BODY_BY_SIZE) {
+ if (_concat) {
+ _body = _body + last;
+ }
+ _size++;
+ _remain--;
+ if (_remain <= 1 && status == status_type::READING_BODY_BY_SIZE) {
+ if (_concat) {
+ _body = _body + c;
+ }
+ _size++;
+ status = status_type::DONE;
+ break;
+ }
+ } else {
+ _current = _current + last;
+ }
+
+ }
+ last = c;
+ }
+ }
+ return status == status_type::DONE;
+ }
+
+ void set_chunk(const std::string& s) {
+ _remain = stoi(s, nullptr, 16);
+ if (_remain == 0) {
+ status = status_type::CHUNK_END;
+ } else {
+ status = status_type::CHUNK_BODY;
+ }
+ }
+
+ void add_header(const std::string& s) {
+ std::vector<std::string> strs;
+ boost::split(strs, s, boost::is_any_of(":"));
+ if (strs.size() > 1) {
+ _headers[strs[0]] = strs[1];
+ }
+ }
+};
+
+class test_client_server {
+public:
+ static future<> write_request(output_stream<char>& output) {
+ return output.write(sstring("GET /test HTTP/1.1\r\nHost: myhost.org\r\n\r\n")).then([&output]{
+ return output.flush();
+ });
+ }
+
+ static future<> run_test(std::function<future<>(output_stream<char> &&)>&& write_func, std::function<bool(size_t, http_consumer&)> reader) {
+ return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
+ [reader, &write_func] (loopback_connection_factory& lcf, auto& server) {
+ return do_with(loopback_socket_impl(lcf), [&server, &lcf, reader, &write_func](loopback_socket_impl& lsi) {
+ httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
+
+ auto client = seastar::async([&lsi, reader] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+ bool more = true;
+ size_t count = 0;
+ while (more) {
+ http_consumer htp;
+ htp._concat = false;
+
+ write_request(output).get();
+ repeat([&input, &htp] {
+ return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
+ return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
+ make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ }).get();
+ std::cout << htp._body << std::endl;
+ more = reader(count, htp);
+ count++;
+ }
+ if (input.eof()) {
+ input.close().get();
+ }
+ });
+
+ auto server_setup = seastar::async([&server, &write_func] {
+ class test_handler : public handler_base {
+ size_t count = 0;
+ http_server& _server;
+ std::function<future<>(output_stream<char> &&)> _write_func;
+ promise<> _all_message_sent;
+ public:
+ test_handler(http_server& server, std::function<future<>(output_stream<char> &&)>&& write_func) : _server(server), _write_func(write_func) {
+ }
+ future<std::unique_ptr<reply>> handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
+ rep->write_body("json", std::move(_write_func));
+ count++;
+ _all_message_sent.set_value();
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+ future<> wait_for_message() {
+ return _all_message_sent.get_future();
+ }
+ };
+ auto handler = new test_handler(*server, std::move(write_func));
+ server->_routes.put(GET, "/test", handler);
+ when_all(server->do_accepts(0), handler->wait_for_message()).get();
+ });
+ return when_all(std::move(client), std::move(server_setup));
+ }).discard_result().then_wrapped([&server] (auto f) {
+ f.ignore_ready_future();
+ return server->stop();
+ });
+ });
+ }
+ static future<> run(std::vector<std::tuple<bool, size_t>> tests) {
+ return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
+ [tests] (loopback_connection_factory& lcf, auto& server) {
+ return do_with(loopback_socket_impl(lcf), [&server, &lcf, tests](loopback_socket_impl& lsi) {
+ httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
+
+ auto client = seastar::async([&lsi, tests] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+ bool more = true;
+ size_t count = 0;
+ while (more) {
+ http_consumer htp;
+ write_request(output).get();
+ repeat([&input, &htp] {
+ return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
+ return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
+ make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ }).get();
+ if (std::get<bool>(tests[count])) {
+ BOOST_REQUIRE_EQUAL(htp._body.length(), std::get<size_t>(tests[count]));
+ } else {
+ BOOST_REQUIRE_EQUAL(input.eof(), true);
+ more = false;
+ }
+ count++;
+ if (count == tests.size()) {
+ more = false;
+ }
+ }
+ if (input.eof()) {
+ input.close().get();
+ }
+ });
+
+ auto server_setup = seastar::async([&server, tests] {
+ class test_handler : public handler_base {
+ size_t count = 0;
+ http_server& _server;
+ std::vector<std::tuple<bool, size_t>> _tests;
+ promise<> _all_message_sent;
+ public:
+ test_handler(http_server& server, const std::vector<std::tuple<bool, size_t>>& tests) : _server(server), _tests(tests) {
+ }
+ future<std::unique_ptr<reply>> handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
+ rep->write_body("txt", make_writer(std::get<size_t>(_tests[count]), std::get<bool>(_tests[count])));
+ count++;
+ if (count == _tests.size()) {
+ _all_message_sent.set_value();
+ }
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+ future<> wait_for_message() {
+ return _all_message_sent.get_future();
+ }
+ };
+ auto handler = new test_handler(*server, tests);
+ server->_routes.put(GET, "/test", handler);
+ when_all(server->do_accepts(0), handler->wait_for_message()).get();
+ });
+ return when_all(std::move(client), std::move(server_setup));
+ }).discard_result().then_wrapped([&server] (auto f) {
+ f.ignore_ready_future();
+ return server->stop();
+ });
+ });
+ }
+
+ static noncopyable_function<future<>(output_stream<char>&& o_stream)> make_writer(size_t len, bool success) {
+ return [len, success] (output_stream<char>&& o_stream) mutable {
+ return do_with(output_stream<char>(std::move(o_stream)), uint32_t(len/10), [success](output_stream<char>& str, uint32_t& remain) {
+ if (remain == 0) {
+ if (success) {
+ return str.close();
+ } else {
+ throw std::runtime_error("Throwing exception before writing");
+ }
+ }
+ return repeat([&str, &remain] () mutable {
+ return str.write("1234567890").then([&remain]() mutable {
+ remain--;
+ return (remain == 0)? make_ready_future<stop_iteration>(stop_iteration::yes) : make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ }).then([&str, success] {
+ if (!success) {
+ return str.flush();
+ }
+ return make_ready_future<>();
+ }).then([&str, success] {
+ if (success) {
+ return str.close();
+ } else {
+ throw std::runtime_error("Throwing exception after writing");
+ }
+ });
+ });
+ };
+ }
+};
+
+SEASTAR_TEST_CASE(test_message_with_error_non_empty_body) {
+ std::vector<std::tuple<bool, size_t>> tests = {
+ std::make_tuple(true, 100),
+ std::make_tuple(false, 10000)};
+ return test_client_server::run(tests);
+}
+
+SEASTAR_TEST_CASE(test_simple_chunked) {
+ std::vector<std::tuple<bool, size_t>> tests = {
+ std::make_tuple(true, 100000),
+ std::make_tuple(true, 100)};
+ return test_client_server::run(tests);
+}
+
+SEASTAR_TEST_CASE(test_http_client_server_full) {
+ std::vector<std::tuple<bool, size_t>> tests = {
+ std::make_tuple(true, 100),
+ std::make_tuple(true, 10000),
+ std::make_tuple(true, 100),
+ std::make_tuple(true, 0),
+ std::make_tuple(true, 5000),
+ std::make_tuple(true, 10000),
+ std::make_tuple(true, 9000),
+ std::make_tuple(true, 10000)};
+ return test_client_server::run(tests);
+}
+
+/*
+ * return string in the given size
+ * The string size takes the quotes into consideration.
+ */
+std::string get_value(int size) {
+ std::stringstream res;
+ for (auto i = 0; i < size - 2; i++) {
+ res << "a";
+ }
+ return res.str();
+}
+
+/*
+ * A helper object that map to a big json string
+ * in the format of:
+ * {"valu": "aaa....aa", "valu": "aaa....aa", "valu": "aaa....aa"...}
+ *
+ * The object can have an arbitrary size in multiplication of 10000 bytes
+ * */
+struct extra_big_object : public json::json_base {
+ json::json_element<sstring>* value;
+ extra_big_object(size_t size) {
+ value = new json::json_element<sstring>;
+ // size = brackets + (name + ": " + get_value) * n + ", " * (n-1)
+ // size = 2 + (name + 6 + get_value) * n - 2
+ value->_name = "valu";
+ *value = get_value(9990);
+ for (size_t i = 0; i < size/10000; i++) {
+ _elements.emplace_back(value);
+ }
+ }
+
+ virtual ~extra_big_object() {
+ delete value;
+ }
+
+ extra_big_object(const extra_big_object& o) {
+ value = new json::json_element<sstring>;
+ value->_name = o.value->_name;
+ *value = (*o.value)();
+ for (size_t i = 0; i < o._elements.size(); i++) {
+ _elements.emplace_back(value);
+ }
+ }
+};
+
+SEASTAR_TEST_CASE(json_stream) {
+ std::vector<extra_big_object> vec;
+ size_t num_objects = 1000;
+ size_t total_size = num_objects * 1000001 + 1;
+ for (size_t i = 0; i < num_objects; i++) {
+ vec.emplace_back(1000000);
+ }
+ return test_client_server::run_test(json::stream_object(vec), [total_size](size_t s, http_consumer& h) {
+ BOOST_REQUIRE_EQUAL(h._size, total_size);
+ return false;
+ });
+}
+
+class json_test_handler : public handler_base {
+ std::function<future<>(output_stream<char> &&)> _write_func;
+public:
+ json_test_handler(std::function<future<>(output_stream<char> &&)>&& write_func) : _write_func(write_func) {
+ }
+ future<std::unique_ptr<reply>> handle(const sstring& path,
+ std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
+ rep->write_body("json", _write_func);
+ return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
+ }
+};
+
+SEASTAR_TEST_CASE(content_length_limit) {
+ return seastar::async([] {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ server.set_content_length_limit(11);
+ loopback_socket_impl lsi(lcf);
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+
+ future<> client = seastar::async([&lsi] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 11\r\n\r\nxxxxxxxxxxx")).get();
+ output.flush().get();
+ resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\n\r\nxxxxxxxxxxxxxxxx")).get();
+ output.flush().get();
+ resp = input.read().get0();
+ BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
+
+ input.close().get();
+ output.close().get();
+ });
+
+ auto handler = new json_test_handler(json::stream_object("hello"));
+ server._routes.put(GET, "/test", handler);
+ server.do_accepts(0).get();
+
+ client.get();
+ server.stop().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_100_continue) {
+ return seastar::async([] {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ server.set_content_length_limit(11);
+ loopback_socket_impl lsi(lcf);
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+ future<> client = seastar::async([&lsi] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+
+ for (auto version : {sstring("1.0"), sstring("1.1")}) {
+ for (auto content : {sstring(""), sstring("xxxxxxxxxxx")}) {
+ for (auto expect : {sstring(""), sstring("Expect: 100-continue\r\n"), sstring("Expect: 100-cOnTInUE\r\n")}) {
+ auto content_len = content.empty() ? sstring("") : (sstring("Content-Length: ") + to_sstring(content.length()) + sstring("\r\n"));
+ sstring req = sstring("GET /test HTTP/") + version + sstring("\r\nHost: test\r\nConnection: Keep-Alive\r\n") + content_len + expect + sstring("\r\n");
+ output.write(req).get();
+ output.flush().get();
+ bool already_ok = false;
+ if (version == "1.1" && expect.length()) {
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
+ already_ok = content.empty() && std::string(resp.get(), resp.size()).find("200 OK") != std::string::npos;
+ }
+ if (!already_ok) {
+ //If the body is empty, the final response might have already been read
+ output.write(content).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
+ }
+ }
+ }
+ }
+ output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
+
+ input.close().get();
+ output.close().get();
+ });
+
+ auto handler = new json_test_handler(json::stream_object("hello"));
+ server._routes.put(GET, "/test", handler);
+ server.do_accepts(0).get();
+
+ client.get();
+ server.stop().get();
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_unparsable_request) {
+ // Test if a message that cannot be parsed as a http request is being replied with a 400 Bad Request response
+ return seastar::async([] {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ loopback_socket_impl lsi(lcf);
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+ future<> client = seastar::async([&lsi] {
+ connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
+ input_stream<char> input(c_socket.input());
+ output_stream<char> output(c_socket.output());
+
+ output.write(sstring("GET /test HTTP/1.1\r\nhello\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
+ output.flush().get();
+ auto resp = input.read().get0();
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("400 Bad Request"), std::string::npos);
+ BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("Can't parse the request"), std::string::npos);
+
+ input.close().get();
+ output.close().get();
+ });
+
+ auto handler = new json_test_handler(json::stream_object("hello"));
+ server._routes.put(GET, "/test", handler);
+ server.do_accepts(0).get();
+
+ client.get();
+ server.stop().get();
+ });
+}
+
+SEASTAR_TEST_CASE(case_insensitive_header) {
+ std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
+ req->_headers["conTEnt-LengtH"] = "17";
+ BOOST_REQUIRE_EQUAL(req->get_header("content-length"), "17");
+ BOOST_REQUIRE_EQUAL(req->get_header("Content-Length"), "17");
+ BOOST_REQUIRE_EQUAL(req->get_header("cOnTeNT-lEnGTh"), "17");
+ return make_ready_future<>();
+}
+
+SEASTAR_THREAD_TEST_CASE(multiple_connections) {
+ loopback_connection_factory lcf;
+ http_server server("test");
+ httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
+ socket_address addr{ipv4_addr()};
+
+ std::vector<connected_socket> socks;
+ // Make sure one shard has two connections pending.
+ for (unsigned i = 0; i <= smp::count; ++i) {
+ socks.push_back(loopback_socket_impl(lcf).connect(addr, addr).get0());
+ }
+
+ server.do_accepts(0).get();
+ server.stop().get();
+ lcf.destroy_all_shards().get();
+}
diff --git a/src/seastar/tests/unit/ipv6_test.cc b/src/seastar/tests/unit/ipv6_test.cc
new file mode 100644
index 000000000..d96401b22
--- /dev/null
+++ b/src/seastar/tests/unit/ipv6_test.cc
@@ -0,0 +1,105 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+static logger iplog("ipv6");
+
+static bool check_ipv6_support() {
+ if (!engine().net().supports_ipv6()) {
+ iplog.info("No IPV6 support detected. Skipping...");
+ return false;
+ }
+ return true;
+}
+
+SEASTAR_TEST_CASE(udp_packet_test) {
+ if (!check_ipv6_support()) {
+ return make_ready_future<>();
+ }
+
+ auto sc = make_udp_channel(ipv6_addr{"::1"});
+
+ BOOST_REQUIRE(sc.local_address().addr().is_ipv6());
+
+ auto cc = make_udp_channel(ipv6_addr{"::1"});
+
+ auto f1 = cc.send(sc.local_address(), "apa");
+
+ return f1.then([cc = std::move(cc), sc = std::move(sc)]() mutable {
+ auto src = cc.local_address();
+ cc.close();
+ auto f2 = sc.receive();
+
+ return f2.then([sc = std::move(sc), src](auto pkt) mutable {
+ auto a = sc.local_address();
+ sc.close();
+ BOOST_REQUIRE_EQUAL(src, pkt.get_src());
+ auto dst = pkt.get_dst();
+ // Don't always get a dst address.
+ if (dst != socket_address()) {
+ BOOST_REQUIRE_EQUAL(a, pkt.get_dst());
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(tcp_packet_test) {
+ if (!check_ipv6_support()) {
+ return make_ready_future<>();
+ }
+
+ return async([] {
+ auto sc = server_socket(engine().net().listen(ipv6_addr{"::1"}, {}));
+ auto la = sc.local_address();
+
+ BOOST_REQUIRE(la.addr().is_ipv6());
+
+ auto cc = connect(la).get0();
+ auto lc = std::move(sc.accept().get0().connection);
+
+ auto strm = cc.output();
+ strm.write("los lobos").get();
+ strm.flush().get();
+
+ auto in = lc.input();
+
+ using consumption_result_type = typename input_stream<char>::consumption_result_type;
+ using stop_consuming_type = typename consumption_result_type::stop_consuming_type;
+ using tmp_buf = stop_consuming_type::tmp_buf;
+
+ in.consume([](tmp_buf buf) {
+ return make_ready_future<consumption_result_type>(stop_consuming<char>({}));
+ }).get();
+
+ strm.close().get();
+ in.close().get();
+ sc.abort_accept();
+ });
+}
+
diff --git a/src/seastar/tests/unit/json_formatter_test.cc b/src/seastar/tests/unit/json_formatter_test.cc
new file mode 100644
index 000000000..213b93bbd
--- /dev/null
+++ b/src/seastar/tests/unit/json_formatter_test.cc
@@ -0,0 +1,52 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2016 ScyllaDB.
+ */
+#include <vector>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/json/formatter.hh>
+
+using namespace seastar;
+using namespace json;
+
+SEASTAR_TEST_CASE(test_simple_values) {
+ BOOST_CHECK_EQUAL("3", formatter::to_json(3));
+ BOOST_CHECK_EQUAL("3", formatter::to_json(3.0));
+ BOOST_CHECK_EQUAL("3.5", formatter::to_json(3.5));
+ BOOST_CHECK_EQUAL("true", formatter::to_json(true));
+ BOOST_CHECK_EQUAL("false", formatter::to_json(false));
+ BOOST_CHECK_EQUAL("\"apa\"", formatter::to_json("apa"));
+
+ return make_ready_future();
+}
+
+SEASTAR_TEST_CASE(test_collections) {
+ BOOST_CHECK_EQUAL("{1:2,3:4}", formatter::to_json(std::map<int,int>({{1,2},{3,4}})));
+ BOOST_CHECK_EQUAL("[1,2,3,4]", formatter::to_json(std::vector<int>({1,2,3,4})));
+ BOOST_CHECK_EQUAL("[{1:2},{3:4}]", formatter::to_json(std::vector<std::pair<int,int>>({{1,2},{3,4}})));
+ BOOST_CHECK_EQUAL("[{1:2},{3:4}]", formatter::to_json(std::vector<std::map<int,int>>({{{1,2}},{{3,4}}})));
+ BOOST_CHECK_EQUAL("[[1,2],[3,4]]", formatter::to_json(std::vector<std::vector<int>>({{1,2},{3,4}})));
+
+ return make_ready_future();
+}
diff --git a/src/seastar/tests/unit/locking_test.cc b/src/seastar/tests/unit/locking_test.cc
new file mode 100644
index 000000000..55a1251b8
--- /dev/null
+++ b/src/seastar/tests/unit/locking_test.cc
@@ -0,0 +1,223 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB.
+ */
+
+#include <chrono>
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/rwlock.hh>
+#include <seastar/core/shared_mutex.hh>
+#include <seastar/util/alloc_failure_injector.hh>
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_THREAD_TEST_CASE(test_rwlock) {
+ rwlock l;
+
+ l.for_write().lock().get();
+ BOOST_REQUIRE(!l.try_write_lock());
+ BOOST_REQUIRE(!l.try_read_lock());
+ l.for_write().unlock();
+
+ l.for_read().lock().get();
+ BOOST_REQUIRE(!l.try_write_lock());
+ BOOST_REQUIRE(l.try_read_lock());
+ l.for_read().lock().get();
+ l.for_read().unlock();
+ l.for_read().unlock();
+ l.for_read().unlock();
+
+ BOOST_REQUIRE(l.try_write_lock());
+ l.for_write().unlock();
+}
+
+SEASTAR_TEST_CASE(test_with_lock_mutable) {
+ return do_with(rwlock(), [](rwlock& l) {
+ return with_lock(l.for_read(), [p = std::make_unique<int>(42)] () mutable {});
+ });
+}
+
+SEASTAR_TEST_CASE(test_rwlock_exclusive) {
+ return do_with(rwlock(), unsigned(0), [] (rwlock& l, unsigned& counter) {
+ return parallel_for_each(boost::irange(0, 10), [&l, &counter] (int idx) {
+ return with_lock(l.for_write(), [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(1ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_rwlock_shared) {
+ return do_with(rwlock(), unsigned(0), unsigned(0), [] (rwlock& l, unsigned& counter, unsigned& max) {
+ return parallel_for_each(boost::irange(0, 10), [&l, &counter, &max] (int idx) {
+ return with_lock(l.for_read(), [&counter, &max] {
+ ++counter;
+ max = std::max(max, counter);
+ return sleep(1ms).then([&counter] {
+ --counter;
+ });
+ });
+ }).finally([&counter, &max] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ BOOST_REQUIRE_NE(max, 0u);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_rwlock_failed_func) {
+ rwlock l;
+
+ // verify that the rwlock is unlocked when func fails
+ future<> fut = with_lock(l.for_read(), [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ fut = with_lock(l.for_write(), [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ BOOST_REQUIRE(l.try_write_lock());
+ l.for_write().unlock();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_failed_with_lock) {
+ struct test_lock {
+ future<> lock() noexcept {
+ return make_exception_future<>(std::runtime_error("injected"));
+ }
+ void unlock() noexcept {
+ BOOST_REQUIRE(false);
+ }
+ };
+
+ test_lock l;
+
+ // if l.lock() fails neither the function nor l.unlock()
+ // should be called.
+ BOOST_REQUIRE_THROW(with_lock(l, [] {
+ BOOST_REQUIRE(false);
+ }).get(), std::runtime_error);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_mutex) {
+ shared_mutex sm;
+
+ sm.lock().get();
+ BOOST_REQUIRE(!sm.try_lock());
+ BOOST_REQUIRE(!sm.try_lock_shared());
+ sm.unlock();
+
+ sm.lock_shared().get();
+ BOOST_REQUIRE(!sm.try_lock());
+ BOOST_REQUIRE(sm.try_lock_shared());
+ sm.lock_shared().get();
+ sm.unlock_shared();
+ sm.unlock_shared();
+ sm.unlock_shared();
+
+ BOOST_REQUIRE(sm.try_lock());
+ sm.unlock();
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_exclusive) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ return parallel_for_each(boost::irange(0, 10), [&sm, &counter] (int idx) {
+ return with_lock(sm, [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(1ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_shared) {
+ return do_with(shared_mutex(), unsigned(0), unsigned(0), [] (shared_mutex& sm, unsigned& counter, unsigned& max) {
+ return parallel_for_each(boost::irange(0, 10), [&sm, &counter, &max] (int idx) {
+ return with_shared(sm, [&counter, &max] {
+ ++counter;
+ max = std::max(max, counter);
+ return sleep(1ms).then([&counter] {
+ --counter;
+ });
+ });
+ }).finally([&counter, &max] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ BOOST_REQUIRE_NE(max, 0u);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_mutex_failed_func) {
+ shared_mutex sm;
+
+ // verify that the shared_mutex is unlocked when func fails
+ future<> fut = with_shared(sm, [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ fut = with_lock(sm, [] {
+ throw std::runtime_error("injected");
+ });
+ BOOST_REQUIRE_THROW(fut.get(), std::runtime_error);
+
+ BOOST_REQUIRE(sm.try_lock());
+ sm.unlock();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_shared_mutex_failed_lock) {
+#ifdef SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION
+ shared_mutex sm;
+
+ // if l.lock() fails neither the function nor l.unlock()
+ // should be called.
+ sm.lock().get();
+ seastar::memory::local_failure_injector().fail_after(0);
+ BOOST_REQUIRE_THROW(with_shared(sm, [] {
+ BOOST_REQUIRE(false);
+ }).get(), std::bad_alloc);
+
+ seastar::memory::local_failure_injector().fail_after(0);
+ BOOST_REQUIRE_THROW(with_lock(sm, [] {
+ BOOST_REQUIRE(false);
+ }).get(), std::bad_alloc);
+ sm.unlock();
+
+ seastar::memory::local_failure_injector().cancel();
+#endif // SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION
+}
diff --git a/src/seastar/tests/unit/log_buf_test.cc b/src/seastar/tests/unit/log_buf_test.cc
new file mode 100644
index 000000000..fbb11a7b1
--- /dev/null
+++ b/src/seastar/tests/unit/log_buf_test.cc
@@ -0,0 +1,55 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2020 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(log_buf_realloc) {
+ std::array<char, 128> external_buf;
+
+ const auto external_buf_ptr = reinterpret_cast<uintptr_t>(external_buf.data());
+
+ internal::log_buf b(external_buf.data(), external_buf.size());
+
+ BOOST_REQUIRE_EQUAL(reinterpret_cast<uintptr_t>(b.data()), external_buf_ptr);
+
+ auto it = b.back_insert_begin();
+
+ BOOST_REQUIRE_EQUAL(reinterpret_cast<uintptr_t>(&*it), external_buf_ptr);
+
+ for (auto i = 0; i < 128; ++i) {
+ *it++ = 'a';
+ }
+
+ *it = 'a'; // should trigger realloc
+
+ BOOST_REQUIRE_NE(reinterpret_cast<uintptr_t>(b.data()), reinterpret_cast<uintptr_t>(external_buf.data()));
+ BOOST_REQUIRE_NE(reinterpret_cast<uintptr_t>(&*it), reinterpret_cast<uintptr_t>(external_buf.data() + 128));
+
+ const char* p = b.data();
+ for (auto i = 0; i < 129; ++i) {
+ BOOST_REQUIRE_EQUAL(p[i], 'a');
+ }
+
+ return make_ready_future<>();
+}
diff --git a/src/seastar/tests/unit/loopback_socket.hh b/src/seastar/tests/unit/loopback_socket.hh
new file mode 100644
index 000000000..c21653031
--- /dev/null
+++ b/src/seastar/tests/unit/loopback_socket.hh
@@ -0,0 +1,278 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#pragma once
+
+#include <system_error>
+#include <seastar/core/iostream.hh>
+#include <seastar/core/circular_buffer.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/queue.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/net/stack.hh>
+#include <seastar/core/sharded.hh>
+
+namespace seastar {
+
+struct loopback_error_injector {
+ virtual ~loopback_error_injector() {};
+ virtual bool server_rcv_error() { return false; }
+ virtual bool server_snd_error() { return false; }
+ virtual bool client_rcv_error() { return false; }
+ virtual bool client_snd_error() { return false; }
+};
+
+class loopback_buffer {
+public:
+ enum class type : uint8_t {
+ CLIENT_TX,
+ SERVER_TX
+ };
+private:
+ bool _aborted = false;
+ queue<temporary_buffer<char>> _q{1};
+ loopback_error_injector* _error_injector;
+ type _type;
+public:
+ loopback_buffer(loopback_error_injector* error_injection, type t) : _error_injector(error_injection), _type(t) {}
+ future<> push(temporary_buffer<char>&& b) {
+ if (_aborted) {
+ return make_exception_future<>(std::system_error(EPIPE, std::system_category()));
+ }
+ bool error = false;
+ if (_error_injector) {
+ error = _type == type::CLIENT_TX ? _error_injector->client_snd_error() : _error_injector->server_snd_error();
+ }
+ if (error) {
+ shutdown();
+ return make_exception_future<>(std::runtime_error("test injected error on send"));
+ }
+ return _q.push_eventually(std::move(b));
+ }
+ future<temporary_buffer<char>> pop() {
+ if (_aborted) {
+ return make_exception_future<temporary_buffer<char>>(std::system_error(EPIPE, std::system_category()));
+ }
+ bool error = false;
+ if (_error_injector) {
+ error = _type == type::CLIENT_TX ? _error_injector->client_rcv_error() : _error_injector->server_rcv_error();
+ }
+ if (error) {
+ shutdown();
+ return make_exception_future<temporary_buffer<char>>(std::runtime_error("test injected error on receive"));
+ }
+ return _q.pop_eventually();
+ }
+ void shutdown() {
+ _aborted = true;
+ _q.abort(std::make_exception_ptr(std::system_error(EPIPE, std::system_category())));
+ }
+};
+
+class loopback_data_sink_impl : public data_sink_impl {
+ foreign_ptr<lw_shared_ptr<loopback_buffer>>& _buffer;
+public:
+ explicit loopback_data_sink_impl(foreign_ptr<lw_shared_ptr<loopback_buffer>>& buffer)
+ : _buffer(buffer) {
+ }
+ future<> put(net::packet data) override {
+ return do_with(data.release(), [this] (std::vector<temporary_buffer<char>>& bufs) {
+ return do_for_each(bufs, [this] (temporary_buffer<char>& buf) {
+ return smp::submit_to(_buffer.get_owner_shard(), [this, b = buf.get(), s = buf.size()] {
+ return _buffer->push(temporary_buffer<char>(b, s));
+ });
+ });
+ });
+ }
+ future<> close() override {
+ return smp::submit_to(_buffer.get_owner_shard(), [this] {
+ return _buffer->push({}).handle_exception_type([] (std::system_error& err) {
+ if (err.code().value() != EPIPE) {
+ throw err;
+ }
+ });
+ });
+ }
+};
+
+class loopback_data_source_impl : public data_source_impl {
+ bool _eof = false;
+ lw_shared_ptr<loopback_buffer> _buffer;
+public:
+ explicit loopback_data_source_impl(lw_shared_ptr<loopback_buffer> buffer)
+ : _buffer(std::move(buffer)) {
+ }
+ future<temporary_buffer<char>> get() override {
+ return _buffer->pop().then_wrapped([this] (future<temporary_buffer<char>>&& b) {
+ _eof = b.failed();
+ if (!_eof) {
+ // future::get0() is destructive, so we have to play these games
+ // FIXME: make future::get0() non-destructive
+ auto&& tmp = b.get0();
+ _eof = tmp.empty();
+ b = make_ready_future<temporary_buffer<char>>(std::move(tmp));
+ }
+ return std::move(b);
+ });
+ }
+ future<> close() override {
+ if (!_eof) {
+ _buffer->shutdown();
+ }
+ return make_ready_future<>();
+ }
+};
+
+
+class loopback_connected_socket_impl : public net::connected_socket_impl {
+ foreign_ptr<lw_shared_ptr<loopback_buffer>> _tx;
+ lw_shared_ptr<loopback_buffer> _rx;
+public:
+ loopback_connected_socket_impl(foreign_ptr<lw_shared_ptr<loopback_buffer>> tx, lw_shared_ptr<loopback_buffer> rx)
+ : _tx(std::move(tx)), _rx(std::move(rx)) {
+ }
+ data_source source() override {
+ return data_source(std::make_unique<loopback_data_source_impl>(_rx));
+ }
+ data_sink sink() override {
+ return data_sink(std::make_unique<loopback_data_sink_impl>(_tx));
+ }
+ void shutdown_input() override {
+ _rx->shutdown();
+ }
+ void shutdown_output() override {
+ (void)smp::submit_to(_tx.get_owner_shard(), [this] {
+ // FIXME: who holds to _tx?
+ _tx->shutdown();
+ });
+ }
+ void set_nodelay(bool nodelay) override {
+ }
+ bool get_nodelay() const override {
+ return true;
+ }
+ void set_keepalive(bool keepalive) override {}
+ bool get_keepalive() const override {
+ return false;
+ }
+ void set_keepalive_parameters(const net::keepalive_params&) override {}
+ net::keepalive_params get_keepalive_parameters() const override {
+ return net::tcp_keepalive_params {std::chrono::seconds(0), std::chrono::seconds(0), 0};
+ }
+ void set_sockopt(int level, int optname, const void* data, size_t len) override {
+ throw std::runtime_error("Setting custom socket options is not supported for loopback");
+ }
+ int get_sockopt(int level, int optname, void* data, size_t len) const override {
+ throw std::runtime_error("Getting custom socket options is not supported for loopback");
+ }
+};
+
+class loopback_server_socket_impl : public net::server_socket_impl {
+ lw_shared_ptr<queue<connected_socket>> _pending;
+public:
+ explicit loopback_server_socket_impl(lw_shared_ptr<queue<connected_socket>> q)
+ : _pending(std::move(q)) {
+ }
+ future<accept_result> accept() override {
+ return _pending->pop_eventually().then([] (connected_socket&& cs) {
+ return make_ready_future<accept_result>(accept_result{std::move(cs), socket_address()});
+ });
+ }
+ void abort_accept() override {
+ _pending->abort(std::make_exception_ptr(std::system_error(ECONNABORTED, std::system_category())));
+ }
+ socket_address local_address() const override {
+ // CMH dummy
+ return {};
+ }
+};
+
+
+class loopback_connection_factory {
+ unsigned _shard = 0;
+ std::vector<lw_shared_ptr<queue<connected_socket>>> _pending;
+public:
+ loopback_connection_factory() {
+ _pending.resize(smp::count);
+ }
+ server_socket get_server_socket() {
+ if (!_pending[this_shard_id()]) {
+ _pending[this_shard_id()] = make_lw_shared<queue<connected_socket>>(10);
+ }
+ return server_socket(std::make_unique<loopback_server_socket_impl>(_pending[this_shard_id()]));
+ }
+ future<> make_new_server_connection(foreign_ptr<lw_shared_ptr<loopback_buffer>> b1, lw_shared_ptr<loopback_buffer> b2) {
+ if (!_pending[this_shard_id()]) {
+ _pending[this_shard_id()] = make_lw_shared<queue<connected_socket>>(10);
+ }
+ return _pending[this_shard_id()]->push_eventually(connected_socket(std::make_unique<loopback_connected_socket_impl>(std::move(b1), b2)));
+ }
+ connected_socket make_new_client_connection(lw_shared_ptr<loopback_buffer> b1, foreign_ptr<lw_shared_ptr<loopback_buffer>> b2) {
+ return connected_socket(std::make_unique<loopback_connected_socket_impl>(std::move(b2), b1));
+ }
+ unsigned next_shard() {
+ return _shard++ % smp::count;
+ }
+ void destroy_shard(unsigned shard) {
+ _pending[shard] = nullptr;
+ }
+ future<> destroy_all_shards() {
+ return smp::invoke_on_all([this] () {
+ destroy_shard(this_shard_id());
+ });
+ }
+};
+
+class loopback_socket_impl : public net::socket_impl {
+ loopback_connection_factory& _factory;
+ loopback_error_injector* _error_injector;
+ lw_shared_ptr<loopback_buffer> _b1;
+ foreign_ptr<lw_shared_ptr<loopback_buffer>> _b2;
+public:
+ loopback_socket_impl(loopback_connection_factory& factory, loopback_error_injector* error_injector = nullptr)
+ : _factory(factory), _error_injector(error_injector)
+ { }
+ future<connected_socket> connect(socket_address sa, socket_address local, seastar::transport proto = seastar::transport::TCP) override {
+ auto shard = _factory.next_shard();
+ _b1 = make_lw_shared<loopback_buffer>(_error_injector, loopback_buffer::type::SERVER_TX);
+ return smp::submit_to(shard, [this, b1 = make_foreign(_b1)] () mutable {
+ auto b2 = make_lw_shared<loopback_buffer>(_error_injector, loopback_buffer::type::CLIENT_TX);
+ _b2 = make_foreign(b2);
+ return _factory.make_new_server_connection(std::move(b1), b2).then([b2] {
+ return make_foreign(b2);
+ });
+ }).then([this] (foreign_ptr<lw_shared_ptr<loopback_buffer>> b2) {
+ return _factory.make_new_client_connection(_b1, std::move(b2));
+ });
+ }
+ virtual void set_reuseaddr(bool reuseaddr) override {}
+ virtual bool get_reuseaddr() const override { return false; };
+
+ void shutdown() override {
+ _b1->shutdown();
+ (void)smp::submit_to(_b2.get_owner_shard(), [b2 = std::move(_b2)] {
+ b2->shutdown();
+ });
+ }
+};
+
+}
diff --git a/src/seastar/tests/unit/lowres_clock_test.cc b/src/seastar/tests/unit/lowres_clock_test.cc
new file mode 100644
index 000000000..1beb230b6
--- /dev/null
+++ b/src/seastar/tests/unit/lowres_clock_test.cc
@@ -0,0 +1,118 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#include <seastar/testing/test_case.hh>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/core/lowres_clock.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/loop.hh>
+
+#include <ctime>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+
+using namespace seastar;
+
+//
+// Sanity check the accuracy of the steady low-resolution clock.
+//
+SEASTAR_TEST_CASE(steady_clock_sanity) {
+ return do_with(lowres_clock::now(), [](auto &&t1) {
+ static constexpr auto sleep_duration = std::chrono::milliseconds(100);
+
+ return ::seastar::sleep(sleep_duration).then([&t1] {
+ auto const elapsed = lowres_clock::now() - t1;
+ auto const minimum_elapsed = 0.9 * sleep_duration;
+
+ BOOST_REQUIRE(elapsed >= minimum_elapsed);
+
+ return make_ready_future<>();
+ });
+ });
+}
+
+//
+// At the very least, we can verify that the low-resolution system clock is within a second of the
+// high-resolution system clock.
+//
+SEASTAR_TEST_CASE(system_clock_sanity) {
+ static const auto check_matching = [] {
+ auto const system_time = std::chrono::system_clock::now();
+ auto const lowres_time = lowres_system_clock::now();
+
+ auto const t1 = std::chrono::system_clock::to_time_t(system_time);
+ auto const t2 = lowres_system_clock::to_time_t(lowres_time);
+
+ std::tm *lt1 = std::localtime(&t1);
+ std::tm *lt2 = std::localtime(&t2);
+
+ return (lt1->tm_isdst == lt2->tm_isdst) &&
+ (lt1->tm_year == lt2->tm_year) &&
+ (lt1->tm_mon == lt2->tm_mon) &&
+ (lt1->tm_yday == lt2->tm_yday) &&
+ (lt1->tm_mday == lt2->tm_mday) &&
+ (lt1->tm_wday == lt2->tm_wday) &&
+ (lt1->tm_hour == lt2->tm_hour) &&
+ (lt1->tm_min == lt2->tm_min) &&
+ (lt1->tm_sec == lt2->tm_sec);
+ };
+
+ //
+ // Check two out of three samples in order to account for the possibility that the high-resolution clock backing
+ // the low-resoltuion clock was captured in the range of the 990th to 999th millisecond of the second. This would
+ // make the low-resolution clock and the high-resolution clock disagree on the current second.
+ //
+
+ return do_with(0ul, 0ul, [](std::size_t& index, std::size_t& success_count) {
+ return repeat([&index, &success_count] {
+ if (index >= 3) {
+ BOOST_REQUIRE_GE(success_count, 2u);
+ return make_ready_future<stop_iteration>(stop_iteration::yes);
+ }
+
+ return ::seastar::sleep(std::chrono::milliseconds(10)).then([&index, &success_count] {
+ if (check_matching()) {
+ ++success_count;
+ }
+
+ ++index;
+ return stop_iteration::no;
+ });
+ });
+ });
+}
+
+//
+// Verify that the low-resolution clock updates its reported time point over time.
+//
+SEASTAR_TEST_CASE(system_clock_dynamic) {
+ return do_with(lowres_system_clock::now(), [](auto &&t1) {
+ return seastar::sleep(std::chrono::milliseconds(100)).then([&t1] {
+ auto const t2 = lowres_system_clock::now();
+ BOOST_REQUIRE_NE(t1.time_since_epoch().count(), t2.time_since_epoch().count());
+
+ return make_ready_future<>();
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/metrics_test.cc b/src/seastar/tests/unit/metrics_test.cc
new file mode 100644
index 000000000..b6906010c
--- /dev/null
+++ b/src/seastar/tests/unit/metrics_test.cc
@@ -0,0 +1,170 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2019 ScyllaDB.
+ */
+
+#include <seastar/core/metrics_registration.hh>
+#include <seastar/core/metrics.hh>
+#include <seastar/core/metrics_api.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/scheduling.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/io_queue.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <boost/range/irange.hpp>
+
+SEASTAR_TEST_CASE(test_add_group) {
+ using namespace seastar::metrics;
+ // Just has to compile:
+ metric_groups()
+ .add_group("g1", {})
+ .add_group("g2", std::vector<metric_definition>());
+ return seastar::make_ready_future();
+}
+
+/**
+ * This function return the different name label values
+ * for the named metric.
+ *
+ * @note: If the statistic or label doesn't exist, the test
+ * that calls this function will fail.
+ *
+ * @param metric_name - the metric name
+ * @param label_name - the label name
+ * @return a set containing all the different values
+ * of the label.
+ */
+static std::set<seastar::sstring> get_label_values(seastar::sstring metric_name, seastar::sstring label_name) {
+ namespace smi = seastar::metrics::impl;
+ auto all_metrics = smi::get_values();
+ const auto& all_metadata = *all_metrics->metadata;
+ const auto qp_group = find_if(cbegin(all_metadata), cend(all_metadata),
+ [&metric_name] (const auto& x) { return x.mf.name == metric_name; });
+ BOOST_REQUIRE(qp_group != cend(all_metadata));
+ std::set<seastar::sstring> labels;
+ for (const auto& metric : qp_group->metrics) {
+ const auto found = metric.id.labels().find(label_name);
+ BOOST_REQUIRE(found != metric.id.labels().cend());
+ labels.insert(found->second);
+ }
+ return labels;
+}
+
+SEASTAR_THREAD_TEST_CASE(test_renaming_scheuling_groups) {
+ // this seams a little bit out of place but the
+ // renaming functionality is primarily for statistics
+ // otherwise those classes could have just been reused
+ // without renaming them.
+ using namespace seastar;
+
+ static const char* name1 = "A";
+ static const char* name2 = "B";
+ scheduling_group sg = create_scheduling_group("hello", 111).get0();
+ boost::integer_range<int> rng(0, 1000);
+ // repeatedly change the group name back and forth in
+ // decresing time intervals to see if it generate double
+ //registration statistics errors.
+ for (auto&& i : rng) {
+ const char* name = i%2 ? name1 : name2;
+ const char* prev_name = i%2 ? name2 : name1;
+ sleep(std::chrono::microseconds(100000/(i+1))).get();
+ rename_scheduling_group(sg, name).get();
+ std::set<sstring> label_vals = get_label_values(sstring("scheduler_shares"), sstring("group"));
+ // validate that the name that we *renamed to* is in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(name)) != label_vals.end());
+ // validate that the name that we *renamed from* is *not* in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(prev_name)) == label_vals.end());
+ }
+
+ smp::invoke_on_all([sg] () {
+ return do_with(std::uniform_int_distribution<int>(), boost::irange<int>(0, 1000),
+ [sg] (std::uniform_int_distribution<int>& dist, boost::integer_range<int>& rng) {
+ // flip a fair coin and rename to one of two options and rename to that
+ // scheduling group name, do it 1000 in parallel on all shards so there
+ // is a chance of collision.
+ return do_for_each(rng, [sg, &dist] (auto i) {
+ bool odd = dist(seastar::testing::local_random_engine)%2;
+ return rename_scheduling_group(sg, odd ? name1 : name2);
+ });
+ });
+ }).get();
+
+ std::set<sstring> label_vals = get_label_values(sstring("scheduler_shares"), sstring("group"));
+ // validate that only one of the names is eventually in the metrics
+ bool name1_found = label_vals.find(sstring(name1)) != label_vals.end();
+ bool name2_found = label_vals.find(sstring(name2)) != label_vals.end();
+ BOOST_REQUIRE((name1_found && !name2_found) || (name2_found && !name1_found));
+}
+
+SEASTAR_THREAD_TEST_CASE(test_renaming_io_priority_classes) {
+ // this seams a little bit out of place but the
+ // renaming functionality is primarily for statistics
+ // otherwise those classes could have just been reused
+ // without renaming them.
+ using namespace seastar;
+ static const char* name1 = "A";
+ static const char* name2 = "B";
+ seastar::io_priority_class pc = engine().register_one_priority_class("hello",100);
+ smp::invoke_on_all([pc] () {
+ // this is a trick to get all of the queues actually register their
+ // stats.
+ return engine().update_shares_for_class(pc,101);
+ }).get();
+
+ boost::integer_range<int> rng(0, 1000);
+ // repeatedly change the group name back and forth in
+ // decresing time intervals to see if it generate double
+ //registration statistics errors.
+ for (auto&& i : rng) {
+ const char* name = i%2 ? name1 : name2;
+ const char* prev_name = i%2 ? name2 : name1;
+ sleep(std::chrono::microseconds(100000/(i+1))).get();
+ rename_priority_class(pc, name).get();
+ std::set<sstring> label_vals = get_label_values(sstring("io_queue_shares"), sstring("class"));
+ // validate that the name that we *renamed to* is in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(name)) != label_vals.end());
+ // validate that the name that we *renamed from* is *not* in the stats
+ BOOST_REQUIRE(label_vals.find(sstring(prev_name)) == label_vals.end());
+ }
+
+ smp::invoke_on_all([pc] () {
+ return do_with(std::uniform_int_distribution<int>(), boost::irange<int>(0, 1000),
+ [pc] (std::uniform_int_distribution<int>& dist, boost::integer_range<int>& rng) {
+ // flip a fair coin and rename to one of two options and rename to that
+ // scheduling group name, do it 1000 in parallel on all shards so there
+ // is a chance of collision.
+ return do_for_each(rng, [pc, &dist] (auto i) {
+ bool odd = dist(seastar::testing::local_random_engine)%2;
+ return rename_priority_class(pc, odd ? name1 : name2);
+ });
+ });
+ }).get();
+
+ std::set<sstring> label_vals = get_label_values(sstring("io_queue_shares"), sstring("class"));
+ // validate that only one of the names is eventually in the metrics
+ bool name1_found = label_vals.find(sstring(name1)) != label_vals.end();
+ bool name2_found = label_vals.find(sstring(name2)) != label_vals.end();
+ BOOST_REQUIRE((name1_found && !name2_found) || (name2_found && !name1_found));
+}
diff --git a/src/seastar/tests/unit/mkcert.gmk b/src/seastar/tests/unit/mkcert.gmk
new file mode 100644
index 000000000..ecf2d5dfe
--- /dev/null
+++ b/src/seastar/tests/unit/mkcert.gmk
@@ -0,0 +1,94 @@
+server = $(shell hostname)
+domain = $(shell dnsdomainname)
+name = $(server)
+
+country = SE
+state = Stockholm
+locality= $(state)
+org = $(domain)
+unit = $(domain)
+mail = mx
+common = $(server).$(domain)
+email = postmaster@$(domain)
+ckey = ca$(key).pem
+
+pubkey = $(name).pub
+prvkey = $(name).key
+width = 4096
+
+csr = $(name).csr
+crt = $(name).crt
+
+root = ca$(name).pem
+rootkey = ca$(name).key
+
+config = $(name).cfg
+days = 3650
+
+alg = RSA
+alg_opt = -pkeyopt rsa_keygen_bits:$(width)
+
+hosts =
+
+all : $(crt)
+
+clean :
+ @rm -f $(crt) $(csr) $(pubkey) $(prvkey)
+
+%.key :
+ @echo generating $@
+ openssl genpkey -out $@ -algorithm $(alg) $(alg_opt)
+
+%.pub : %.key
+ @echo generating $@
+ openssl pkey -in $< -out $@
+
+$(config) : $(MAKEFILE_LIST)
+ @echo generating $@
+ @( \
+ echo [ req ] ; \
+ echo default_bits = $(width) ; \
+ echo default_keyfile = $(prvkey) ; \
+ echo default_md = sha256 ; \
+ echo distinguished_name = req_distinguished_name ; \
+ echo req_extensions = v3_req ; \
+ echo prompt = no ; \
+ echo [ req_distinguished_name ] ; \
+ echo C = $(country) ; \
+ echo ST = $(state) ; \
+ echo L = $(locality) ; \
+ echo O = $(org) ; \
+ echo OU = $(unit) ; \
+ echo CN= $(common) ; \
+ echo emailAddress = $(email) ; \
+ echo [v3_ca] ; \
+ echo subjectKeyIdentifier=hash ; \
+ echo authorityKeyIdentifier=keyid:always,issuer:always ; \
+ echo basicConstraints = CA:true ; \
+ echo [v3_req] ; \
+ echo "# Extensions to add to a certificate request" ; \
+ echo basicConstraints = CA:FALSE ; \
+ echo keyUsage = nonRepudiation, digitalSignature, keyEncipherment ; \
+ $(if $(hosts), echo subjectAltName = @alt_names ;) \
+ $(if $(hosts), echo [alt_names] ;) \
+ $(if $(hosts), index=1; for host in $(hosts); \
+ do echo DNS.$$index = $$host.$(domain); \
+ index=$$(($$index + 1));done ;) \
+ ) > $@
+
+%.csr : %.key $(config)
+ @echo generating $@
+ openssl req -new -key $< -out $@ -config $(config)
+
+%.crt : %.csr $(root) $(rootkey)
+ @echo generating $@
+ openssl x509 -req -in $< -CA $(root) -CAkey $(rootkey) -CAcreateserial \
+ -out $@ -days $(days)
+
+%.pem : %.key $(config)
+ @echo generating $@
+ openssl req -x509 -new -nodes -key $< -days $(days) -config $(config) \
+ -out $@
+
+.PRECIOUS : %.pem %.key %.pub %.crt %.csr
+
diff --git a/src/seastar/tests/unit/mock_file.hh b/src/seastar/tests/unit/mock_file.hh
new file mode 100644
index 000000000..7b7f8eeed
--- /dev/null
+++ b/src/seastar/tests/unit/mock_file.hh
@@ -0,0 +1,113 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+#pragma once
+
+#include <boost/range/numeric.hpp>
+
+#include <seastar/testing/seastar_test.hh>
+#include <seastar/core/file.hh>
+
+namespace seastar {
+
+class mock_read_only_file final : public file_impl {
+ bool _closed = false;
+ uint64_t _total_file_size;
+ size_t _allowed_read_requests = 0;
+ std::function<void(size_t)> _verify_length;
+private:
+ size_t verify_read(uint64_t position, size_t length) {
+ BOOST_CHECK(!_closed);
+ BOOST_CHECK_LE(position, _total_file_size);
+ BOOST_CHECK_LE(position + length, _total_file_size);
+ if (position + length != _total_file_size) {
+ _verify_length(length);
+ }
+ BOOST_CHECK(_allowed_read_requests);
+ assert(_allowed_read_requests);
+ _allowed_read_requests--;
+ return length;
+ }
+public:
+ explicit mock_read_only_file(uint64_t file_size) noexcept
+ : _total_file_size(file_size)
+ , _verify_length([] (auto) { })
+ { }
+
+ void set_read_size_verifier(std::function<void(size_t)> fn) {
+ _verify_length = fn;
+ }
+ void set_expected_read_size(size_t expected) {
+ _verify_length = [expected] (auto length) {
+ BOOST_CHECK_EQUAL(length, expected);
+ };
+ }
+ void set_allowed_read_requests(size_t requests) {
+ _allowed_read_requests = requests;
+ }
+
+ virtual future<size_t> write_dma(uint64_t, const void*, size_t, const io_priority_class&) noexcept override {
+ return make_exception_future<size_t>(std::bad_function_call());
+ }
+ virtual future<size_t> write_dma(uint64_t, std::vector<iovec>, const io_priority_class&) noexcept override {
+ return make_exception_future<size_t>(std::bad_function_call());
+ }
+ virtual future<size_t> read_dma(uint64_t pos, void*, size_t len, const io_priority_class&) noexcept override {
+ return make_ready_future<size_t>(verify_read(pos, len));
+ }
+ virtual future<size_t> read_dma(uint64_t pos, std::vector<iovec> iov, const io_priority_class&) noexcept override {
+ auto length = boost::accumulate(iov | boost::adaptors::transformed([] (auto&& iov) { return iov.iov_len; }),
+ size_t(0), std::plus<size_t>());
+ return make_ready_future<size_t>(verify_read(pos, length));
+ }
+ virtual future<> flush() noexcept override {
+ return make_ready_future<>();
+ }
+ virtual future<struct stat> stat() noexcept override {
+ return make_exception_future<struct stat>(std::bad_function_call());
+ }
+ virtual future<> truncate(uint64_t) noexcept override {
+ return make_exception_future<>(std::bad_function_call());
+ }
+ virtual future<> discard(uint64_t offset, uint64_t length) noexcept override {
+ return make_exception_future<>(std::bad_function_call());
+ }
+ virtual future<> allocate(uint64_t position, uint64_t length) noexcept override {
+ return make_exception_future<>(std::bad_function_call());
+ }
+ virtual future<uint64_t> size() noexcept override {
+ return make_ready_future<uint64_t>(_total_file_size);
+ }
+ virtual future<> close() noexcept override {
+ BOOST_CHECK(!_closed);
+ _closed = true;
+ return make_ready_future<>();
+ }
+ virtual subscription<directory_entry> list_directory(std::function<future<> (directory_entry de)>) override {
+ throw std::bad_function_call();
+ }
+ virtual future<temporary_buffer<uint8_t>> dma_read_bulk(uint64_t offset, size_t range_size, const io_priority_class&) noexcept override {
+ auto length = verify_read(offset, range_size);
+ return make_ready_future<temporary_buffer<uint8_t>>(temporary_buffer<uint8_t>(length));
+ }
+};
+
+}
diff --git a/src/seastar/tests/unit/net_config_test.cc b/src/seastar/tests/unit/net_config_test.cc
new file mode 100644
index 000000000..ec26135fd
--- /dev/null
+++ b/src/seastar/tests/unit/net_config_test.cc
@@ -0,0 +1,127 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2017 Marek Waszkiewicz ( marek.waszkiewicz77@gmail.com )
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <seastar/net/config.hh>
+#include <boost/test/included/unit_test.hpp>
+#include <exception>
+#include <sstream>
+
+using namespace seastar::net;
+
+BOOST_AUTO_TEST_CASE(test_valid_config_with_pci_address) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0 } , eth1: {pci-address: 0000:06:00.1, dhcp: true } }";
+ auto device_configs = parse_config(ss);
+
+ // eth0 tests
+ BOOST_REQUIRE(device_configs.find("eth0") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").hw_cfg.pci_address, "0000:06:00.0");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.dhcp, false);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.ip, "192.168.100.10");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.gateway, "192.168.100.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.netmask, "255.255.255.0");
+
+ // eth1 tests
+ BOOST_REQUIRE(device_configs.find("eth1") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").hw_cfg.pci_address, "0000:06:00.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.dhcp, true);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.ip, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.gateway, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.netmask, "");
+}
+
+BOOST_AUTO_TEST_CASE(test_valid_config_with_port_index) {
+ std::stringstream ss;
+ ss << "{eth0: {port-index: 0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0 } , eth1: {port-index: 1, dhcp: true } }";
+ auto device_configs = parse_config(ss);
+
+ // eth0 tests
+ BOOST_REQUIRE(device_configs.find("eth0") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(*device_configs.at("eth0").hw_cfg.port_index, 0u);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.dhcp, false);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.ip, "192.168.100.10");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.gateway, "192.168.100.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.netmask, "255.255.255.0");
+
+ // eth1 tests
+ BOOST_REQUIRE(device_configs.find("eth1") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(*device_configs.at("eth1").hw_cfg.port_index, 1u);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.dhcp, true);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.ip, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.gateway, "");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth1").ip_cfg.netmask, "");
+}
+
+BOOST_AUTO_TEST_CASE(test_valid_config_single_device) {
+ std::stringstream ss;
+ ss << "eth0: {pci-address: 0000:06:00.0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0 }";
+ auto device_configs = parse_config(ss);
+
+ // eth0 tests
+ BOOST_REQUIRE(device_configs.find("eth0") != device_configs.end());
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").hw_cfg.pci_address, "0000:06:00.0");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.dhcp, false);
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.ip, "192.168.100.10");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.gateway, "192.168.100.1");
+ BOOST_REQUIRE_EQUAL(device_configs.at("eth0").ip_cfg.netmask, "255.255.255.0");
+}
+
+BOOST_AUTO_TEST_CASE(test_unsupported_key) {
+ std::stringstream ss;
+ ss << "{eth0: { some_not_supported_tag: xxx, pci-address: 0000:06:00.0, ip: 192.168.100.10, "
+ "gateway: 192.168.100.1, netmask: 255.255.255.0 } , eth1: {pci-address: 0000:06:00.1, "
+ "dhcp: true } }";
+
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
+
+BOOST_AUTO_TEST_CASE(test_bad_yaml_syntax_if_thrown) {
+ std::stringstream ss;
+ ss << "some bad: [ yaml syntax }";
+ BOOST_REQUIRE_THROW(parse_config(ss), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_pci_address_and_port_index_if_thrown) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, port-index: 0, ip: 192.168.100.10, gateway: "
+ "192.168.100.1, netmask: 255.255.255.0 } , eth1: {pci-address: 0000:06:00.1, dhcp: true} "
+ "}";
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
+
+BOOST_AUTO_TEST_CASE(test_dhcp_and_ip_if_thrown) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, ip: 192.168.100.10, gateway: 192.168.100.1, netmask: "
+ "255.255.255.0, dhcp: true } , eth1: {pci-address: 0000:06:00.1, dhcp: true} }";
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
+
+BOOST_AUTO_TEST_CASE(test_ip_missing_if_thrown) {
+ std::stringstream ss;
+ ss << "{eth0: {pci-address: 0000:06:00.0, gateway: 192.168.100.1, netmask: 255.255.255.0 } , "
+ "eth1: {pci-address: 0000:06:00.1, dhcp: true} }";
+ BOOST_REQUIRE_THROW(parse_config(ss), config_exception);
+}
diff --git a/src/seastar/tests/unit/network_interface_test.cc b/src/seastar/tests/unit/network_interface_test.cc
new file mode 100644
index 000000000..08e90041d
--- /dev/null
+++ b/src/seastar/tests/unit/network_interface_test.cc
@@ -0,0 +1,88 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/net/ethernet.hh>
+#include <seastar/net/ip.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+static logger niflog("network_interface_test");
+
+SEASTAR_TEST_CASE(list_interfaces) {
+ // just verifying we have something. And can access all the stuff.
+ auto interfaces = engine().net().network_interfaces();
+ BOOST_REQUIRE_GT(interfaces.size(), 0);
+
+ for (auto& nif : interfaces) {
+ niflog.info("Iface: {}, index = {}, mtu = {}, loopback = {}, virtual = {}, up = {}",
+ nif.name(), nif.index(), nif.mtu(), nif.is_loopback(), nif.is_virtual(), nif.is_up()
+ );
+ if (nif.hardware_address().size() >= 6) {
+ niflog.info(" HW: {}", net::ethernet_address(nif.hardware_address().data()));
+ }
+ for (auto& addr : nif.addresses()) {
+ niflog.info(" Addr: {}", addr);
+ }
+ }
+
+ return make_ready_future();
+}
+
+SEASTAR_TEST_CASE(match_ipv6_scope) {
+ auto interfaces = engine().net().network_interfaces();
+
+ for (auto& nif : interfaces) {
+ if (nif.is_loopback()) {
+ continue;
+ }
+ auto i = std::find_if(nif.addresses().begin(), nif.addresses().end(), std::mem_fn(&net::inet_address::is_ipv6));
+ if (i == nif.addresses().end()) {
+ continue;
+ }
+
+ std::ostringstream ss;
+ ss << net::inet_address(i->as_ipv6_address()) << "%" << nif.name();
+ auto text = ss.str();
+
+ net::inet_address na(text);
+
+ BOOST_REQUIRE_EQUAL(na.as_ipv6_address(), i->as_ipv6_address());
+ // also verify that the inet_address itself matches
+ BOOST_REQUIRE_EQUAL(na, *i);
+ // and that inet_address _without_ scope matches.
+ BOOST_REQUIRE_EQUAL(net::inet_address(na.as_ipv6_address()), *i);
+ BOOST_REQUIRE_EQUAL(na.scope(), nif.index());
+ // and that they are not ipv4 addresses
+ BOOST_REQUIRE_THROW(i->as_ipv4_address(), std::invalid_argument);
+ BOOST_REQUIRE_THROW(na.as_ipv4_address(), std::invalid_argument);
+
+ niflog.info("Org: {}, Parsed: {}, Text: {}", *i, na, text);
+
+ }
+
+ return make_ready_future();
+}
diff --git a/src/seastar/tests/unit/noncopyable_function_test.cc b/src/seastar/tests/unit/noncopyable_function_test.cc
new file mode 100644
index 000000000..dc19d7525
--- /dev/null
+++ b/src/seastar/tests/unit/noncopyable_function_test.cc
@@ -0,0 +1,87 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/util/noncopyable_function.hh>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(basic_tests) {
+ struct s {
+ int f1(int x) const { return x + 1; }
+ int f2(int x) { return x + 2; }
+ static int f3(int x) { return x + 3; }
+ int operator()(int x) const { return x + 4; }
+ };
+ s obj, obj2;
+ auto fn1 = noncopyable_function<int (const s*, int)>(&s::f1);
+ auto fn2 = noncopyable_function<int (s*, int)>(&s::f2);
+ auto fn3 = noncopyable_function<int (int)>(&s::f3);
+ auto fn4 = noncopyable_function<int (int)>(std::move(obj2));
+ BOOST_REQUIRE_EQUAL(fn1(&obj, 1), 2);
+ BOOST_REQUIRE_EQUAL(fn2(&obj, 1), 3);
+ BOOST_REQUIRE_EQUAL(fn3(1), 4);
+ BOOST_REQUIRE_EQUAL(fn4(1), 5);
+}
+
+template <size_t Extra>
+struct payload {
+ static unsigned live;
+ char extra[Extra];
+ std::unique_ptr<int> v;
+ payload(int x) : v(std::make_unique<int>(x)) { ++live; }
+ payload(payload&& x) noexcept : v(std::move(x.v)) { ++live; }
+ void operator=(payload&&) = delete;
+ ~payload() { --live; }
+ int operator()() const { return *v; }
+};
+
+template <size_t Extra>
+unsigned payload<Extra>::live;
+
+template <size_t Extra>
+void do_move_tests() {
+ using payload = ::payload<Extra>;
+ auto f1 = noncopyable_function<int ()>(payload(3));
+ BOOST_REQUIRE_EQUAL(payload::live, 1u);
+ BOOST_REQUIRE_EQUAL(f1(), 3);
+ auto f2 = noncopyable_function<int ()>();
+ BOOST_CHECK_THROW(f2(), std::bad_function_call);
+ f2 = std::move(f1);
+ BOOST_CHECK_THROW(f1(), std::bad_function_call);
+ BOOST_REQUIRE_EQUAL(f2(), 3);
+ BOOST_REQUIRE_EQUAL(payload::live, 1u);
+ f2 = {};
+ BOOST_REQUIRE_EQUAL(payload::live, 0u);
+ BOOST_CHECK_THROW(f2(), std::bad_function_call);
+}
+
+BOOST_AUTO_TEST_CASE(small_move_tests) {
+ do_move_tests<1>();
+}
+
+BOOST_AUTO_TEST_CASE(large_move_tests) {
+ do_move_tests<1000>();
+}
+
diff --git a/src/seastar/tests/unit/output_stream_test.cc b/src/seastar/tests/unit/output_stream_test.cc
new file mode 100644
index 000000000..cbacd38aa
--- /dev/null
+++ b/src/seastar/tests/unit/output_stream_test.cc
@@ -0,0 +1,159 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/vector-data-sink.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/later.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/net/packet.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <vector>
+
+using namespace seastar;
+using namespace net;
+
+static sstring to_sstring(const packet& p) {
+ sstring res = uninitialized_string(p.len());
+ auto i = res.begin();
+ for (auto& frag : p.fragments()) {
+ i = std::copy(frag.base, frag.base + frag.size, i);
+ }
+ return res;
+}
+
+struct stream_maker {
+ bool _trim = false;
+ size_t _size;
+
+ stream_maker size(size_t size) && {
+ _size = size;
+ return std::move(*this);
+ }
+
+ stream_maker trim(bool do_trim) && {
+ _trim = do_trim;
+ return std::move(*this);
+ }
+
+ lw_shared_ptr<output_stream<char>> operator()(data_sink sink) {
+ return make_lw_shared<output_stream<char>>(std::move(sink), _size, _trim);
+ }
+};
+
+template <typename T, typename StreamConstructor>
+future<> assert_split(StreamConstructor stream_maker, std::initializer_list<T> write_calls,
+ std::vector<std::string> expected_split) {
+ static int i = 0;
+ BOOST_TEST_MESSAGE("checking split: " << i++);
+ auto sh_write_calls = make_lw_shared<std::vector<T>>(std::move(write_calls));
+ auto sh_expected_splits = make_lw_shared<std::vector<std::string>>(std::move(expected_split));
+ auto v = make_shared<std::vector<packet>>();
+ auto out = stream_maker(data_sink(std::make_unique<vector_data_sink>(*v)));
+
+ return do_for_each(sh_write_calls->begin(), sh_write_calls->end(), [out, sh_write_calls] (auto&& chunk) {
+ return out->write(chunk);
+ }).then([out, v, sh_expected_splits] {
+ return out->close().then([out, v, sh_expected_splits] {
+ BOOST_REQUIRE_EQUAL(v->size(), sh_expected_splits->size());
+ int i = 0;
+ for (auto&& chunk : *sh_expected_splits) {
+ BOOST_REQUIRE(to_sstring((*v)[i]) == chunk);
+ i++;
+ }
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_splitting) {
+ auto ctor = stream_maker().trim(false).size(4);
+ return now()
+ .then([=] { return assert_split(ctor, {"1"}, {"1"}); })
+ .then([=] { return assert_split(ctor, {"12", "3"}, {"123"}); })
+ .then([=] { return assert_split(ctor, {"12", "34"}, {"1234"}); })
+ .then([=] { return assert_split(ctor, {"12", "345"}, {"1234", "5"}); })
+ .then([=] { return assert_split(ctor, {"1234"}, {"1234"}); })
+ .then([=] { return assert_split(ctor, {"12345"}, {"12345"}); })
+ .then([=] { return assert_split(ctor, {"1234567890"}, {"1234567890"}); })
+ .then([=] { return assert_split(ctor, {"1", "23456"}, {"1234", "56"}); })
+ .then([=] { return assert_split(ctor, {"123", "4567"}, {"1234", "567"}); })
+ .then([=] { return assert_split(ctor, {"123", "45678"}, {"1234", "5678"}); })
+ .then([=] { return assert_split(ctor, {"123", "4567890"}, {"1234", "567890"}); })
+ .then([=] { return assert_split(ctor, {"1234", "567"}, {"1234", "567"}); })
+
+ .then([] { return assert_split(stream_maker().trim(false).size(3), {"1", "234567", "89"}, {"123", "4567", "89"}); })
+ .then([] { return assert_split(stream_maker().trim(false).size(3), {"1", "2345", "67"}, {"123", "456", "7"}); })
+ ;
+}
+
+SEASTAR_TEST_CASE(test_splitting_with_trimming) {
+ auto ctor = stream_maker().trim(true).size(4);
+ return now()
+ .then([=] { return assert_split(ctor, {"1"}, {"1"}); })
+ .then([=] { return assert_split(ctor, {"12", "3"}, {"123"}); })
+ .then([=] { return assert_split(ctor, {"12", "3456789"}, {"1234", "5678", "9"}); })
+ .then([=] { return assert_split(ctor, {"12", "3456789", "12"}, {"1234", "5678", "912"}); })
+ .then([=] { return assert_split(ctor, {"123456789"}, {"1234", "5678", "9"}); })
+ .then([=] { return assert_split(ctor, {"12345678"}, {"1234", "5678"}); })
+ .then([=] { return assert_split(ctor, {"12345678", "9"}, {"1234", "5678", "9"}); })
+ .then([=] { return assert_split(ctor, {"1234", "567890"}, {"1234", "5678", "90"}); })
+ ;
+}
+
+SEASTAR_TEST_CASE(test_flush_on_empty_buffer_does_not_push_empty_packet_down_stream) {
+ auto v = make_shared<std::vector<packet>>();
+ auto out = make_shared<output_stream<char>>(
+ data_sink(std::make_unique<vector_data_sink>(*v)), 8);
+
+ return out->flush().then([v, out] {
+ BOOST_REQUIRE(v->empty());
+ return out->close();
+ }).finally([out]{});
+}
+
+SEASTAR_THREAD_TEST_CASE(test_simple_write) {
+ auto vec = std::vector<net::packet>{};
+ auto out = output_stream<char>(data_sink(std::make_unique<vector_data_sink>(vec)), 8);
+
+ auto value1 = sstring("te");
+ out.write(value1).get();
+
+
+ auto value2 = sstring("st");
+ out.write(value2).get();
+
+ auto value3 = sstring("abcdefgh1234");
+ out.write(value3).get();
+
+ out.close().get();
+
+ auto value = value1 + value2 + value3;
+ auto packets = net::packet{};
+ for (auto& p : vec) {
+ packets.append(std::move(p));
+ }
+ packets.linearize();
+ auto buf = packets.release();
+ BOOST_REQUIRE_EQUAL(buf.size(), 1);
+ BOOST_REQUIRE_EQUAL(sstring(buf.front().get(), buf.front().size()), value);
+}
diff --git a/src/seastar/tests/unit/packet_test.cc b/src/seastar/tests/unit/packet_test.cc
new file mode 100644
index 000000000..a0bdd291e
--- /dev/null
+++ b/src/seastar/tests/unit/packet_test.cc
@@ -0,0 +1,121 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/net/packet.hh>
+#include <array>
+
+using namespace seastar;
+using namespace net;
+
+BOOST_AUTO_TEST_CASE(test_many_fragments) {
+ std::vector<char> expected;
+
+ auto append = [&expected] (net::packet p, char c, size_t n) {
+ auto tmp = temporary_buffer<char>(n);
+ std::fill_n(tmp.get_write(), n, c);
+ std::fill_n(std::back_inserter(expected), n, c);
+ return net::packet(std::move(p), std::move(tmp));
+ };
+
+ net::packet p;
+ p = append(std::move(p), 'a', 5);
+ p = append(std::move(p), 'b', 31);
+ p = append(std::move(p), 'c', 65);
+ p = append(std::move(p), 'c', 4096);
+ p = append(std::move(p), 'd', 4096);
+
+ auto verify = [&expected] (const net::packet& p) {
+ BOOST_CHECK_EQUAL(p.len(), expected.size());
+ auto expected_it = expected.begin();
+ for (auto&& frag : p.fragments()) {
+ BOOST_CHECK_LE(frag.size, static_cast<size_t>(expected.end() - expected_it));
+ BOOST_CHECK(std::equal(frag.base, frag.base + frag.size, expected_it));
+ expected_it += frag.size;
+ }
+ };
+
+ auto trim_front = [&expected] (net::packet& p, size_t n) {
+ p.trim_front(n);
+ expected.erase(expected.begin(), expected.begin() + n);
+ };
+
+ verify(p);
+
+ trim_front(p, 1);
+ verify(p);
+
+ trim_front(p, 6);
+ verify(p);
+
+ trim_front(p, 29);
+ verify(p);
+
+ trim_front(p, 1024);
+ verify(p);
+
+ net::packet p2;
+ p2 = append(std::move(p2), 'z', 9);
+ p2 = append(std::move(p2), 'x', 7);
+
+ p.append(std::move(p2));
+ verify(p);
+}
+
+BOOST_AUTO_TEST_CASE(test_headers_are_contiguous) {
+ using tcp_header = std::array<char, 20>;
+ using ip_header = std::array<char, 20>;
+ char data[1000] = {};
+ fragment f{data, sizeof(data)};
+ packet p(f);
+ p.prepend_header<tcp_header>();
+ p.prepend_header<ip_header>();
+ BOOST_REQUIRE_EQUAL(p.nr_frags(), 2u);
+}
+
+BOOST_AUTO_TEST_CASE(test_headers_are_contiguous_even_with_small_fragment) {
+ using tcp_header = std::array<char, 20>;
+ using ip_header = std::array<char, 20>;
+ char data[100] = {};
+ fragment f{data, sizeof(data)};
+ packet p(f);
+ p.prepend_header<tcp_header>();
+ p.prepend_header<ip_header>();
+ BOOST_REQUIRE_EQUAL(p.nr_frags(), 2u);
+}
+
+BOOST_AUTO_TEST_CASE(test_headers_are_contiguous_even_with_many_fragments) {
+ using tcp_header = std::array<char, 20>;
+ using ip_header = std::array<char, 20>;
+ char data[100] = {};
+ fragment f{data, sizeof(data)};
+ packet p(f);
+ for (int i = 0; i < 7; ++i) {
+ p.append(packet(f));
+ }
+ p.prepend_header<tcp_header>();
+ p.prepend_header<ip_header>();
+ BOOST_REQUIRE_EQUAL(p.nr_frags(), 9u);
+}
+
diff --git a/src/seastar/tests/unit/program_options_test.cc b/src/seastar/tests/unit/program_options_test.cc
new file mode 100644
index 000000000..408752d5d
--- /dev/null
+++ b/src/seastar/tests/unit/program_options_test.cc
@@ -0,0 +1,63 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <seastar/util/program-options.hh>
+
+#include <boost/program_options.hpp>
+#include <boost/test/included/unit_test.hpp>
+
+#include <initializer_list>
+#include <vector>
+
+namespace bpo = boost::program_options;
+
+using namespace seastar;
+
+static bpo::variables_map parse(const bpo::options_description& desc, std::initializer_list<const char*> args) {
+ std::vector<const char*> raw_args{"program_options_test"};
+ for (const char* arg : args) {
+ raw_args.push_back(arg);
+ }
+
+ bpo::variables_map vars;
+ bpo::store(bpo::parse_command_line(raw_args.size(), raw_args.data(), desc), vars);
+ bpo::notify(vars);
+
+ return vars;
+}
+
+BOOST_AUTO_TEST_CASE(string_map) {
+ bpo::options_description desc;
+ desc.add_options()
+ ("ages", bpo::value<program_options::string_map>());
+
+ const auto vars = parse(desc, {"--ages", "joe=15:sally=20", "--ages", "phil=18:joe=11"});
+ const auto& ages = vars["ages"].as<program_options::string_map>();
+
+ // `string_map` values can be specified multiple times. The last association takes precedence.
+ BOOST_REQUIRE_EQUAL(ages.at("joe"), "11");
+ BOOST_REQUIRE_EQUAL(ages.at("phil"), "18");
+ BOOST_REQUIRE_EQUAL(ages.at("sally"), "20");
+
+ BOOST_REQUIRE_THROW(parse(desc, {"--ages", "tim:"}), bpo::invalid_option_value);
+}
diff --git a/src/seastar/tests/unit/queue_test.cc b/src/seastar/tests/unit/queue_test.cc
new file mode 100644
index 000000000..a8405e0ae
--- /dev/null
+++ b/src/seastar/tests/unit/queue_test.cc
@@ -0,0 +1,70 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2018 ScyllaDB
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/queue.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_queue_pop_after_abort) {
+ return async([] {
+ queue<int> q(1);
+ bool exception = false;
+ bool timer = false;
+ future<> done = make_ready_future();
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ done = sleep(1ms).then([&] {
+ timer = true;
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ });
+ try {
+ q.pop_eventually().get();
+ } catch(...) {
+ exception = !timer;
+ }
+ BOOST_REQUIRE(exception);
+ done.get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_queue_push_abort) {
+ return async([] {
+ queue<int> q(1);
+ bool exception = false;
+ bool timer = false;
+ future<> done = make_ready_future();
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ done = sleep(1ms).then([&] {
+ timer = true;
+ q.abort(std::make_exception_ptr(std::runtime_error("boom")));
+ });
+ try {
+ q.push_eventually(1).get();
+ } catch(...) {
+ exception = !timer;
+ }
+ BOOST_REQUIRE(exception);
+ done.get();
+ });
+}
diff --git a/src/seastar/tests/unit/request_parser_test.cc b/src/seastar/tests/unit/request_parser_test.cc
new file mode 100644
index 000000000..e8d694561
--- /dev/null
+++ b/src/seastar/tests/unit/request_parser_test.cc
@@ -0,0 +1,74 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB.
+ */
+
+#include <seastar/core/ragel.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/temporary_buffer.hh>
+#include <seastar/http/request.hh>
+#include <seastar/http/request_parser.hh>
+#include <seastar/testing/test_case.hh>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+using namespace seastar;
+
+SEASTAR_TEST_CASE(test_header_parsing) {
+ struct test_set {
+ sstring msg;
+ bool parsable;
+ sstring header_name = "";
+ sstring header_value = "";
+
+ temporary_buffer<char> buf() {
+ return temporary_buffer<char>(msg.c_str(), msg.size());
+ }
+ };
+
+ std::vector<test_set> tests = {
+ { "GET /test HTTP/1.1\r\nHost: test\r\n\r\n", true, "Host", "test" },
+ { "GET /hello HTTP/1.0\r\nHeader: Field\r\n\r\n", true, "Header", "Field" },
+ { "GET /hello HTTP/1.0\r\nHeader: \r\n\r\n", true, "Header", "" },
+ { "GET /hello HTTP/1.0\r\nHeader: f i e l d \r\n\r\n", true, "Header", "f i e l d" },
+ { "GET /hello HTTP/1.0\r\nHeader: fiel\r\n d\r\n\r\n", true, "Header", "fiel d" },
+ { "GET /hello HTTP/1.0\r\ntchars.^_`|123: printable!@#%^&*()obs_text\x80\x81\xff\r\n\r\n", true,
+ "tchars.^_`|123", "printable!@#%^&*()obs_text\x80\x81\xff" },
+ { "GET /hello HTTP/1.0\r\nHeader: Field\r\nHeader: Field2\r\n\r\n", true, "Header", "Field,Field2" },
+ { "GET /hello HTTP/1.0\r\n\r\n", true },
+ { "GET /hello HTTP/1.0\r\nHeader : Field\r\n\r\n", false },
+ { "GET /hello HTTP/1.0\r\nHeader Field\r\n\r\n", false },
+ { "GET /hello HTTP/1.0\r\nHeader@: Field\r\n\r\n", false },
+ { "GET /hello HTTP/1.0\r\nHeader: fiel\r\nd \r\n\r\n", false }
+ };
+
+ http_request_parser parser;
+ for (auto& tset : tests) {
+ parser.init();
+ BOOST_REQUIRE(parser(tset.buf()).get0().has_value());
+ BOOST_REQUIRE_NE(parser.failed(), tset.parsable);
+ if (tset.parsable) {
+ auto req = parser.get_parsed_request();
+ BOOST_REQUIRE_EQUAL(req->get_header(std::move(tset.header_name)), std::move(tset.header_value));
+ }
+ }
+ return make_ready_future<>();
+}
diff --git a/src/seastar/tests/unit/rpc_test.cc b/src/seastar/tests/unit/rpc_test.cc
new file mode 100644
index 000000000..0b729ada1
--- /dev/null
+++ b/src/seastar/tests/unit/rpc_test.cc
@@ -0,0 +1,1199 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2016 ScyllaDB
+ */
+
+#include "loopback_socket.hh"
+#include <seastar/rpc/rpc.hh>
+#include <seastar/rpc/rpc_types.hh>
+#include <seastar/rpc/lz4_compressor.hh>
+#include <seastar/rpc/lz4_fragmented_compressor.hh>
+#include <seastar/rpc/multi_algo_compressor_factory.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/distributed.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/defer.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+struct serializer {
+};
+
+template <typename T, typename Output>
+inline
+void write_arithmetic_type(Output& out, T v) {
+ static_assert(std::is_arithmetic<T>::value, "must be arithmetic type");
+ return out.write(reinterpret_cast<const char*>(&v), sizeof(T));
+}
+
+template <typename T, typename Input>
+inline
+T read_arithmetic_type(Input& in) {
+ static_assert(std::is_arithmetic<T>::value, "must be arithmetic type");
+ T v;
+ in.read(reinterpret_cast<char*>(&v), sizeof(T));
+ return v;
+}
+
+template <typename Output>
+inline void write(serializer, Output& output, int32_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, uint32_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, int64_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, uint64_t v) { return write_arithmetic_type(output, v); }
+template <typename Output>
+inline void write(serializer, Output& output, double v) { return write_arithmetic_type(output, v); }
+template <typename Input>
+inline int32_t read(serializer, Input& input, rpc::type<int32_t>) { return read_arithmetic_type<int32_t>(input); }
+template <typename Input>
+inline uint32_t read(serializer, Input& input, rpc::type<uint32_t>) { return read_arithmetic_type<uint32_t>(input); }
+template <typename Input>
+inline uint64_t read(serializer, Input& input, rpc::type<uint64_t>) { return read_arithmetic_type<uint64_t>(input); }
+template <typename Input>
+inline uint64_t read(serializer, Input& input, rpc::type<int64_t>) { return read_arithmetic_type<int64_t>(input); }
+template <typename Input>
+inline double read(serializer, Input& input, rpc::type<double>) { return read_arithmetic_type<double>(input); }
+
+template <typename Output>
+inline void write(serializer, Output& out, const sstring& v) {
+ write_arithmetic_type(out, uint32_t(v.size()));
+ out.write(v.c_str(), v.size());
+}
+
+template <typename Input>
+inline sstring read(serializer, Input& in, rpc::type<sstring>) {
+ auto size = read_arithmetic_type<uint32_t>(in);
+ sstring ret = uninitialized_string(size);
+ in.read(ret.data(), size);
+ return ret;
+}
+
+using test_rpc_proto = rpc::protocol<serializer>;
+using make_socket_fn = std::function<seastar::socket ()>;
+
+struct rpc_loopback_error_injector : public loopback_error_injector {
+ int _x = 0;
+ bool server_rcv_error() override {
+ return _x++ >= 50;
+ }
+};
+
+class rpc_socket_impl : public ::net::socket_impl {
+ promise<connected_socket> _p;
+ bool _connect;
+ loopback_socket_impl _socket;
+ rpc_loopback_error_injector _error_injector;
+public:
+ rpc_socket_impl(loopback_connection_factory& factory, bool connect, bool inject_error)
+ : _connect(connect),
+ _socket(factory, inject_error ? &_error_injector : nullptr) {
+ }
+ virtual future<connected_socket> connect(socket_address sa, socket_address local, transport proto = transport::TCP) override {
+ return _connect ? _socket.connect(sa, local, proto) : _p.get_future();
+ }
+ virtual void set_reuseaddr(bool reuseaddr) override {}
+ virtual bool get_reuseaddr() const override { return false; };
+ virtual void shutdown() override {
+ if (_connect) {
+ _socket.shutdown();
+ } else {
+ _p.set_exception(std::make_exception_ptr(std::system_error(ECONNABORTED, std::system_category())));
+ }
+ }
+};
+
+struct rpc_test_config {
+ rpc::resource_limits resource_limits = {};
+ rpc::server_options server_options = {};
+ bool connect = true;
+ bool inject_error = false;
+};
+
+template<typename MsgType = int>
+class rpc_test_env {
+ struct rpc_test_service {
+ test_rpc_proto _proto;
+ test_rpc_proto::server _server;
+ std::vector<MsgType> _handlers;
+
+ rpc_test_service() = delete;
+ explicit rpc_test_service(const rpc_test_config& cfg, loopback_connection_factory& lcf)
+ : _proto(serializer())
+ , _server(_proto, cfg.server_options, lcf.get_server_socket(), cfg.resource_limits)
+ { }
+
+ test_rpc_proto& proto() {
+ return _proto;
+ }
+
+ test_rpc_proto::server& server() {
+ return _server;
+ }
+
+ future<> stop() {
+ return parallel_for_each(_handlers, [this] (auto t) {
+ return proto().unregister_handler(t);
+ }).finally([this] {
+ return server().stop();
+ });
+ }
+
+ template<typename Func>
+ auto register_handler(MsgType t, scheduling_group sg, Func func) {
+ _handlers.emplace_back(t);
+ return proto().register_handler(t, sg, std::move(func));
+ }
+
+ future<> unregister_handler(MsgType t) {
+ auto it = std::find(_handlers.begin(), _handlers.end(), t);
+ assert(it != _handlers.end());
+ _handlers.erase(it);
+ return proto().unregister_handler(t);
+ }
+ };
+
+ rpc_test_config _cfg;
+ loopback_connection_factory _lcf;
+ std::unique_ptr<sharded<rpc_test_service>> _service;
+
+public:
+ rpc_test_env() = delete;
+ explicit rpc_test_env(rpc_test_config cfg)
+ : _cfg(cfg), _service(std::make_unique<sharded<rpc_test_service>>())
+ {
+ }
+
+ using test_fn = std::function<future<> (rpc_test_env<MsgType>& env)>;
+ static future<> do_with(rpc_test_config cfg, test_fn&& func) {
+ return seastar::do_with(rpc_test_env(cfg), [func] (rpc_test_env<MsgType>& env) {
+ return env.start().then([&env, func] {
+ return func(env);
+ }).finally([&env] {
+ return env.stop();
+ });
+ });
+ }
+
+ using thread_test_fn = std::function<void (rpc_test_env<MsgType>& env)>;
+ static future<> do_with_thread(rpc_test_config cfg, thread_test_fn&& func) {
+ return do_with(std::move(cfg), [func] (rpc_test_env<MsgType>& env) {
+ return seastar::async([&env, func] {
+ func(env);
+ });
+ });
+ }
+
+ using thread_test_fn_with_client = std::function<void (rpc_test_env<MsgType>& env, test_rpc_proto::client& cl)>;
+ static future<> do_with_thread(rpc_test_config cfg, rpc::client_options co, thread_test_fn_with_client&& func) {
+ return do_with(std::move(cfg), [func, co = std::move(co)] (rpc_test_env<MsgType>& env) {
+ return seastar::async([&env, func, co = std::move(co)] {
+ test_rpc_proto::client cl(env.proto(), co, env.make_socket(), ipv4_addr());
+ auto stop = defer([&] { cl.stop().get(); });
+ func(env, cl);
+ });
+ });
+ }
+
+ static future<> do_with_thread(rpc_test_config cfg, thread_test_fn_with_client&& func) {
+ return do_with_thread(std::move(cfg), rpc::client_options(), std::move(func));
+ }
+
+ auto make_socket() {
+ return seastar::socket(std::make_unique<rpc_socket_impl>(_lcf, _cfg.connect, _cfg.inject_error));
+ };
+
+ test_rpc_proto& proto() {
+ return local_service().proto();
+ }
+
+ test_rpc_proto::server& server() {
+ return local_service().server();
+ }
+
+ template<typename Func>
+ future<> register_handler(MsgType t, scheduling_group sg, Func func) {
+ return _service->invoke_on_all([t, func = std::move(func), sg] (rpc_test_service& s) mutable {
+ s.register_handler(t, sg, std::move(func));
+ });
+ }
+
+ template<typename Func>
+ future<> register_handler(MsgType t, Func func) {
+ return register_handler(t, scheduling_group(), std::move(func));
+ }
+
+ future<> unregister_handler(MsgType t) {
+ return _service->invoke_on_all([t] (rpc_test_service& s) mutable {
+ return s.unregister_handler(t);
+ });
+ }
+
+private:
+ rpc_test_service& local_service() {
+ return _service->local();
+
+ }
+
+ future<> start() {
+ return _service->start(std::cref(_cfg), std::ref(_lcf));
+ }
+
+ future<> stop() {
+ return _service->stop().then([this] {
+ return _lcf.destroy_all_shards();
+ });
+ }
+};
+
+struct cfactory : rpc::compressor::factory {
+ mutable int use_compression = 0;
+ const sstring name;
+ cfactory(sstring name_ = "LZ4") : name(std::move(name_)) {}
+ const sstring& supported() const override {
+ return name;
+ }
+ class mylz4 : public rpc::lz4_compressor {
+ sstring _name;
+ public:
+ mylz4(const sstring& n) : _name(n) {}
+ sstring name() const override {
+ return _name;
+ }
+ };
+ std::unique_ptr<rpc::compressor> negotiate(sstring feature, bool is_server) const override {
+ if (feature == name) {
+ use_compression++;
+ return std::make_unique<mylz4>(name);
+ } else {
+ return nullptr;
+ }
+ }
+};
+
+SEASTAR_TEST_CASE(test_rpc_connect) {
+ std::vector<future<>> fs;
+
+ for (auto i = 0; i < 2; i++) {
+ for (auto j = 0; j < 4; j++) {
+ auto factory = std::make_unique<cfactory>();
+ rpc::server_options so;
+ rpc::client_options co;
+ if (i == 1) {
+ so.compressor_factory = factory.get();
+ }
+ if (j & 1) {
+ co.compressor_factory = factory.get();
+ }
+ co.send_timeout_data = j & 2;
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ auto f = rpc_test_env<>::do_with_thread(cfg, co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ auto result = sum(c1, 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }).handle_exception([] (auto ep) {
+ BOOST_FAIL("No exception expected");
+ }).finally([factory = std::move(factory), i, j = j & 1] {
+ if (i == 1 && j == 1) {
+ BOOST_REQUIRE_EQUAL(factory->use_compression, 2);
+ } else {
+ BOOST_REQUIRE_EQUAL(factory->use_compression, 0);
+ }
+ });
+ fs.emplace_back(std::move(f));
+ }
+ }
+ return when_all(fs.begin(), fs.end()).discard_result();
+}
+
+SEASTAR_TEST_CASE(test_rpc_connect_multi_compression_algo) {
+ auto factory1 = std::make_unique<cfactory>();
+ auto factory2 = std::make_unique<cfactory>("LZ4NEW");
+ rpc::server_options so;
+ rpc::client_options co;
+ static rpc::multi_algo_compressor_factory server({factory1.get(), factory2.get()});
+ static rpc::multi_algo_compressor_factory client({factory2.get(), factory1.get()});
+ so.compressor_factory = &server;
+ co.compressor_factory = &client;
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ return rpc_test_env<>::do_with_thread(cfg, co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ auto result = sum(c1, 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }).finally([factory1 = std::move(factory1), factory2 = std::move(factory2)] {
+ BOOST_REQUIRE_EQUAL(factory1->use_compression, 0);
+ BOOST_REQUIRE_EQUAL(factory2->use_compression, 2);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_connect_abort) {
+ rpc_test_config cfg;
+ cfg.connect = false;
+ return rpc_test_env<>::do_with_thread(cfg, [] (rpc_test_env<>& env) {
+ test_rpc_proto::client c1(env.proto(), {}, env.make_socket(), ipv4_addr());
+ env.register_handler(1, []() { return make_ready_future<>(); }).get();
+ auto f = env.proto().make_client<void ()>(1);
+ c1.stop().get0();
+ try {
+ f(c1).get0();
+ BOOST_REQUIRE(false);
+ } catch (...) {}
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_cancel) {
+ using namespace std::chrono_literals;
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ bool rpc_executed = false;
+ int good = 0;
+ promise<> handler_called;
+ future<> f_handler_called = handler_called.get_future();
+ env.register_handler(1, [&rpc_executed, &handler_called] {
+ handler_called.set_value(); rpc_executed = true; return sleep(1ms);
+ }).get();
+ auto call = env.proto().make_client<void ()>(1);
+ rpc::cancellable cancel;
+ auto f = call(c1, cancel);
+ // cancel send side
+ cancel.cancel();
+ try {
+ f.get();
+ } catch(rpc::canceled_error&) {
+ good += !rpc_executed;
+ };
+ f = call(c1, cancel);
+ // cancel wait side
+ f_handler_called.then([&cancel] {
+ cancel.cancel();
+ }).get();
+ try {
+ f.get();
+ } catch(rpc::canceled_error&) {
+ good += 10*rpc_executed;
+ };
+ BOOST_REQUIRE_EQUAL(good, 11);
+ });
+}
+
+SEASTAR_TEST_CASE(test_message_to_big) {
+ rpc_test_config cfg;
+ cfg.resource_limits = {0, 1, 100};
+ return rpc_test_env<>::do_with_thread(cfg, [] (rpc_test_env<>& env, test_rpc_proto::client& c) {
+ bool good = true;
+ env.register_handler(1, [&] (sstring payload) mutable {
+ good = false;
+ }).get();
+ auto call = env.proto().make_client<void (sstring)>(1);
+ try {
+ call(c, uninitialized_string(101)).get();
+ good = false;
+ } catch(std::runtime_error& err) {
+ } catch(...) {
+ good = false;
+ }
+ BOOST_REQUIRE_EQUAL(good, true);
+ });
+}
+
+struct stream_test_result {
+ bool client_source_closed = false;
+ bool server_source_closed = false;
+ bool sink_exception = false;
+ bool sink_close_exception = false;
+ bool source_done_exception = false;
+ bool server_done_exception = false;
+ bool client_stop_exception = false;
+ int server_sum = 0;
+};
+
+future<stream_test_result> stream_test_func(rpc_test_env<>& env, bool stop_client, bool expect_connection_error = false) {
+ return seastar::async([&env, stop_client, expect_connection_error] {
+ stream_test_result r;
+ test_rpc_proto::client c(env.proto(), {}, env.make_socket(), ipv4_addr());
+ future<> server_done = make_ready_future();
+ env.register_handler(1, [&](int i, rpc::source<int> source) {
+ BOOST_REQUIRE_EQUAL(i, 666);
+ auto sink = source.make_sink<serializer, sstring>();
+ auto sink_loop = seastar::async([sink] () mutable {
+ for (auto i = 0; i < 100; i++) {
+ sink("seastar").get();
+ sleep(std::chrono::milliseconds(1)).get();
+ }
+ }).finally([sink] () mutable {
+ return sink.flush();
+ }).finally([sink] () mutable {
+ return sink.close();
+ }).finally([sink] {});
+
+ auto source_loop = seastar::async([source, &r] () mutable {
+ while (!r.server_source_closed) {
+ auto data = source().get0();
+ if (data) {
+ r.server_sum += std::get<0>(*data);
+ } else {
+ r.server_source_closed = true;
+ try {
+ // check that reading after eos does not crash
+ // and throws correct exception
+ source().get();
+ } catch (rpc::stream_closed& ex) {
+ // expected
+ } catch (...) {
+ BOOST_FAIL("wrong exception on reading from a stream after eos");
+ }
+ }
+ }
+ });
+ server_done = when_all_succeed(std::move(sink_loop), std::move(source_loop)).discard_result();
+ return sink;
+ }).get();
+ auto call = env.proto().make_client<rpc::source<sstring> (int, rpc::sink<int>)>(1);
+ auto x = [&] {
+ try {
+ return c.make_stream_sink<serializer, int>(env.make_socket()).get0();
+ } catch (...) {
+ c.stop().get();
+ throw;
+ }
+ };
+ auto sink = x();
+ auto source = call(c, 666, sink).get0();
+ auto source_done = seastar::async([&] {
+ while (!r.client_source_closed) {
+ auto data = source().get0();
+ if (data) {
+ BOOST_REQUIRE_EQUAL(std::get<0>(*data), "seastar");
+ } else {
+ r.client_source_closed = true;
+ }
+ }
+ });
+ auto check_exception = [] (auto f) {
+ try {
+ f.get();
+ } catch (...) {
+ return true;
+ }
+ return false;
+ };
+ future<> stop_client_future = make_ready_future();
+ // With a connection error sink() will eventually fail, but we
+ // cannot guarantee when.
+ int max = expect_connection_error ? std::numeric_limits<int>::max() : 101;
+ for (int i = 1; i < max; i++) {
+ if (stop_client && i == 50) {
+ // stop client while stream is in use
+ stop_client_future = c.stop();
+ }
+ sleep(std::chrono::milliseconds(1)).get();
+ r.sink_exception = check_exception(sink(i));
+ if (r.sink_exception) {
+ break;
+ }
+ }
+ r.sink_close_exception = check_exception(sink.close());
+ r.source_done_exception = check_exception(std::move(source_done));
+ r.server_done_exception = check_exception(std::move(server_done));
+ r.client_stop_exception = check_exception(!stop_client ? c.stop() : std::move(stop_client_future));
+ return r;
+ });
+}
+
+SEASTAR_TEST_CASE(test_stream_simple) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ return rpc_test_env<>::do_with(cfg, [] (rpc_test_env<>& env) {
+ return stream_test_func(env, false).then([] (stream_test_result r) {
+ BOOST_REQUIRE(r.client_source_closed);
+ BOOST_REQUIRE(r.server_source_closed);
+ BOOST_REQUIRE(r.server_sum == 5050);
+ BOOST_REQUIRE(!r.sink_exception);
+ BOOST_REQUIRE(!r.sink_close_exception);
+ BOOST_REQUIRE(!r.source_done_exception);
+ BOOST_REQUIRE(!r.server_done_exception);
+ BOOST_REQUIRE(!r.client_stop_exception);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_stream_stop_client) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ return rpc_test_env<>::do_with(cfg, [] (rpc_test_env<>& env) {
+ return stream_test_func(env, true).then([] (stream_test_result r) {
+ BOOST_REQUIRE(!r.client_source_closed);
+ BOOST_REQUIRE(!r.server_source_closed);
+ BOOST_REQUIRE(r.sink_exception);
+ BOOST_REQUIRE(r.sink_close_exception);
+ BOOST_REQUIRE(r.source_done_exception);
+ BOOST_REQUIRE(r.server_done_exception);
+ BOOST_REQUIRE(!r.client_stop_exception);
+ });
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_stream_connection_error) {
+ rpc::server_options so;
+ so.streaming_domain = rpc::streaming_domain_type(1);
+ rpc_test_config cfg;
+ cfg.server_options = so;
+ cfg.inject_error = true;
+ return rpc_test_env<>::do_with(cfg, [] (rpc_test_env<>& env) {
+ return stream_test_func(env, false, true).then([] (stream_test_result r) {
+ BOOST_REQUIRE(!r.client_source_closed);
+ BOOST_REQUIRE(!r.server_source_closed);
+ BOOST_REQUIRE(r.sink_exception);
+ BOOST_REQUIRE(r.sink_close_exception);
+ BOOST_REQUIRE(r.source_done_exception);
+ BOOST_REQUIRE(r.server_done_exception);
+ BOOST_REQUIRE(!r.client_stop_exception);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_scheduling) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ auto sg = create_scheduling_group("rpc", 100).get0();
+ env.register_handler(1, sg, [] () {
+ return make_ready_future<unsigned>(internal::scheduling_group_index(current_scheduling_group()));
+ }).get();
+ auto call_sg_id = env.proto().make_client<unsigned ()>(1);
+ auto id = call_sg_id(c1).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg));
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_rpc_scheduling_connection_based) {
+ auto sg1 = create_scheduling_group("sg1", 100).get0();
+ auto sg1_kill = defer([&] { destroy_scheduling_group(sg1).get(); });
+ auto sg2 = create_scheduling_group("sg2", 100).get0();
+ auto sg2_kill = defer([&] { destroy_scheduling_group(sg2).get(); });
+ rpc::resource_limits limits;
+ limits.isolate_connection = [sg1, sg2] (sstring cookie) {
+ auto sg = current_scheduling_group();
+ if (cookie == "sg1") {
+ sg = sg1;
+ } else if (cookie == "sg2") {
+ sg = sg2;
+ }
+ rpc::isolation_config cfg;
+ cfg.sched_group = sg;
+ return cfg;
+ };
+ rpc_test_config cfg;
+ cfg.resource_limits = limits;
+ rpc_test_env<>::do_with_thread(cfg, [sg1, sg2] (rpc_test_env<>& env) {
+ rpc::client_options co1;
+ co1.isolation_cookie = "sg1";
+ test_rpc_proto::client c1(env.proto(), co1, env.make_socket(), ipv4_addr());
+ rpc::client_options co2;
+ co2.isolation_cookie = "sg2";
+ test_rpc_proto::client c2(env.proto(), co2, env.make_socket(), ipv4_addr());
+ env.register_handler(1, [] {
+ return make_ready_future<unsigned>(internal::scheduling_group_index(current_scheduling_group()));
+ }).get();
+ auto call_sg_id = env.proto().make_client<unsigned ()>(1);
+ unsigned id;
+ id = call_sg_id(c1).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg1));
+ id = call_sg_id(c2).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg2));
+ c1.stop().get();
+ c2.stop().get();
+ }).get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_rpc_scheduling_connection_based_compatibility) {
+ auto sg1 = create_scheduling_group("sg1", 100).get0();
+ auto sg1_kill = defer([&] { destroy_scheduling_group(sg1).get(); });
+ auto sg2 = create_scheduling_group("sg2", 100).get0();
+ auto sg2_kill = defer([&] { destroy_scheduling_group(sg2).get(); });
+ rpc::resource_limits limits;
+ limits.isolate_connection = [sg1, sg2] (sstring cookie) {
+ auto sg = current_scheduling_group();
+ if (cookie == "sg1") {
+ sg = sg1;
+ } else if (cookie == "sg2") {
+ sg = sg2;
+ }
+ rpc::isolation_config cfg;
+ cfg.sched_group = sg;
+ return cfg;
+ };
+ rpc_test_config cfg;
+ cfg.resource_limits = limits;
+ rpc_test_env<>::do_with_thread(cfg, [sg1, sg2] (rpc_test_env<>& env) {
+ rpc::client_options co1;
+ co1.isolation_cookie = "sg1";
+ test_rpc_proto::client c1(env.proto(), co1, env.make_socket(), ipv4_addr());
+ rpc::client_options co2;
+ co2.isolation_cookie = "sg2";
+ test_rpc_proto::client c2(env.proto(), co2, env.make_socket(), ipv4_addr());
+ // An old client, that doesn't have an isolation cookie
+ rpc::client_options co3;
+ test_rpc_proto::client c3(env.proto(), co3, env.make_socket(), ipv4_addr());
+ // A server that uses sg1 if the client is old
+ env.register_handler(1, sg1, [] () {
+ return make_ready_future<unsigned>(internal::scheduling_group_index(current_scheduling_group()));
+ }).get();
+ auto call_sg_id = env.proto().make_client<unsigned ()>(1);
+ unsigned id;
+ id = call_sg_id(c1).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg1));
+ id = call_sg_id(c2).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg2));
+ id = call_sg_id(c3).get0();
+ BOOST_REQUIRE(id == internal::scheduling_group_index(sg1));
+ c1.stop().get();
+ c2.stop().get();
+ c3.stop().get();
+ }).get();
+}
+
+void test_compressor(std::function<std::unique_ptr<seastar::rpc::compressor>()> compressor_factory) {
+ using namespace seastar::rpc;
+
+ auto linearize = [&] (const auto& buffer) {
+ return seastar::visit(buffer.bufs,
+ [] (const temporary_buffer<char>& buf) {
+ return buf.clone();
+ },
+ [&] (const std::vector<temporary_buffer<char>>& bufs) {
+ auto buf = temporary_buffer<char>(buffer.size);
+ auto dst = buf.get_write();
+ for (auto& b : bufs) {
+ dst = std::copy_n(b.get(), b.size(), dst);
+ }
+ return buf;
+ }
+ );
+ };
+
+ auto split_buffer = [&] (temporary_buffer<char> b, size_t chunk_size) {
+ std::vector<temporary_buffer<char>> bufs;
+ auto src = b.get();
+ auto n = b.size();
+ while (n) {
+ auto this_chunk = std::min(chunk_size, n);
+ bufs.emplace_back(this_chunk);
+ std::copy_n(src, this_chunk, bufs.back().get_write());
+ src += this_chunk;
+ n -= this_chunk;
+ }
+ return bufs;
+ };
+
+ auto clone = [&] (const auto& buffer) {
+ auto c = std::decay_t<decltype(buffer)>();
+ c.size = buffer.size;
+ c.bufs = seastar::visit(buffer.bufs,
+ [] (const temporary_buffer<char>& buf) -> decltype(c.bufs) {
+ return buf.clone();
+ },
+ [] (const std::vector<temporary_buffer<char>>& bufs) -> decltype(c.bufs) {
+ std::vector<temporary_buffer<char>> c;
+ c.reserve(bufs.size());
+ for (auto& b : bufs) {
+ c.emplace_back(b.clone());
+ }
+ return c;
+ }
+ );
+ return c;
+ };
+
+ auto compressor = compressor_factory();
+
+ std::vector<std::tuple<sstring, size_t, snd_buf>> inputs;
+
+ auto& eng = testing::local_random_engine;
+ auto dist = std::uniform_int_distribution<char>();
+
+ auto snd = snd_buf(1);
+ *snd.front().get_write() = 'a';
+ inputs.emplace_back("one byte, no headroom", 0, std::move(snd));
+
+ snd = snd_buf(1);
+ *snd.front().get_write() = 'a';
+ inputs.emplace_back("one byte, 128k of headroom", 128 * 1024, std::move(snd));
+
+ auto gen_fill = [&](size_t s, sstring msg, std::optional<size_t> split = {}) {
+ auto buf = temporary_buffer<char>(s);
+ std::fill_n(buf.get_write(), s, 'a');
+
+ auto snd = snd_buf();
+ snd.size = s;
+ if (split) {
+ snd.bufs = split_buffer(buf.clone(), *split);
+ } else {
+ snd.bufs = buf.clone();
+ }
+ inputs.emplace_back(msg, 0, std::move(snd));
+ };
+
+ gen_fill(16 * 1024, "single 16 kB buffer of \'a\'");
+
+ auto gen_rand = [&](size_t s, sstring msg, std::optional<size_t> split = {}) {
+ auto buf = temporary_buffer<char>(s);
+ std::generate_n(buf.get_write(), s, [&] { return dist(eng); });
+
+ auto snd = snd_buf();
+ snd.size = s;
+ if (split) {
+ snd.bufs = split_buffer(buf.clone(), *split);
+ } else {
+ snd.bufs = buf.clone();
+ }
+ inputs.emplace_back(msg, 0, std::move(snd));
+ };
+
+ gen_rand(16 * 1024, "single 16 kB buffer of random");
+
+ auto gen_text = [&](size_t s, sstring msg, std::optional<size_t> split = {}) {
+ static const std::string_view text = "The quick brown fox wants bananas for his long term health but sneaks bacon behind his wife's back. ";
+
+ auto buf = temporary_buffer<char>(s);
+ size_t n = 0;
+ while (n < s) {
+ auto rem = std::min(s - n, text.size());
+ std::copy(text.data(), text.data() + rem, buf.get_write() + n);
+ n += rem;
+ }
+
+ auto snd = snd_buf();
+ snd.size = s;
+ if (split) {
+ snd.bufs = split_buffer(buf.clone(), *split);
+ } else {
+ snd.bufs = buf.clone();
+ }
+ inputs.emplace_back(msg, 0, std::move(snd));
+ };
+
+
+ for (auto s : { 1, 4, 8 }) {
+ for (auto ss : { 32, 64, 128, 48, 56, 246, 511 }) {
+ gen_fill(s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} kB - {}", s, ss, ss), ss * 1024 - ss);
+ gen_fill(s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} kB", s, ss), ss * 1024);
+ gen_rand(s * 1024 * 1024, format("{} MB buffer of random split into {} kB", s, ss), ss * 1024);
+
+ gen_fill(s * 1024 * 1024 + 1, format("{} MB + 1B buffer of \'a\' split into {} kB", s, ss), ss * 1024);
+ gen_rand(s * 1024 * 1024 + 1, format("{} MB + 1B buffer of random split into {} kB", s, ss), ss * 1024);
+ }
+
+ for (auto ss : { 128, 246, 511, 3567, 2*1024, 8*1024 }) {
+ gen_fill(s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} B", s, ss), ss);
+ gen_rand(s * 1024 * 1024, format("{} MB buffer of random split into {} B", s, ss), ss);
+ gen_text(s * 1024 * 1024, format("{} MB buffer of text split into {} B", s, ss), ss);
+ gen_fill(s * 1024 * 1024 - ss, format("{} MB - {}B buffer of \'a\' split into {} B", s, ss, ss), ss);
+ gen_rand(s * 1024 * 1024 - ss, format("{} MB - {}B buffer of random split into {} B", s, ss, ss), ss);
+ gen_text(s * 1024 * 1024 - ss, format("{} MB - {}B buffer of random split into {} B", s, ss, ss), ss);
+ }
+ }
+
+ for (auto s : { 64*1024 + 5670, 16*1024 + 3421, 32*1024 - 321 }) {
+ gen_fill(s, format("{} bytes buffer of \'a\'", s));
+ gen_rand(s, format("{} bytes buffer of random", s));
+ gen_text(s, format("{} bytes buffer of text", s));
+ }
+
+ std::vector<std::tuple<sstring, std::function<rcv_buf(snd_buf)>>> transforms {
+ { "identity", [] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = std::move(snd.bufs);
+ return rcv;
+ } },
+ { "linearized", [&linearize] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = linearize(snd);
+ return rcv;
+ } },
+ { "split 1 B", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 1);
+ return rcv;
+ } },
+ { "split 129 B", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 129);
+ return rcv;
+ } },
+ { "split 4 kB", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 4096);
+ return rcv;
+ } },
+ { "split 4 kB - 128", [&] (snd_buf snd) {
+ rcv_buf rcv;
+ rcv.size = snd.size;
+ rcv.bufs = split_buffer(linearize(snd), 4096 - 128);
+ return rcv;
+ } },
+ };
+
+ auto sanity_check = [&] (const auto& buffer) {
+ auto actual_size = seastar::visit(buffer.bufs,
+ [] (const temporary_buffer<char>& buf) {
+ return buf.size();
+ },
+ [] (const std::vector<temporary_buffer<char>>& bufs) {
+ return boost::accumulate(bufs, size_t(0), [] (size_t sz, const temporary_buffer<char>& buf) {
+ return sz + buf.size();
+ });
+ }
+ );
+ BOOST_CHECK_EQUAL(actual_size, buffer.size);
+ };
+
+ for (auto& in : inputs) {
+ BOOST_TEST_MESSAGE("Input: " << std::get<0>(in));
+ auto headroom = std::get<1>(in);
+ auto compressed = compressor->compress(headroom, clone(std::get<2>(in)));
+ sanity_check(compressed);
+
+ // Remove headroom
+ BOOST_CHECK_GE(compressed.size, headroom);
+ compressed.size -= headroom;
+ seastar::visit(compressed.bufs,
+ [&] (temporary_buffer<char>& buf) {
+ BOOST_CHECK_GE(buf.size(), headroom);
+ buf.trim_front(headroom);
+ },
+ [&] (std::vector<temporary_buffer<char>>& bufs) {
+ while (headroom) {
+ BOOST_CHECK(!bufs.empty());
+ auto to_remove = std::min(bufs.front().size(), headroom);
+ bufs.front().trim_front(to_remove);
+ if (bufs.front().empty() && bufs.size() > 1) {
+ bufs.erase(bufs.begin());
+ }
+ headroom -= to_remove;
+ }
+ }
+ );
+
+ auto in_l = linearize(std::get<2>(in));
+
+ for (auto& t : transforms) {
+ BOOST_TEST_MESSAGE(" Transform: " << std::get<0>(t));
+ auto received = std::get<1>(t)(clone(compressed));
+
+ auto decompressed = compressor->decompress(std::move(received));
+ sanity_check(decompressed);
+
+ BOOST_CHECK_EQUAL(decompressed.size, std::get<2>(in).size);
+
+ auto out_l = linearize(decompressed);
+
+ BOOST_CHECK_EQUAL(in_l.size(), out_l.size());
+ BOOST_CHECK(in_l == out_l);
+ }
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_lz4_compressor) {
+ test_compressor([] { return std::make_unique<rpc::lz4_compressor>(); });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_lz4_fragmented_compressor) {
+ test_compressor([] { return std::make_unique<rpc::lz4_fragmented_compressor>(); });
+}
+
+// Test reproducing issue #671: If timeout is time_point::max(), translating
+// it to relative timeout in the sender and then back in the receiver, when
+// these calculations happen across a millisecond boundary, overflowed the
+// integer and mislead the receiver to think the requested timeout was
+// negative, and cause it drop its response, so the RPC call never completed.
+SEASTAR_TEST_CASE(test_max_absolute_timeout) {
+ // The typical failure of this test is a hang. So we use semaphore to
+ // stop the test either when it succeeds, or after a long enough hang.
+ auto success = make_lw_shared<bool>(false);
+ auto done = make_lw_shared<semaphore>(0);
+ auto abrt = make_lw_shared<abort_source>();
+ (void) seastar::sleep_abortable(std::chrono::seconds(3), *abrt).then([done, success] {
+ done->signal(1);
+ }).handle_exception([] (std::exception_ptr) {});
+ rpc::client_options co;
+ co.send_timeout_data = 1;
+ (void)rpc_test_env<>::do_with_thread(rpc_test_config(), co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ // The bug only reproduces if the calculation done on the sender
+ // and receiver sides, happened across a millisecond boundary.
+ // We can't control when it happens, so we just need to loop many
+ // times, at least many milliseconds, to increase the probability
+ // that we catch the bug. Experimentally, if we loop for 200ms, we
+ // catch the bug in #671 virtually every time.
+ auto until = seastar::lowres_clock::now() + std::chrono::milliseconds(200);
+ while (seastar::lowres_clock::now() <= until) {
+ auto result = sum(c1, rpc::rpc_clock_type::time_point::max(), 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }
+ }).then([success, done, abrt] {
+ *success = true;
+ abrt->request_abort();
+ done->signal();
+ });
+ return done->wait().then([done, success] {
+ BOOST_REQUIRE(*success);
+ });
+}
+
+// Similar to the above test: Test that a relative timeout duration::max()
+// also works, and again doesn't cause the timeout wrapping around to the
+// past and causing dropped responses.
+SEASTAR_TEST_CASE(test_max_relative_timeout) {
+ // The typical failure of this test is a hang. So we use semaphore to
+ // stop the test either when it succeeds, or after a long enough hang.
+ auto success = make_lw_shared<bool>(false);
+ auto done = make_lw_shared<semaphore>(0);
+ auto abrt = make_lw_shared<abort_source>();
+ (void) seastar::sleep_abortable(std::chrono::seconds(3), *abrt).then([done, success] {
+ done->signal(1);
+ }).handle_exception([] (std::exception_ptr) {});
+ rpc::client_options co;
+ co.send_timeout_data = 1;
+ (void)rpc_test_env<>::do_with_thread(rpc_test_config(), co, [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [](int a, int b) {
+ return make_ready_future<int>(a+b);
+ }).get();
+ auto sum = env.proto().make_client<int (int, int)>(1);
+ // The following call used to always hang, when max()+now()
+ // overflowed and appeared to be a negative timeout.
+ auto result = sum(c1, rpc::rpc_clock_type::duration::max(), 2, 3).get0();
+ BOOST_REQUIRE_EQUAL(result, 2 + 3);
+ }).then([success, done, abrt] {
+ *success = true;
+ abrt->request_abort();
+ done->signal();
+ });
+ return done->wait().then([done, success] {
+ BOOST_REQUIRE(*success);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_tuple) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ env.register_handler(1, [] () {
+ return make_ready_future<rpc::tuple<int, long>>(rpc::tuple<int, long>(1, 0x7'0000'0000L));
+ }).get();
+ auto f1 = env.proto().make_client<rpc::tuple<int, long> ()>(1);
+ auto result = f1(c1).get0();
+ BOOST_REQUIRE_EQUAL(std::get<0>(result), 1);
+ BOOST_REQUIRE_EQUAL(std::get<1>(result), 0x7'0000'0000L);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_nonvariadic_client_variadic_server) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ // Server is variadic
+ env.register_handler(1, [] () {
+ return make_ready_future<rpc::tuple<int, long>>(rpc::tuple(1, 0x7'0000'0000L));
+ }).get();
+ // Client is non-variadic
+ auto f1 = env.proto().make_client<future<rpc::tuple<int, long>> ()>(1);
+ auto result = f1(c1).get0();
+ BOOST_REQUIRE_EQUAL(std::get<0>(result), 1);
+ BOOST_REQUIRE_EQUAL(std::get<1>(result), 0x7'0000'0000L);
+ });
+}
+
+SEASTAR_TEST_CASE(test_rpc_variadic_client_nonvariadic_server) {
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ // Server is nonvariadic
+ env.register_handler(1, [] () {
+ return make_ready_future<rpc::tuple<int, long>>(rpc::tuple<int, long>(1, 0x7'0000'0000L));
+ }).get();
+ // Client is variadic
+ auto f1 = env.proto().make_client<future<rpc::tuple<int, long>> ()>(1);
+ auto result = f1(c1).get0();
+ BOOST_REQUIRE_EQUAL(std::get<0>(result), 1);
+ BOOST_REQUIRE_EQUAL(std::get<1>(result), 0x7'0000'0000L);
+ });
+}
+
+SEASTAR_TEST_CASE(test_handler_registration) {
+ rpc_test_config cfg;
+ cfg.connect = false;
+ return rpc_test_env<>::do_with_thread(cfg, [] (rpc_test_env<>& env) {
+ auto& proto = env.proto();
+
+ // new proto must be empty
+ BOOST_REQUIRE(!proto.has_handlers());
+
+ // non-existing handler should not be found
+ BOOST_REQUIRE(!proto.has_handler(1));
+
+ // unregistered non-existing handler should return ready future
+ auto fut = proto.unregister_handler(1);
+ BOOST_REQUIRE(fut.available() && !fut.failed());
+ fut.get();
+
+ // existing handler should be found
+ auto handler = [] () { return make_ready_future<>(); };
+ proto.register_handler(1, handler);
+ BOOST_REQUIRE(proto.has_handler(1));
+
+ // cannot register handler if already registered
+ BOOST_REQUIRE_THROW(proto.register_handler(1, handler), std::runtime_error);
+
+ // unregistered handler should not be found
+ proto.unregister_handler(1).get();
+ BOOST_REQUIRE(!proto.has_handler(1));
+
+ // re-registering a handler should succeed
+ proto.register_handler(1, handler);
+ BOOST_REQUIRE(proto.has_handler(1));
+
+ // proto with handlers must not be empty
+ BOOST_REQUIRE(proto.has_handlers());
+ });
+}
+
+SEASTAR_TEST_CASE(test_unregister_handler) {
+ using namespace std::chrono_literals;
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ promise<> handler_called;
+ future<> f_handler_called = handler_called.get_future();
+ bool rpc_executed = false;
+ bool rpc_completed = false;
+
+ auto reset_state = [&f_handler_called, &rpc_executed, &rpc_completed] {
+ if (f_handler_called.available()) {
+ f_handler_called.get();
+ }
+ rpc_executed = false;
+ rpc_completed = false;
+ };
+
+ auto get_handler = [&handler_called, &rpc_executed, &rpc_completed] {
+ return [&handler_called, &rpc_executed, &rpc_completed] {
+ handler_called.set_value();
+ rpc_executed = true;
+ return sleep(1ms).then([&rpc_completed] {
+ rpc_completed = true;
+ });
+ };
+ };
+
+ // handler should not run if unregistered before being called
+ env.register_handler(1, get_handler()).get();
+ env.unregister_handler(1).get();
+ BOOST_REQUIRE(!f_handler_called.available());
+ BOOST_REQUIRE(!rpc_executed);
+ BOOST_REQUIRE(!rpc_completed);
+
+ // verify normal execution path
+ env.register_handler(1, get_handler()).get();
+ auto call = env.proto().make_client<void ()>(1);
+ call(c1).get();
+ BOOST_REQUIRE(f_handler_called.available());
+ BOOST_REQUIRE(rpc_executed);
+ BOOST_REQUIRE(rpc_completed);
+ reset_state();
+
+ // call should fail after handler is unregistered
+ env.unregister_handler(1).get();
+ try {
+ call(c1).get();
+ BOOST_REQUIRE(false);
+ } catch (rpc::unknown_verb_error&) {
+ // expected
+ } catch (...) {
+ std::cerr << "call failed in an unexpected way: " << std::current_exception() << std::endl;
+ BOOST_REQUIRE(false);
+ }
+ BOOST_REQUIRE(!f_handler_called.available());
+ BOOST_REQUIRE(!rpc_executed);
+ BOOST_REQUIRE(!rpc_completed);
+
+ // unregistered is allowed while call is in flight
+ auto delayed_unregister = [&env] {
+ return sleep(500us).then([&env] {
+ return env.unregister_handler(1);
+ });
+ };
+
+ env.register_handler(1, get_handler()).get();
+ try {
+ when_all_succeed(call(c1), delayed_unregister()).get();
+ reset_state();
+ } catch (rpc::unknown_verb_error&) {
+ // expected
+ } catch (...) {
+ std::cerr << "call failed in an unexpected way: " << std::current_exception() << std::endl;
+ BOOST_REQUIRE(false);
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_loggers) {
+ static seastar::logger log("dummy");
+ log.set_level(log_level::debug);
+ return rpc_test_env<>::do_with_thread(rpc_test_config(), [] (rpc_test_env<>& env, test_rpc_proto::client& c1) {
+ socket_address dummy_addr;
+ auto& proto = env.proto();
+ auto& logger = proto.get_logger();
+ logger(dummy_addr, "Hello0");
+ logger(dummy_addr, log_level::debug, "Hello1");
+ proto.set_logger(&log);
+ logger(dummy_addr, "Hello2");
+ logger(dummy_addr, log_level::debug, "Hello3");
+ // We *want* to test the deprecated API, don't spam warnings about it.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ proto.set_logger([] (const sstring& str) {
+ log.info("Test: {}", str);
+ });
+#pragma GCC diagnostic pop
+ logger(dummy_addr, "Hello4");
+ logger(dummy_addr, log_level::debug, "Hello5");
+ proto.set_logger(nullptr);
+ logger(dummy_addr, "Hello6");
+ logger(dummy_addr, log_level::debug, "Hello7");
+ });
+}
+
+static_assert(std::is_same_v<decltype(rpc::tuple(1U, 1L)), rpc::tuple<unsigned, long>>, "rpc::tuple deduction guid not working");
diff --git a/src/seastar/tests/unit/scheduling_group_test.cc b/src/seastar/tests/unit/scheduling_group_test.cc
new file mode 100644
index 000000000..5f30d5a97
--- /dev/null
+++ b/src/seastar/tests/unit/scheduling_group_test.cc
@@ -0,0 +1,251 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB Ltd.
+ */
+
+#include <algorithm>
+#include <vector>
+#include <chrono>
+
+#include <seastar/core/thread.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/testing/test_runner.hh>
+#include <seastar/core/execution_stage.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/scheduling_specific.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/with_scheduling_group.hh>
+#include <seastar/util/later.hh>
+
+using namespace std::chrono_literals;
+
+using namespace seastar;
+
+/**
+ * Test setting primitive and object as a value after all groups are created
+ */
+SEASTAR_THREAD_TEST_CASE(sg_specific_values_define_after_sg_create) {
+ using ivec = std::vector<int>;
+ const int num_scheduling_groups = 4;
+ std::vector<scheduling_group> sgs;
+ for (int i = 0; i < num_scheduling_groups; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+
+ const auto destroy_scheduling_groups = defer([&sgs] () {
+ for (scheduling_group sg : sgs) {
+ destroy_scheduling_group(sg).get();
+ }
+ });
+ scheduling_group_key_config key1_conf = make_scheduling_group_key_config<int>();
+ scheduling_group_key key1 = scheduling_group_key_create(key1_conf).get0();
+
+ scheduling_group_key_config key2_conf = make_scheduling_group_key_config<ivec>();
+ scheduling_group_key key2 = scheduling_group_key_create(key2_conf).get0();
+
+ smp::invoke_on_all([key1, key2, &sgs] () {
+ int factor = this_shard_id() + 1;
+ for (int i=0; i < num_scheduling_groups; i++) {
+ sgs[i].get_specific<int>(key1) = (i + 1) * factor;
+ sgs[i].get_specific<ivec>(key2).push_back((i + 1) * factor);
+ }
+
+ for (int i=0; i < num_scheduling_groups; i++) {
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<int>(key1) = (i + 1) * factor, (i + 1) * factor);
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<ivec>(key2)[0], (i + 1) * factor);
+ }
+
+ }).get();
+
+ smp::invoke_on_all([key1, key2] () {
+ return reduce_scheduling_group_specific<int>(std::plus<int>(), int(0), key1).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ }). then([key2] {
+ auto ivec_to_int = [] (ivec& v) {
+ return v.size() ? v[0] : 0;
+ };
+
+ return map_reduce_scheduling_group_specific<ivec>(ivec_to_int, std::plus<int>(), int(0), key2).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ });
+
+ });
+ }).get();
+
+
+}
+
+/**
+ * Test setting primitive and object as a value before all groups are created
+ */
+SEASTAR_THREAD_TEST_CASE(sg_specific_values_define_before_sg_create) {
+ using ivec = std::vector<int>;
+ const int num_scheduling_groups = 4;
+ std::vector<scheduling_group> sgs;
+ const auto destroy_scheduling_groups = defer([&sgs] () {
+ for (scheduling_group sg : sgs) {
+ destroy_scheduling_group(sg).get();
+ }
+ });
+ scheduling_group_key_config key1_conf = make_scheduling_group_key_config<int>();
+ scheduling_group_key key1 = scheduling_group_key_create(key1_conf).get0();
+
+ scheduling_group_key_config key2_conf = make_scheduling_group_key_config<ivec>();
+ scheduling_group_key key2 = scheduling_group_key_create(key2_conf).get0();
+
+ for (int i = 0; i < num_scheduling_groups; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+
+ smp::invoke_on_all([key1, key2, &sgs] () {
+ int factor = this_shard_id() + 1;
+ for (int i=0; i < num_scheduling_groups; i++) {
+ sgs[i].get_specific<int>(key1) = (i + 1) * factor;
+ sgs[i].get_specific<ivec>(key2).push_back((i + 1) * factor);
+ }
+
+ for (int i=0; i < num_scheduling_groups; i++) {
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<int>(key1) = (i + 1) * factor, (i + 1) * factor);
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<ivec>(key2)[0], (i + 1) * factor);
+ }
+
+ }).get();
+
+ smp::invoke_on_all([key1, key2] () {
+ return reduce_scheduling_group_specific<int>(std::plus<int>(), int(0), key1).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ }). then([key2] {
+ auto ivec_to_int = [] (ivec& v) {
+ return v.size() ? v[0] : 0;
+ };
+
+ return map_reduce_scheduling_group_specific<ivec>(ivec_to_int, std::plus<int>(), int(0), key2).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ });
+
+ });
+ }).get();
+
+}
+
+/**
+ * Test setting primitive and an object as a value before some groups are created
+ * and after some of the groups are created.
+ */
+SEASTAR_THREAD_TEST_CASE(sg_specific_values_define_before_and_after_sg_create) {
+ using ivec = std::vector<int>;
+ const int num_scheduling_groups = 4;
+ std::vector<scheduling_group> sgs;
+ const auto destroy_scheduling_groups = defer([&sgs] () {
+ for (scheduling_group sg : sgs) {
+ destroy_scheduling_group(sg).get();
+ }
+ });
+
+ for (int i = 0; i < num_scheduling_groups/2; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+ scheduling_group_key_config key1_conf = make_scheduling_group_key_config<int>();
+ scheduling_group_key key1 = scheduling_group_key_create(key1_conf).get0();
+
+ scheduling_group_key_config key2_conf = make_scheduling_group_key_config<ivec>();
+ scheduling_group_key key2 = scheduling_group_key_create(key2_conf).get0();
+
+ for (int i = num_scheduling_groups/2; i < num_scheduling_groups; i++) {
+ sgs.push_back(create_scheduling_group(format("sg{}", i).c_str(), 100).get0());
+ }
+
+ smp::invoke_on_all([key1, key2, &sgs] () {
+ int factor = this_shard_id() + 1;
+ for (int i=0; i < num_scheduling_groups; i++) {
+ sgs[i].get_specific<int>(key1) = (i + 1) * factor;
+ sgs[i].get_specific<ivec>(key2).push_back((i + 1) * factor);
+ }
+
+ for (int i=0; i < num_scheduling_groups; i++) {
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<int>(key1) = (i + 1) * factor, (i + 1) * factor);
+ BOOST_REQUIRE_EQUAL(sgs[i].get_specific<ivec>(key2)[0], (i + 1) * factor);
+ }
+
+ }).get();
+
+ smp::invoke_on_all([key1, key2] () {
+ return reduce_scheduling_group_specific<int>(std::plus<int>(), int(0), key1).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ }). then([key2] {
+ auto ivec_to_int = [] (ivec& v) {
+ return v.size() ? v[0] : 0;
+ };
+
+ return map_reduce_scheduling_group_specific<ivec>(ivec_to_int, std::plus<int>(), int(0), key2).then([] (int sum) {
+ int factor = this_shard_id() + 1;
+ int expected_sum = ((1 + num_scheduling_groups)*num_scheduling_groups) * factor /2;
+ BOOST_REQUIRE_EQUAL(expected_sum, sum);
+ });
+
+ });
+ }).get();
+}
+
+/*
+ * Test that current scheduling group is inherited by seastar::async()
+ */
+SEASTAR_THREAD_TEST_CASE(sg_scheduling_group_inheritance_in_seastar_async_test) {
+ scheduling_group sg = create_scheduling_group("sg0", 100).get0();
+ thread_attributes attr = {};
+ attr.sched_group = sg;
+ seastar::async(attr, [attr] {
+ BOOST_REQUIRE_EQUAL(internal::scheduling_group_index(current_scheduling_group()),
+ internal::scheduling_group_index(*(attr.sched_group)));
+
+ seastar::async([attr] {
+ BOOST_REQUIRE_EQUAL(internal::scheduling_group_index(current_scheduling_group()),
+ internal::scheduling_group_index(*(attr.sched_group)));
+
+ smp::invoke_on_all([sched_group_idx = internal::scheduling_group_index(*(attr.sched_group))] () {
+ BOOST_REQUIRE_EQUAL(internal::scheduling_group_index(current_scheduling_group()), sched_group_idx);
+ }).get();
+ }).get();
+ }).get();
+}
+
+
+SEASTAR_THREAD_TEST_CASE(later_preserves_sg) {
+ scheduling_group sg = create_scheduling_group("sg", 100).get0();
+ auto cleanup = defer([&] { destroy_scheduling_group(sg).get(); });
+ with_scheduling_group(sg, [&] {
+ return later().then([&] {
+ BOOST_REQUIRE_EQUAL(
+ internal::scheduling_group_index(current_scheduling_group()),
+ internal::scheduling_group_index(sg));
+ });
+ }).get();
+}
diff --git a/src/seastar/tests/unit/semaphore_test.cc b/src/seastar/tests/unit/semaphore_test.cc
new file mode 100644
index 000000000..1f164ed00
--- /dev/null
+++ b/src/seastar/tests/unit/semaphore_test.cc
@@ -0,0 +1,310 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/map_reduce.hh>
+#include <seastar/core/sleep.hh>
+#include <seastar/core/shared_mutex.hh>
+#include <boost/range/irange.hpp>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_semaphore_consume) {
+ semaphore sem(0);
+ sem.consume(1);
+ BOOST_REQUIRE_EQUAL(sem.current(), 0u);
+ BOOST_REQUIRE_EQUAL(sem.waiters(), 0u);
+
+ BOOST_REQUIRE_EQUAL(sem.try_wait(0), false);
+ auto fut = sem.wait(1);
+ BOOST_REQUIRE_EQUAL(fut.available(), false);
+ BOOST_REQUIRE_EQUAL(sem.waiters(), 1u);
+ sem.signal(2);
+ BOOST_REQUIRE_EQUAL(sem.waiters(), 0u);
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(test_semaphore_1) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ (void)x.first.wait().then([&x] {
+ x.second++;
+ });
+ x.first.signal();
+ return sleep(10ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 1);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_2) {
+ auto sem = std::make_optional<semaphore>(0);
+ int x = 0;
+ auto fut = sem->wait().then([&x] {
+ x++;
+ });
+ sleep(10ms).get();
+ BOOST_REQUIRE_EQUAL(x, 0);
+ sem = std::nullopt;
+ BOOST_CHECK_THROW(fut.get(), broken_promise);
+}
+
+SEASTAR_TEST_CASE(test_semaphore_timeout_1) {
+ return do_with(std::make_pair(semaphore(0), 0), [] (std::pair<semaphore, int>& x) {
+ (void)x.first.wait(100ms).then([&x] {
+ x.second++;
+ });
+ (void)sleep(3ms).then([&x] {
+ x.first.signal();
+ });
+ return sleep(200ms).then([&x] {
+ BOOST_REQUIRE_EQUAL(x.second, 1);
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_timeout_2) {
+ auto sem = semaphore(0);
+ int x = 0;
+ auto fut1 = sem.wait(3ms).then([&x] {
+ x++;
+ });
+ bool signaled = false;
+ auto fut2 = sleep(100ms).then([&sem, &signaled] {
+ signaled = true;
+ sem.signal();
+ });
+ sleep(200ms).get();
+ fut2.get();
+ BOOST_REQUIRE_EQUAL(signaled, true);
+ BOOST_CHECK_THROW(fut1.get(), semaphore_timed_out);
+ BOOST_REQUIRE_EQUAL(x, 0);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_mix_1) {
+ auto sem = semaphore(0);
+ int x = 0;
+ auto fut1 = sem.wait(30ms).then([&x] {
+ x++;
+ });
+ auto fut2 = sem.wait().then([&x] {
+ x += 10;
+ });
+ auto fut3 = sleep(100ms).then([&sem] {
+ sem.signal();
+ });
+ sleep(200ms).get();
+ fut3.get();
+ fut2.get();
+ BOOST_CHECK_THROW(fut1.get(), semaphore_timed_out);
+ BOOST_REQUIRE_EQUAL(x, 10);
+}
+
+SEASTAR_TEST_CASE(test_broken_semaphore) {
+ auto sem = make_lw_shared<semaphore>(0);
+ struct oops {};
+ auto check_result = [sem] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("expecting exception");
+ } catch (oops& x) {
+ // ok
+ return make_ready_future<>();
+ } catch (...) {
+ BOOST_FAIL("wrong exception seen");
+ }
+ BOOST_FAIL("unreachable");
+ return make_ready_future<>();
+ };
+ auto ret = sem->wait().then_wrapped(check_result);
+ sem->broken(oops());
+ return sem->wait().then_wrapped(check_result).then([ret = std::move(ret)] () mutable {
+ return std::move(ret);
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_exclusive) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ return parallel_for_each(boost::irange(0, 10), [&sm, &counter] (int idx) {
+ return with_lock(sm, [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_shared) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ auto running_in_parallel = [&sm, &counter] (int instance) {
+ return with_shared(sm, [&counter] {
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ bool was_parallel = counter != 0;
+ --counter;
+ return was_parallel;
+ });
+ });
+ };
+ return map_reduce(boost::irange(0, 100), running_in_parallel, false, std::bit_or<bool>()).then([&counter] (bool result) {
+ BOOST_REQUIRE_EQUAL(result, true);
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_shared_mutex_mixed) {
+ return do_with(shared_mutex(), unsigned(0), [] (shared_mutex& sm, unsigned& counter) {
+ auto running_in_parallel = [&sm, &counter] (int instance) {
+ return with_shared(sm, [&counter] {
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ bool was_parallel = counter != 0;
+ --counter;
+ return was_parallel;
+ });
+ });
+ };
+ auto running_alone = [&sm, &counter] (int instance) {
+ return with_lock(sm, [&counter] {
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ ++counter;
+ return sleep(10ms).then([&counter] {
+ --counter;
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ return true;
+ });
+ });
+ };
+ auto run = [running_in_parallel, running_alone] (int instance) {
+ if (instance % 9 == 0) {
+ return running_alone(instance);
+ } else {
+ return running_in_parallel(instance);
+ }
+ };
+ return map_reduce(boost::irange(0, 100), run, false, std::bit_or<bool>()).then([&counter] (bool result) {
+ BOOST_REQUIRE_EQUAL(result, true);
+ BOOST_REQUIRE_EQUAL(counter, 0u);
+ });
+ });
+}
+
+
+SEASTAR_TEST_CASE(test_with_semaphore) {
+ return do_with(semaphore(1), 0, [] (semaphore& sem, int& counter) {
+ return with_semaphore(sem, 1, [&counter] {
+ ++counter;
+ }).then([&counter, &sem] () {
+ return with_semaphore(sem, 1, [&counter] {
+ ++counter;
+ throw 123;
+ }).then_wrapped([&counter] (auto&& fut) {
+ BOOST_REQUIRE_EQUAL(counter, 2);
+ BOOST_REQUIRE(fut.failed());
+ fut.ignore_ready_future();
+ });
+ });
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_units_splitting) {
+ auto sm = semaphore(2);
+ auto units = get_units(sm, 2, 1min).get0();
+ {
+ BOOST_REQUIRE_EQUAL(units.count(), 2);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ auto split = units.split(1);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ }
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ units.~semaphore_units();
+ units = get_units(sm, 2, 1min).get0();
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ BOOST_REQUIRE_THROW(units.split(10), std::invalid_argument);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_semaphore_units_return) {
+ auto sm = semaphore(3);
+ auto units = get_units(sm, 3, 1min).get0();
+ BOOST_REQUIRE_EQUAL(units.count(), 3);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 0);
+ BOOST_REQUIRE_EQUAL(units.return_units(1), 2);
+ BOOST_REQUIRE_EQUAL(units.count(), 2);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ units.~semaphore_units();
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 3);
+
+ units = get_units(sm, 2, 1min).get0();
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ BOOST_REQUIRE_THROW(units.return_units(10), std::invalid_argument);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 1);
+ units.return_all();
+ BOOST_REQUIRE_EQUAL(units.count(), 0);
+ BOOST_REQUIRE_EQUAL(sm.available_units(), 3);
+}
+
+SEASTAR_THREAD_TEST_CASE(test_named_semaphore_error) {
+ auto sem = make_lw_shared<named_semaphore>(0, named_semaphore_exception_factory{"name_of_the_semaphore"});
+ auto check_result = [sem] (future<> f) {
+ try {
+ f.get();
+ BOOST_FAIL("Expecting an exception");
+ } catch (broken_named_semaphore& ex) {
+ BOOST_REQUIRE_NE(std::string(ex.what()).find("name_of_the_semaphore"), std::string::npos);
+ } catch (...) {
+ BOOST_FAIL("Expected an instance of broken_named_semaphore with proper semaphore name");
+ }
+ return make_ready_future<>();
+ };
+ auto ret = sem->wait().then_wrapped(check_result);
+ sem->broken();
+ sem->wait().then_wrapped(check_result).then([ret = std::move(ret)] () mutable {
+ return std::move(ret);
+ }).get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_named_semaphore_timeout) {
+ auto sem = make_lw_shared<named_semaphore>(0, named_semaphore_exception_factory{"name_of_the_semaphore"});
+
+ auto f = sem->wait(named_semaphore::clock::now() + 1ms, 1);
+ try {
+ f.get();
+ BOOST_FAIL("Expecting an exception");
+ } catch (named_semaphore_timed_out& ex) {
+ BOOST_REQUIRE_NE(std::string(ex.what()).find("name_of_the_semaphore"), std::string::npos);
+ } catch (...) {
+ BOOST_FAIL("Expected an instance of named_semaphore_timed_out with proper semaphore name");
+ }
+}
diff --git a/src/seastar/tests/unit/sharded_test.cc b/src/seastar/tests/unit/sharded_test.cc
new file mode 100644
index 000000000..f243d7fc0
--- /dev/null
+++ b/src/seastar/tests/unit/sharded_test.cc
@@ -0,0 +1,110 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 Scylladb, Ltd.
+ */
+
+#include <seastar/testing/thread_test_case.hh>
+
+#include <seastar/core/sharded.hh>
+
+using namespace seastar;
+
+namespace {
+class invoke_on_during_stop final : public peering_sharded_service<invoke_on_during_stop> {
+ bool flag = false;
+
+public:
+ future<> stop() {
+ return container().invoke_on(0, [] (invoke_on_during_stop& instance) {
+ instance.flag = true;
+ });
+ }
+
+ ~invoke_on_during_stop() {
+ if (this_shard_id() == 0) {
+ assert(flag);
+ }
+ }
+};
+}
+
+SEASTAR_THREAD_TEST_CASE(invoke_on_during_stop_test) {
+ sharded<invoke_on_during_stop> s;
+ s.start().get();
+ s.stop().get();
+}
+
+class mydata {
+public:
+ int x = 1;
+ future<> stop() {
+ return make_ready_future<>();
+ }
+};
+
+SEASTAR_THREAD_TEST_CASE(invoke_map_returns_non_future_value) {
+ seastar::sharded<mydata> s;
+ s.start().get();
+ s.map([] (mydata& m) {
+ return m.x;
+ }).then([] (std::vector<int> results) {
+ for (auto& x : results) {
+ assert(x == 1);
+ }
+ }).get();
+ s.stop().get();
+};
+
+SEASTAR_THREAD_TEST_CASE(invoke_map_returns_future_value) {
+ seastar::sharded<mydata> s;
+ s.start().get();
+ s.map([] (mydata& m) {
+ return make_ready_future<int>(m.x);
+ }).then([] (std::vector<int> results) {
+ for (auto& x : results) {
+ assert(x == 1);
+ }
+ }).get();
+ s.stop().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(invoke_map_returns_future_value_from_thread) {
+ seastar::sharded<mydata> s;
+ s.start().get();
+ s.map([] (mydata& m) {
+ return seastar::async([&m] {
+ return m.x;
+ });
+ }).then([] (std::vector<int> results) {
+ for (auto& x : results) {
+ assert(x == 1);
+ }
+ }).get();
+ s.stop().get();
+}
+
+SEASTAR_THREAD_TEST_CASE(failed_sharded_start_doesnt_hang) {
+ class fail_to_start {
+ public:
+ fail_to_start() { throw 0; }
+ };
+
+ seastar::sharded<fail_to_start> s;
+ s.start().then_wrapped([] (auto&& fut) { fut.ignore_ready_future(); }).get();
+}
diff --git a/src/seastar/tests/unit/shared_ptr_test.cc b/src/seastar/tests/unit/shared_ptr_test.cc
new file mode 100644
index 000000000..3ee4b6589
--- /dev/null
+++ b/src/seastar/tests/unit/shared_ptr_test.cc
@@ -0,0 +1,204 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright 2015 Cloudius Systems
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <set>
+#include <unordered_map>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/shared_ptr.hh>
+
+using namespace seastar;
+
+struct expected_exception : public std::exception {};
+
+struct A {
+ static bool destroyed;
+ A() {
+ destroyed = false;
+ }
+ virtual ~A() {
+ destroyed = true;
+ }
+};
+
+struct A_esft : public A, public enable_lw_shared_from_this<A_esft> {
+};
+
+struct B {
+ virtual void x() {}
+};
+
+bool A::destroyed = false;
+
+BOOST_AUTO_TEST_CASE(explot_dynamic_cast_use_after_free_problem) {
+ shared_ptr<A> p = ::make_shared<A>();
+ {
+ auto p2 = dynamic_pointer_cast<B>(p);
+ BOOST_ASSERT(!p2);
+ }
+ BOOST_ASSERT(!A::destroyed);
+}
+
+class C : public enable_shared_from_this<C> {
+public:
+ shared_ptr<C> dup() { return shared_from_this(); }
+ shared_ptr<const C> get() const { return shared_from_this(); }
+};
+
+BOOST_AUTO_TEST_CASE(test_const_ptr) {
+ shared_ptr<C> a = make_shared<C>();
+ shared_ptr<const C> ca = a;
+ BOOST_REQUIRE(ca == a);
+ shared_ptr<const C> cca = ca->get();
+ BOOST_REQUIRE(cca == ca);
+}
+
+struct D {};
+
+BOOST_AUTO_TEST_CASE(test_lw_const_ptr_1) {
+ auto pd1 = make_lw_shared<const D>(D());
+ auto pd2 = make_lw_shared(D());
+ lw_shared_ptr<const D> pd3 = pd2;
+ BOOST_REQUIRE(pd2 == pd3);
+}
+
+struct E : enable_lw_shared_from_this<E> {};
+
+BOOST_AUTO_TEST_CASE(test_lw_const_ptr_2) {
+ auto pe1 = make_lw_shared<const E>();
+ auto pe2 = make_lw_shared<E>();
+ lw_shared_ptr<const E> pe3 = pe2;
+ BOOST_REQUIRE(pe2 == pe3);
+}
+
+struct F : enable_lw_shared_from_this<F> {
+ auto const_method() const {
+ return shared_from_this();
+ }
+};
+
+BOOST_AUTO_TEST_CASE(test_shared_from_this_called_on_const_object) {
+ auto ptr = make_lw_shared<F>();
+ ptr->const_method();
+}
+
+BOOST_AUTO_TEST_CASE(test_exception_thrown_from_constructor_is_propagated) {
+ struct X {
+ X() {
+ throw expected_exception();
+ }
+ };
+ try {
+ auto ptr = make_lw_shared<X>();
+ BOOST_FAIL("Constructor should have thrown");
+ } catch (const expected_exception& e) {
+ BOOST_TEST_MESSAGE("Expected exception caught");
+ }
+ try {
+ auto ptr = ::make_shared<X>();
+ BOOST_FAIL("Constructor should have thrown");
+ } catch (const expected_exception& e) {
+ BOOST_TEST_MESSAGE("Expected exception caught");
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_indirect_functors) {
+ {
+ std::multiset<shared_ptr<sstring>, indirect_less<shared_ptr<sstring>>> a_set;
+
+ a_set.insert(make_shared<sstring>("k3"));
+ a_set.insert(make_shared<sstring>("k1"));
+ a_set.insert(make_shared<sstring>("k2"));
+ a_set.insert(make_shared<sstring>("k4"));
+ a_set.insert(make_shared<sstring>("k0"));
+
+
+ auto i = a_set.begin();
+ BOOST_REQUIRE_EQUAL(sstring("k0"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k1"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k2"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k3"), *(*i++));
+ BOOST_REQUIRE_EQUAL(sstring("k4"), *(*i++));
+ }
+
+ {
+ std::unordered_map<shared_ptr<sstring>, bool,
+ indirect_hash<shared_ptr<sstring>>, indirect_equal_to<shared_ptr<sstring>>> a_map;
+
+ a_map.emplace(make_shared<sstring>("k3"), true);
+ a_map.emplace(make_shared<sstring>("k1"), true);
+ a_map.emplace(make_shared<sstring>("k2"), true);
+ a_map.emplace(make_shared<sstring>("k4"), true);
+ a_map.emplace(make_shared<sstring>("k0"), true);
+
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k0")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k1")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k2")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k3")));
+ BOOST_REQUIRE(a_map.count(make_shared<sstring>("k4")));
+ BOOST_REQUIRE(!a_map.count(make_shared<sstring>("k5")));
+ }
+}
+
+template<typename T>
+void do_test_release() {
+ auto ptr = make_lw_shared<T>();
+ BOOST_REQUIRE(!T::destroyed);
+
+ auto ptr2 = ptr;
+
+ BOOST_REQUIRE(!ptr.release());
+ BOOST_REQUIRE(!ptr);
+ BOOST_REQUIRE(ptr2.use_count() == 1);
+
+ auto uptr2 = ptr2.release();
+ BOOST_REQUIRE(uptr2);
+ BOOST_REQUIRE(!ptr2);
+ ptr2 = {};
+
+ BOOST_REQUIRE(!T::destroyed);
+ uptr2 = {};
+
+ BOOST_REQUIRE(T::destroyed);
+
+ // Check destroying via disposer
+ auto ptr3 = make_lw_shared<T>();
+ auto uptr3 = ptr3.release();
+ BOOST_REQUIRE(uptr3);
+ BOOST_REQUIRE(!T::destroyed);
+
+ auto raw_ptr3 = uptr3.release();
+ lw_shared_ptr<T>::dispose(raw_ptr3);
+ BOOST_REQUIRE(T::destroyed);
+}
+
+BOOST_AUTO_TEST_CASE(test_release) {
+ do_test_release<A>();
+ do_test_release<A_esft>();
+}
+
+BOOST_AUTO_TEST_CASE(test_const_release) {
+ do_test_release<const A>();
+ do_test_release<const A_esft>();
+}
diff --git a/src/seastar/tests/unit/signal_test.cc b/src/seastar/tests/unit/signal_test.cc
new file mode 100755
index 000000000..080de068f
--- /dev/null
+++ b/src/seastar/tests/unit/signal_test.cc
@@ -0,0 +1,48 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+
+using namespace seastar;
+
+extern "C" {
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+}
+
+SEASTAR_TEST_CASE(test_sighup) {
+ return do_with(make_lw_shared<promise<>>(), false, [](auto const& p, bool& signaled) {
+ engine().handle_signal(SIGHUP, [p, &signaled] {
+ signaled = true;
+ p->set_value();
+ });
+
+ kill(getpid(), SIGHUP);
+
+ return p->get_future().then([&] {
+ BOOST_REQUIRE_EQUAL(signaled, true);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/simple_stream_test.cc b/src/seastar/tests/unit/simple_stream_test.cc
new file mode 100644
index 000000000..32e0bf2d4
--- /dev/null
+++ b/src/seastar/tests/unit/simple_stream_test.cc
@@ -0,0 +1,99 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 ScyllaDB Ltd.
+ */
+
+#define BOOST_TEST_MODULE simple_stream
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/simple-stream.hh>
+
+using namespace seastar;
+
+template<typename Input, typename Output>
+static void write_read_test(Input in, Output out)
+{
+ auto aa = std::vector<char>(4, 'a');
+ auto bb = std::vector<char>(3, 'b');
+ auto cc = std::vector<char>(2, 'c');
+
+ out.write(aa.data(), aa.size());
+ out.fill('b', 3);
+
+ BOOST_CHECK_THROW(out.fill(' ', 3), std::out_of_range);
+ BOOST_CHECK_THROW(out.write(" ", 3), std::out_of_range);
+
+ out.write(cc.data(), cc.size());
+
+ BOOST_CHECK_THROW(out.fill(' ', 1), std::out_of_range);
+ BOOST_CHECK_THROW(out.write(" ", 1), std::out_of_range);
+
+ auto actual_aa = std::vector<char>(4);
+ in.read(actual_aa.data(), actual_aa.size());
+ BOOST_CHECK_EQUAL(aa, actual_aa);
+
+ auto actual_bb = std::vector<char>(3);
+ in.read(actual_bb.data(), actual_bb.size());
+ BOOST_CHECK_EQUAL(bb, actual_bb);
+
+ actual_aa.resize(1024);
+ BOOST_CHECK_THROW(in.read(actual_aa.data(), actual_aa.size()), std::out_of_range);
+
+ auto actual_cc = std::vector<char>(2);
+ in.read(actual_cc.data(), actual_cc.size());
+ BOOST_CHECK_EQUAL(cc, actual_cc);
+
+ BOOST_CHECK_THROW(in.read(actual_aa.data(), 1), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_CASE(simple_write_read_test) {
+ auto buf = temporary_buffer<char>(9);
+
+ write_read_test(simple_memory_input_stream(buf.get(), buf.size()),
+ simple_memory_output_stream(buf.get_write(), buf.size()));
+
+ std::fill_n(buf.get_write(), buf.size(), 'x');
+
+ auto out = simple_memory_output_stream(buf.get_write(), buf.size());
+ write_read_test(out.to_input_stream(), out);
+}
+
+BOOST_AUTO_TEST_CASE(fragmented_write_read_test) {
+ static constexpr size_t total_size = 9;
+
+ auto bufs = std::vector<temporary_buffer<char>>();
+ using iterator_type = std::vector<temporary_buffer<char>>::iterator;
+
+ auto test = [&] {
+ write_read_test(fragmented_memory_input_stream<iterator_type>(bufs.begin(), total_size),
+ fragmented_memory_output_stream<iterator_type>(bufs.begin(), total_size));
+
+ auto out = fragmented_memory_output_stream<iterator_type>(bufs.begin(), total_size);
+ write_read_test(out.to_input_stream(), out);
+ };
+
+ bufs.emplace_back(total_size);
+ test();
+
+ bufs.clear();
+ for (auto i = 0u; i < total_size; i++) {
+ bufs.emplace_back(1);
+ }
+ test();
+}
diff --git a/src/seastar/tests/unit/slab_test.cc b/src/seastar/tests/unit/slab_test.cc
new file mode 100644
index 000000000..7588dd79d
--- /dev/null
+++ b/src/seastar/tests/unit/slab_test.cc
@@ -0,0 +1,127 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ *
+ * To compile: g++ -std=c++14 slab_test.cc
+ */
+
+#include <iostream>
+#include <assert.h>
+#include <seastar/core/slab.hh>
+
+using namespace seastar;
+
+namespace bi = boost::intrusive;
+
+static constexpr size_t max_object_size = 1024*1024;
+
+class item : public slab_item_base {
+public:
+ bi::list_member_hook<> _cache_link;
+ uint32_t _slab_page_index;
+
+ item(uint32_t slab_page_index) : _slab_page_index(slab_page_index) {}
+
+ const uint32_t get_slab_page_index() {
+ return _slab_page_index;
+ }
+ const bool is_unlocked() {
+ return true;
+ }
+};
+
+template<typename Item>
+static void free_vector(slab_allocator<Item>& slab, std::vector<item *>& items) {
+ for (auto item : items) {
+ slab.free(item);
+ }
+}
+
+static void test_allocation_1(const double growth_factor, const unsigned slab_limit_size) {
+ slab_allocator<item> slab(growth_factor, slab_limit_size, max_object_size);
+ size_t size = max_object_size;
+
+ slab.print_slab_classes();
+
+ std::vector<item *> items;
+
+ assert(slab_limit_size % size == 0);
+ for (auto i = 0u; i < (slab_limit_size / size); i++) {
+ auto item = slab.create(size);
+ items.push_back(item);
+ }
+ assert(slab.create(size) == nullptr);
+
+ free_vector<item>(slab, items);
+ std::cout << __FUNCTION__ << " done!\n";
+}
+
+static void test_allocation_2(const double growth_factor, const unsigned slab_limit_size) {
+ slab_allocator<item> slab(growth_factor, slab_limit_size, max_object_size);
+ size_t size = 1024;
+
+ std::vector<item *> items;
+
+ auto allocations = 0u;
+ for (;;) {
+ auto item = slab.create(size);
+ if (!item) {
+ break;
+ }
+ items.push_back(item);
+ allocations++;
+ }
+
+ auto class_size = slab.class_size(size);
+ auto per_slab_page = max_object_size / class_size;
+ auto available_slab_pages = slab_limit_size / max_object_size;
+ assert(allocations == (per_slab_page * available_slab_pages));
+
+ free_vector<item>(slab, items);
+ std::cout << __FUNCTION__ << " done!\n";
+}
+
+static void test_allocation_with_lru(const double growth_factor, const unsigned slab_limit_size) {
+ bi::list<item, bi::member_hook<item, bi::list_member_hook<>, &item::_cache_link>> _cache;
+ unsigned evictions = 0;
+
+ slab_allocator<item> slab(growth_factor, slab_limit_size, max_object_size,
+ [&](item& item_ref) { _cache.erase(_cache.iterator_to(item_ref)); evictions++; });
+ size_t size = max_object_size;
+
+ auto max = slab_limit_size / max_object_size;
+ for (auto i = 0u; i < max * 1000; i++) {
+ auto item = slab.create(size);
+ assert(item != nullptr);
+ _cache.push_front(*item);
+ }
+ assert(evictions == max * 999);
+
+ _cache.clear();
+
+ std::cout << __FUNCTION__ << " done!\n";
+}
+
+int main(int ac, char** av) {
+ test_allocation_1(1.25, 5*1024*1024);
+ test_allocation_2(1.07, 5*1024*1024); // 1.07 is the growth factor used by facebook.
+ test_allocation_with_lru(1.25, 5*1024*1024);
+
+ return 0;
+}
diff --git a/src/seastar/tests/unit/smp_test.cc b/src/seastar/tests/unit/smp_test.cc
new file mode 100644
index 000000000..67967674d
--- /dev/null
+++ b/src/seastar/tests/unit/smp_test.cc
@@ -0,0 +1,81 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/smp.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/print.hh>
+
+using namespace seastar;
+
+future<bool> test_smp_call() {
+ return smp::submit_to(1, [] {
+ return make_ready_future<int>(3);
+ }).then([] (int ret) {
+ return make_ready_future<bool>(ret == 3);
+ });
+}
+
+struct nasty_exception {};
+
+future<bool> test_smp_exception() {
+ fmt::print("1\n");
+ return smp::submit_to(1, [] {
+ fmt::print("2\n");
+ auto x = make_exception_future<int>(nasty_exception());
+ fmt::print("3\n");
+ return x;
+ }).then_wrapped([] (future<int> result) {
+ fmt::print("4\n");
+ try {
+ result.get();
+ return make_ready_future<bool>(false); // expected an exception
+ } catch (nasty_exception&) {
+ // all is well
+ return make_ready_future<bool>(true);
+ } catch (...) {
+ // incorrect exception type
+ return make_ready_future<bool>(false);
+ }
+ });
+}
+
+int tests, fails;
+
+future<>
+report(sstring msg, future<bool>&& result) {
+ return std::move(result).then([msg] (bool result) {
+ fmt::print("{}: {}\n", (result ? "PASS" : "FAIL"), msg);
+ tests += 1;
+ fails += !result;
+ });
+}
+
+int main(int ac, char** av) {
+ return app_template().run_deprecated(ac, av, [] {
+ return report("smp call", test_smp_call()).then([] {
+ return report("smp exception", test_smp_exception());
+ }).then([] {
+ fmt::print("\n{:d} tests / {:d} failures\n", tests, fails);
+ engine().exit(fails ? 1 : 0);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/socket_test.cc b/src/seastar/tests/unit/socket_test.cc
new file mode 100644
index 000000000..b4640bbfc
--- /dev/null
+++ b/src/seastar/tests/unit/socket_test.cc
@@ -0,0 +1,82 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Elazar Leibovich
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/memory.hh>
+#include <seastar/util/std-compat.hh>
+
+#include <seastar/net/posix-stack.hh>
+
+using namespace seastar;
+
+future<> handle_connection(connected_socket s) {
+ auto in = s.input();
+ auto out = s.output();
+ return do_with(std::move(in), std::move(out), [](auto& in, auto& out) {
+ return do_until([&in]() { return in.eof(); },
+ [&in, &out] {
+ return in.read().then([&out](auto buf) {
+ return out.write(std::move(buf)).then([&out]() { return out.close(); });
+ });
+ });
+ });
+}
+
+future<> echo_server_loop() {
+ return do_with(
+ server_socket(listen(make_ipv4_address({1234}), listen_options{.reuse_address = true})), [](auto& listener) {
+ // Connect asynchronously in background.
+ (void)connect(make_ipv4_address({"127.0.0.1", 1234})).then([](connected_socket&& socket) {
+ socket.shutdown_output();
+ });
+ return listener.accept().then(
+ [](accept_result ar) {
+ connected_socket s = std::move(ar.connection);
+ return handle_connection(std::move(s));
+ }).then([l = std::move(listener)]() mutable { return l.abort_accept(); });
+ });
+}
+
+class my_malloc_allocator : public std::pmr::memory_resource {
+public:
+ int allocs;
+ int frees;
+ void* do_allocate(std::size_t bytes, std::size_t alignment) override { allocs++; return malloc(bytes); }
+ void do_deallocate(void *ptr, std::size_t bytes, std::size_t alignment) override { frees++; return free(ptr); }
+ virtual bool do_is_equal(const std::pmr::memory_resource& __other) const noexcept override { abort(); }
+};
+
+my_malloc_allocator malloc_allocator;
+std::pmr::polymorphic_allocator<char> allocator{&malloc_allocator};
+
+int main(int ac, char** av) {
+ register_network_stack("posix", boost::program_options::options_description(),
+ [](boost::program_options::variables_map ops) {
+ return smp::main_thread() ? net::posix_network_stack::create(ops, &allocator)
+ : net::posix_ap_network_stack::create(ops);
+ }, true);
+ return app_template().run_deprecated(ac, av, [] {
+ return echo_server_loop().finally([](){ engine().exit((malloc_allocator.allocs == malloc_allocator.frees) ? 0 : 1); });
+ });
+}
diff --git a/src/seastar/tests/unit/sstring_test.cc b/src/seastar/tests/unit/sstring_test.cc
new file mode 100644
index 000000000..c93632683
--- /dev/null
+++ b/src/seastar/tests/unit/sstring_test.cc
@@ -0,0 +1,186 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2014 Cloudius Systems
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/sstring.hh>
+#include <list>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(test_make_sstring) {
+ std::string_view foo = "foo";
+ std::string bar = "bar";
+ sstring zed = "zed";
+ const char* baz = "baz";
+ BOOST_REQUIRE_EQUAL(make_sstring(foo, bar, zed, baz, "bah"), sstring("foobarzedbazbah"));
+}
+
+BOOST_AUTO_TEST_CASE(test_construction) {
+ BOOST_REQUIRE_EQUAL(sstring(std::string_view("abc")), sstring("abc"));
+}
+
+BOOST_AUTO_TEST_CASE(test_equality) {
+ BOOST_REQUIRE_EQUAL(sstring("aaa"), sstring("aaa"));
+}
+
+BOOST_AUTO_TEST_CASE(test_to_sstring) {
+ BOOST_REQUIRE_EQUAL(to_sstring(1234567), sstring("1234567"));
+}
+
+BOOST_AUTO_TEST_CASE(test_add_literal_to_sstring) {
+ BOOST_REQUIRE_EQUAL("x" + sstring("y"), sstring("xy"));
+}
+
+BOOST_AUTO_TEST_CASE(test_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find('b'), 1u);
+ BOOST_REQUIRE_EQUAL(sstring("babcde").find('b',1), 2u);
+}
+
+BOOST_AUTO_TEST_CASE(test_not_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find('x'), sstring::npos);
+}
+
+BOOST_AUTO_TEST_CASE(test_str_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find("bc"), 1u);
+ BOOST_REQUIRE_EQUAL(sstring("abcbcde").find("bc", 2), 3u);
+}
+
+BOOST_AUTO_TEST_CASE(test_str_not_find_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").find("x"), sstring::npos);
+}
+
+BOOST_AUTO_TEST_CASE(test_substr_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").substr(1,2), "bc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(1,2), "bc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(1,3), "bc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(0, 2), "ab");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(3, 2), "");
+ BOOST_REQUIRE_EQUAL(sstring("abc").substr(1), "bc");
+}
+
+BOOST_AUTO_TEST_CASE(test_substr_eor_sstring) {
+ BOOST_REQUIRE_THROW(sstring("abcde").substr(6,1), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_CASE(test_at_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("abcde").at(1), 'b');
+ BOOST_REQUIRE_THROW(sstring("abcde").at(6), std::out_of_range);
+ sstring s("abcde");
+ s.at(1) = 'd';
+ BOOST_REQUIRE_EQUAL(s, "adcde");
+}
+
+BOOST_AUTO_TEST_CASE(test_find_last_sstring) {
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a'), 4u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',5), 4u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',4), 4u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',3), 2u);
+ BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('x'), sstring::npos);
+ BOOST_REQUIRE_EQUAL(sstring("").find_last_of('a'), sstring::npos);
+}
+
+
+BOOST_AUTO_TEST_CASE(test_append) {
+ BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 3), "aba123");
+ BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 4), "aba1234");
+ BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 0), "aba");
+}
+
+BOOST_AUTO_TEST_CASE(test_replace) {
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(1,1, "xyz", 1), "axc");
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(3,2, "xyz", 2), "abcxy");
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(2,2, "xyz", 2), "abxy");
+ BOOST_REQUIRE_EQUAL(sstring("abc").replace(0,2, "", 0), "c");
+ BOOST_REQUIRE_THROW(sstring("abc").replace(4,1, "xyz", 1), std::out_of_range);
+ const char* s = "xyz";
+ sstring str("abcdef");
+ BOOST_REQUIRE_EQUAL(str.replace(str.begin() + 1 , str.begin() + 3, s + 1, s + 3), "ayzdef");
+ BOOST_REQUIRE_THROW(sstring("abc").replace(4,1, "xyz", 1), std::out_of_range);
+
+}
+
+BOOST_AUTO_TEST_CASE(test_insert) {
+ sstring str("abc");
+ const char* s = "xyz";
+ str.insert(str.begin() +1, s + 1, s + 2);
+ BOOST_REQUIRE_EQUAL(str, "aybc");
+ str = "abc";
+ BOOST_REQUIRE_THROW(str.insert(str.begin() + 5, s + 1, s + 2), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_CASE(test_erase) {
+ sstring str("abcdef");
+ auto i = str.erase(str.begin() + 1, str.begin() + 3);
+ BOOST_REQUIRE_EQUAL(*i, 'd');
+ BOOST_REQUIRE_EQUAL(str, "adef");
+}
+
+BOOST_AUTO_TEST_CASE(test_ctor_iterator) {
+ std::list<char> data{{'a', 'b', 'c'}};
+ sstring s(data.begin(), data.end());
+ BOOST_REQUIRE_EQUAL(s, "abc");
+}
+
+BOOST_AUTO_TEST_CASE(test_nul_termination) {
+ using stype = basic_sstring<char, uint32_t, 15, true>;
+
+ for (int size = 1; size <= 32; size *= 2) {
+ auto s1 = uninitialized_string<stype>(size - 1);
+ BOOST_REQUIRE_EQUAL(s1.c_str()[size - 1], '\0');
+ auto s2 = uninitialized_string<stype>(size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[size], '\0');
+
+ s1 = stype("01234567890123456789012345678901", size - 1);
+ BOOST_REQUIRE_EQUAL(s1.c_str()[size - 1], '\0');
+ s2 = stype("01234567890123456789012345678901", size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[size], '\0');
+
+ s1 = stype(size - 1, ' ');
+ BOOST_REQUIRE_EQUAL(s1.c_str()[size - 1], '\0');
+ s2 = stype(size, ' ');
+ BOOST_REQUIRE_EQUAL(s2.c_str()[size], '\0');
+
+ s2 = s1;
+ BOOST_REQUIRE_EQUAL(s2.c_str()[s1.size()], '\0');
+ s2.resize(s1.size());
+ BOOST_REQUIRE_EQUAL(s2.c_str()[s1.size()], '\0');
+ BOOST_REQUIRE_EQUAL(s1, s2);
+
+ auto new_size = size / 2;
+ s2 = s1;
+ s2.resize(new_size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[new_size], '\0');
+ BOOST_REQUIRE(!strncmp(s1.c_str(), s2.c_str(), new_size));
+
+ new_size = size * 2;
+ s2 = s1;
+ s2.resize(new_size);
+ BOOST_REQUIRE_EQUAL(s2.c_str()[new_size], '\0');
+ BOOST_REQUIRE(!strncmp(s1.c_str(), s2.c_str(), std::min(s1.size(), s2.size())));
+
+ new_size = size * 2;
+ s2 = s1 + s1;
+ BOOST_REQUIRE_EQUAL(s2.c_str()[s2.size()], '\0');
+ BOOST_REQUIRE(!strncmp(s1.c_str(), s2.c_str(), std::min(s1.size(), s2.size())));
+ }
+}
diff --git a/src/seastar/tests/unit/stall_detector_test.cc b/src/seastar/tests/unit/stall_detector_test.cc
new file mode 100644
index 000000000..f9e4ba7a0
--- /dev/null
+++ b/src/seastar/tests/unit/stall_detector_test.cc
@@ -0,0 +1,108 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2018 ScyllaDB Ltd.
+ */
+
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread_cputime_clock.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/util/later.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <../../src/core/stall_detector.hh>
+#include <atomic>
+#include <chrono>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+class temporary_stall_detector_settings {
+ std::chrono::milliseconds _old_threshold;
+ std::function<void ()> _old_report;
+public:
+ temporary_stall_detector_settings(std::chrono::duration<double> threshold, std::function<void ()> report)
+ : _old_threshold(engine().get_blocked_reactor_notify_ms())
+ , _old_report(engine().get_stall_detector_report_function()) {
+ engine().update_blocked_reactor_notify_ms(std::chrono::duration_cast<std::chrono::milliseconds>(threshold));
+ engine().set_stall_detector_report_function(std::move(report));
+ }
+ ~temporary_stall_detector_settings() {
+ engine().update_blocked_reactor_notify_ms(_old_threshold);
+ engine().set_stall_detector_report_function(std::move(_old_report));
+ }
+};
+
+void spin(std::chrono::duration<double> how_much) {
+ auto end = internal::cpu_stall_detector::clock_type::now() + how_much;
+ while (internal::cpu_stall_detector::clock_type::now() < end) {
+ // spin!
+ }
+}
+
+void spin_some_cooperatively(std::chrono::duration<double> how_much) {
+ auto end = std::chrono::steady_clock::now() + how_much;
+ while (std::chrono::steady_clock::now() < end) {
+ spin(200us);
+ if (need_preempt()) {
+ thread::yield();
+ }
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(normal_case) {
+ std::atomic<unsigned> reports{};
+ temporary_stall_detector_settings tsds(10ms, [&] { ++reports; });
+ spin_some_cooperatively(1s);
+ BOOST_REQUIRE_EQUAL(reports, 0);
+}
+
+SEASTAR_THREAD_TEST_CASE(simple_stalls) {
+ std::atomic<unsigned> reports{};
+ temporary_stall_detector_settings tsds(10ms, [&] { ++reports; });
+ unsigned nr = 10;
+ for (unsigned i = 0; i < nr; ++i) {
+ spin_some_cooperatively(100ms);
+ spin(20ms);
+ }
+ spin_some_cooperatively(100ms);
+
+ // blocked-reactor-reports-per-minute defaults to 5, so we don't
+ // get all 10 reports.
+ BOOST_REQUIRE_EQUAL(reports, 5);
+}
+
+SEASTAR_THREAD_TEST_CASE(no_poll_no_stall) {
+ std::atomic<unsigned> reports{};
+ temporary_stall_detector_settings tsds(10ms, [&] { ++reports; });
+ spin_some_cooperatively(1ms); // need to yield so that stall detector change from above take effect
+ static constexpr unsigned tasks = 2000;
+ promise<> p;
+ auto f = p.get_future();
+ parallel_for_each(boost::irange(0u, tasks), [&p] (unsigned int i) {
+ (void)later().then([i, &p] {
+ spin(500us);
+ if (i == tasks - 1) {
+ p.set_value();
+ }
+ });
+ return make_ready_future<>();
+ }).get();
+ f.get();
+ BOOST_REQUIRE_EQUAL(reports, 0);
+}
diff --git a/src/seastar/tests/unit/thread_context_switch_test.cc b/src/seastar/tests/unit/thread_context_switch_test.cc
new file mode 100644
index 000000000..28cefd4e9
--- /dev/null
+++ b/src/seastar/tests/unit/thread_context_switch_test.cc
@@ -0,0 +1,96 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/app-template.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/distributed.hh>
+#include <seastar/core/sleep.hh>
+#include <fmt/printf.h>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+class context_switch_tester {
+ uint64_t _switches{0};
+ semaphore _s1{0};
+ semaphore _s2{0};
+ bool _done1{false};
+ bool _done2{false};
+ thread _t1{[this] { main1(); }};
+ thread _t2{[this] { main2(); }};
+private:
+ void main1() {
+ while (!_done1) {
+ _s1.wait().get();
+ ++_switches;
+ _s2.signal();
+ }
+ _done2 = true;
+ }
+ void main2() {
+ while (!_done2) {
+ _s2.wait().get();
+ ++_switches;
+ _s1.signal();
+ }
+ }
+public:
+ void begin_measurement() {
+ _s1.signal();
+ }
+ future<uint64_t> measure() {
+ _done1 = true;
+ return _t1.join().then([this] {
+ return _t2.join();
+ }).then([this] {
+ return _switches;
+ });
+ }
+ future<> stop() {
+ return make_ready_future<>();
+ }
+};
+
+int main(int ac, char** av) {
+ static const auto test_time = 5s;
+ return app_template().run_deprecated(ac, av, [] {
+ auto dcstp = std::make_unique<distributed<context_switch_tester>>();
+ auto& dcst = *dcstp;
+ return dcst.start().then([&dcst] {
+ return dcst.invoke_on_all(&context_switch_tester::begin_measurement);
+ }).then([] {
+ return sleep(test_time);
+ }).then([&dcst] {
+ return dcst.map_reduce0(std::mem_fn(&context_switch_tester::measure), uint64_t(), std::plus<uint64_t>());
+ }).then([] (uint64_t switches) {
+ switches /= smp::count;
+ fmt::print("context switch time: {:5.1f} ns\n",
+ double(std::chrono::duration_cast<std::chrono::nanoseconds>(test_time).count()) / switches);
+ }).then([&dcst] {
+ return dcst.stop();
+ }).then([dcstp = std::move(dcstp)] {
+ engine_exit(0);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/thread_test.cc b/src/seastar/tests/unit/thread_test.cc
new file mode 100644
index 000000000..c1190b9cb
--- /dev/null
+++ b/src/seastar/tests/unit/thread_test.cc
@@ -0,0 +1,264 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/thread.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/semaphore.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/sleep.hh>
+#include <sys/mman.h>
+#include <sys/signal.h>
+
+#include <valgrind/valgrind.h>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+SEASTAR_TEST_CASE(test_thread_1) {
+ return do_with(sstring(), [] (sstring& x) {
+ auto t1 = new thread([&x] {
+ x = "abc";
+ });
+ return t1->join().then([&x, t1] {
+ BOOST_REQUIRE_EQUAL(x, "abc");
+ delete t1;
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_2) {
+ struct tmp {
+ std::vector<thread> threads;
+ semaphore sem1{0};
+ semaphore sem2{0};
+ int counter = 0;
+ void thread_fn() {
+ sem1.wait(1).get();
+ ++counter;
+ sem2.signal(1);
+ }
+ };
+ return do_with(tmp(), [] (tmp& x) {
+ auto n = 10;
+ for (int i = 0; i < n; ++i) {
+ x.threads.emplace_back(std::bind(&tmp::thread_fn, &x));
+ }
+ BOOST_REQUIRE_EQUAL(x.counter, 0);
+ x.sem1.signal(n);
+ return x.sem2.wait(n).then([&x, n] {
+ BOOST_REQUIRE_EQUAL(x.counter, n);
+ return parallel_for_each(x.threads.begin(), x.threads.end(), std::mem_fn(&thread::join));
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_async) {
+ sstring x = "x";
+ sstring y = "y";
+ auto concat = [] (sstring x, sstring y) {
+ sleep(10ms).get();
+ return x + y;
+ };
+ return async(concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_async_immed) {
+ return async([] { return 3; }).then([] (int three) {
+ BOOST_REQUIRE_EQUAL(three, 3);
+ });
+}
+
+SEASTAR_TEST_CASE(test_thread_async_nested) {
+ return async([] {
+ return async([] {
+ return 3;
+ }).get0();
+ }).then([] (int three) {
+ BOOST_REQUIRE_EQUAL(three, 3);
+ });
+}
+
+void compute(float& result, bool& done, uint64_t& ctr) {
+ while (!done) {
+ for (int n = 0; n < 10000; ++n) {
+ result += 1 / (result + 1);
+ ++ctr;
+ }
+ thread::yield();
+ }
+}
+
+#if defined(SEASTAR_ASAN_ENABLED) && defined(SEASTAR_HAVE_ASAN_FIBER_SUPPORT)
+volatile int force_write;
+volatile void* shut_up_gcc;
+
+[[gnu::noinline]]
+void throw_exception() {
+ volatile char buf[1024];
+ shut_up_gcc = &buf;
+ for (int i = 0; i < 1024; i++) {
+ buf[i] = force_write;
+ }
+ throw 1;
+}
+
+[[gnu::noinline]]
+void use_stack() {
+ volatile char buf[2 * 1024];
+ shut_up_gcc = &buf;
+ for (int i = 0; i < 2 * 1024; i++) {
+ buf[i] = force_write;
+ }
+}
+
+SEASTAR_TEST_CASE(test_asan_false_positive) {
+ return async([] {
+ try {
+ throw_exception();
+ } catch (...) {
+ use_stack();
+ }
+ });
+}
+#endif
+
+SEASTAR_THREAD_TEST_CASE_EXPECTED_FAILURES(abc, 2) {
+ BOOST_TEST(false);
+ BOOST_TEST(false);
+}
+
+SEASTAR_TEST_CASE(test_thread_custom_stack_size) {
+ sstring x = "x";
+ sstring y = "y";
+ auto concat = [] (sstring x, sstring y) {
+ sleep(10ms).get();
+ return x + y;
+ };
+ thread_attributes attr;
+ attr.stack_size = 16384;
+ return async(attr, concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ });
+}
+
+// The test case uses x86_64 specific signal handler info. The test
+// fails with detect_stack_use_after_return=1. We could put it behind
+// a command line option and fork/exec to run it after removing
+// detect_stack_use_after_return=1 from the environment.
+#if defined(SEASTAR_THREAD_STACK_GUARDS) && defined(__x86_64__) && !defined(SEASTAR_ASAN_ENABLED)
+struct test_thread_custom_stack_size_failure : public seastar::testing::seastar_test {
+ const char* get_test_file() override { return __FILE__; }
+ const char* get_name() override { return "test_thread_custom_stack_size_failure"; }
+ int get_expected_failures() override { return 0; } \
+ seastar::future<> run_test_case() override;
+};
+
+static test_thread_custom_stack_size_failure test_thread_custom_stack_size_failure_instance;
+static thread_local volatile bool stack_guard_bypassed = false;
+
+static int get_mprotect_flags(void* ctx) {
+ int flags;
+ ucontext_t* context = reinterpret_cast<ucontext_t*>(ctx);
+ if (context->uc_mcontext.gregs[REG_ERR] & 0x2) {
+ flags = PROT_READ | PROT_WRITE;
+ } else {
+ flags = PROT_READ;
+ }
+ return flags;
+}
+
+static void* pagealign(void* ptr, size_t page_size) {
+ static const int pageshift = ffs(page_size) - 1;
+ return reinterpret_cast<void*>(((reinterpret_cast<intptr_t>((ptr)) >> pageshift) << pageshift));
+}
+
+static thread_local struct sigaction default_old_sigsegv_handler;
+
+static void bypass_stack_guard(int sig, siginfo_t* si, void* ctx) {
+ assert(sig == SIGSEGV);
+ int flags = get_mprotect_flags(ctx);
+ stack_guard_bypassed = (flags & PROT_WRITE);
+ if (!stack_guard_bypassed) {
+ return;
+ }
+ size_t page_size = getpagesize();
+ auto mp_result = mprotect(pagealign(si->si_addr, page_size), page_size, PROT_READ | PROT_WRITE);
+ assert(mp_result == 0);
+}
+
+// This test will fail with a regular stack size, because we only probe
+// around 10KiB of data, and the stack guard resides after 128'th KiB.
+seastar::future<> test_thread_custom_stack_size_failure::run_test_case() {
+ if (RUNNING_ON_VALGRIND) {
+ return make_ready_future<>();
+ }
+
+ sstring x = "x";
+ sstring y = "y";
+
+ // Catch segmentation fault once:
+ struct sigaction sa{};
+ sa.sa_sigaction = &bypass_stack_guard;
+ sa.sa_flags = SA_SIGINFO;
+ auto ret = sigaction(SIGSEGV, &sa, &default_old_sigsegv_handler);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+
+ auto concat = [] (sstring x, sstring y) {
+ sleep(10ms).get();
+ // Probe the stack by writing to it in intervals of 1024,
+ // until we hit a write fault. In order not to ruin anything,
+ // the "write" uses data it just read from the address.
+ volatile char* mem = reinterpret_cast<volatile char*>(&x);
+ for (int i = 0; i < 20; ++i) {
+ mem[i*-1024] = char(mem[i*-1024]);
+ if (stack_guard_bypassed) {
+ break;
+ }
+ }
+ return x + y;
+ };
+ thread_attributes attr;
+ attr.stack_size = 16384;
+ return async(attr, concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ BOOST_REQUIRE(stack_guard_bypassed);
+ auto ret = sigaction(SIGSEGV, &default_old_sigsegv_handler, nullptr);
+ if (ret) {
+ throw std::system_error(ret, std::system_category());
+ }
+ }).then([concat, x, y] {
+ // The same function with a default stack will not trigger
+ // a segfault, because its stack is much bigger than 10KiB
+ return async(concat, x, y).then([] (sstring xy) {
+ BOOST_REQUIRE_EQUAL(xy, "xy");
+ });
+ });
+}
+#endif // SEASTAR_THREAD_STACK_GUARDS && __x86_64__
diff --git a/src/seastar/tests/unit/timer_test.cc b/src/seastar/tests/unit/timer_test.cc
new file mode 100644
index 000000000..ca698b7eb
--- /dev/null
+++ b/src/seastar/tests/unit/timer_test.cc
@@ -0,0 +1,141 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/timer.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/sleep.hh>
+#include <chrono>
+#include <iostream>
+
+using namespace seastar;
+using namespace std::chrono_literals;
+
+#define BUG() do { \
+ std::cerr << "ERROR @ " << __FILE__ << ":" << __LINE__ << std::endl; \
+ throw std::runtime_error("test failed"); \
+ } while (0)
+
+#define OK() do { \
+ std::cerr << "OK @ " << __FILE__ << ":" << __LINE__ << std::endl; \
+ } while (0)
+
+template <typename Clock>
+struct timer_test {
+ timer<Clock> t1;
+ timer<Clock> t2;
+ timer<Clock> t3;
+ timer<Clock> t4;
+ timer<Clock> t5;
+ promise<> pr1;
+ promise<> pr2;
+
+ future<> run() {
+ t1.set_callback([this] {
+ OK();
+ fmt::print(" 500ms timer expired\n");
+ if (!t4.cancel()) {
+ BUG();
+ }
+ if (!t5.cancel()) {
+ BUG();
+ }
+ t5.arm(1100ms);
+ });
+ t2.set_callback([] { OK(); fmt::print(" 900ms timer expired\n"); });
+ t3.set_callback([] { OK(); fmt::print("1000ms timer expired\n"); });
+ t4.set_callback([] { OK(); fmt::print(" BAD cancelled timer expired\n"); });
+ t5.set_callback([this] { OK(); fmt::print("1600ms rearmed timer expired\n"); pr1.set_value(); });
+
+ t1.arm(500ms);
+ t2.arm(900ms);
+ t3.arm(1000ms);
+ t4.arm(700ms);
+ t5.arm(800ms);
+
+ return pr1.get_future().then([this] { return test_timer_cancelling(); }).then([this] {
+ return test_timer_with_scheduling_groups();
+ });
+ }
+
+ future<> test_timer_cancelling() {
+ timer<Clock>& t1 = *new timer<Clock>();
+ t1.set_callback([] { BUG(); });
+ t1.arm(100ms);
+ t1.cancel();
+
+ t1.arm(100ms);
+ t1.cancel();
+
+ t1.set_callback([this] { OK(); pr2.set_value(); });
+ t1.arm(100ms);
+ return pr2.get_future().then([&t1] { delete &t1; });
+ }
+
+ future<> test_timer_with_scheduling_groups() {
+ return async([] {
+ auto sg1 = create_scheduling_group("sg1", 100).get0();
+ auto sg2 = create_scheduling_group("sg2", 100).get0();
+ thread_attributes t1attr;
+ t1attr.sched_group = sg1;
+ auto expirations = 0;
+ async(t1attr, [&] {
+ auto make_callback_checking_sg = [&] (scheduling_group sg_to_check) {
+ return [sg_to_check, &expirations] {
+ ++expirations;
+ if (current_scheduling_group() != sg_to_check) {
+ BUG();
+ }
+ };
+ };
+ timer<Clock> t1(make_callback_checking_sg(sg1));
+ t1.arm(10ms);
+ timer<Clock> t2(sg2, make_callback_checking_sg(sg2));
+ t2.arm(10ms);
+ sleep(500ms).get();
+ if (expirations != 2) {
+ BUG();
+ }
+ OK();
+ }).get();
+ destroy_scheduling_group(sg1).get();
+ destroy_scheduling_group(sg2).get();
+ });
+ }
+};
+
+int main(int ac, char** av) {
+ app_template app;
+ timer_test<steady_clock_type> t1;
+ timer_test<lowres_clock> t2;
+ return app.run_deprecated(ac, av, [&t1, &t2] {
+ fmt::print("=== Start High res clock test\n");
+ return t1.run().then([&t2] {
+ fmt::print("=== Start Low res clock test\n");
+ return t2.run();
+ }).then([] {
+ fmt::print("Done\n");
+ engine().exit(0);
+ });
+ });
+}
diff --git a/src/seastar/tests/unit/tls-ca-bundle.pem b/src/seastar/tests/unit/tls-ca-bundle.pem
new file mode 100644
index 000000000..d56c7e67d
--- /dev/null
+++ b/src/seastar/tests/unit/tls-ca-bundle.pem
@@ -0,0 +1,4195 @@
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2
+MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
+ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk
+hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym
+1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW
+OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb
+2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko
+O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU
+AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF
+Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb
+LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir
+oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C
+MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
+sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2
+MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
+ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC
+206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci
+KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2
+JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9
+BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e
+Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B
+PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67
+Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq
+Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ
+o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3
++L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj
+YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj
+FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn
+xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2
+LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc
+obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8
+CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe
+IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA
+DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F
+AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX
+Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb
+AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl
+Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw
+RY8mkaKO/qk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
+ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
+MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
+LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
+KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
+RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
+WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
+Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
+AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
+eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
+zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
+/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
+VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
+bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
+b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
+iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
+r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
+04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
+GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
+3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
+lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
+NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
+cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
+2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
+JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
+Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
+n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
+PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
+dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
+MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
+MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
+A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
+b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
+cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
+VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
+ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
+uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
+9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
+hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
+pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
+MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
+MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
+dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
+cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
+DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
+yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
+L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
+EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
+7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
+QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
+qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
+NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
+LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
+TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
+LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
+I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
+nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
+NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
+dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
+WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
+v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
+UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
+IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
+W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
+lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
+AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
+c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
+MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
+emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
+DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
+FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
+UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
+YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
+MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
+pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
+13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
+AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
+U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
+F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
+oJ2daZH9
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB
+VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp
+bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R
+dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw
+MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy
+dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52
+ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM
+EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj
+lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ
+znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH
+2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1
+k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs
+2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD
+VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
+AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG
+KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+
+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R
+FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
+mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE
+DNuxUCAKGkq6ahq97BvIxYSazQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
+AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
+CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
+BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
+VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
+qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
+HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
+G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
+lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
+IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
+0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
+k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
+4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
+m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
+cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
+uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
+KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
+ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
+AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
+VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
+CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
+cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
+QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
+7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
+cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
+QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
+czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
+aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
+aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
+DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
+BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
+D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
+JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
+AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
+vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
+tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
+7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
+h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
+d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
+pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE
+AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x
+CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW
+MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF
+RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
+AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7
+09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7
+XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P
+Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK
+t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb
+X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28
+MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU
+fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI
+2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH
+K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae
+ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP
+BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ
+MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw
+RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm
+fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3
+gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe
+I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i
+5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi
+ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn
+MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ
+o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6
+zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN
+GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt
+r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK
+Z05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
+BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
+MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
+SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
+ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
+UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
+4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
+KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
+gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
+rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
+51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
+be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
+KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
+v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
+fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
+jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
+ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
+e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
+jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
+WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
+SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
+pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
+X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
+fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
+K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
+ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
+LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
+LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
+MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
+VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
+CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
+tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
+dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
+PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
+BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
+ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
+7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
+43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
+pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
+WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
+MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
+ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
+BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
+6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
+GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
+dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
+1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
+62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
+BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
+MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
+cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
+b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
+IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
+iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
+4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
+XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
+MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
+EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
+BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
+xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
+87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
+2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
+WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
+0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
+A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
+pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
+ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
+aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
+hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
+hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
+P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
+iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
+xqE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
+Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
+ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
+MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
+yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
+VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
+nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
+XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
+vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
+Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
+N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
+nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
+YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
+kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
+QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
+6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
+yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
+QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
+tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
+QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
+Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
+olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
+x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
+dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
+A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
+cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
+qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
+JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
+s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
+HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
+70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
+V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
+qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
+5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
+C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
+OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
+FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
+KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
+8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
+MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
+0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
+u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
+u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
+YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
+GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
+RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
+KeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
+VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
+cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
+BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
+VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
+0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
+ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
+A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
+aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
+flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc
+MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp
+b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT
+AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs
+aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H
+j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K
+f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55
+IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw
+FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht
+QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm
+/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ
+k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ
+MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC
+seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ
+hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+
+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U
+DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj
+B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
+AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
+EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
+FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
+REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
+Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
+VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
+SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
+4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
+cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
+eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
+HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
+A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
+DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
+vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
+DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
+maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
+lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
+KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
+MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
+VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
+ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
+AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
+661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
+am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
+ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
+PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
+3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
+SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
+3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
+ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
+StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
+Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
+jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
+DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
+ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
+VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
+mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
+IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
+mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
+XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
+dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
+jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
+DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
+9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
+jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
+Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
+ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
+R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0
+ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX
+l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB
+HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B
+5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3
+WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP
+gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+
+DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu
+BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs
+h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk
+LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
+6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
+L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
+1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
+MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
+QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
+arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
+Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
+FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
+P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
+9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
+uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
+9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
+OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
+KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
+DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
+H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
+I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
+5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
+3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
+Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg
+isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z
+NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI
++MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R
+hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+
+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP
+Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s
+EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2
+mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC
+e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow
+dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
+ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
+N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
+tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
+0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
+/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
+KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
+zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
+O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
+34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
+K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
+Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
+QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
+IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
+HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
+O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
+033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
+dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
+kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
+3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
+u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
+4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET
+MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE
+AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw
+CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg
+YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE
+Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX
+mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD
+XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW
+S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp
+FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD
+AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu
+ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z
+ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv
+Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw
+DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6
+yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq
+EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB
+EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN
+PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy
+MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk
+D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o
+OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A
+fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe
+IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n
+oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK
+/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj
+rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD
+3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE
+7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC
+yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd
+qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI
+hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
+xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA
+SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo
+HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB
+emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC
+AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb
+7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x
+DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk
+F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF
+a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT
+Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
+MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
+NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
+PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
+x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
+QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
+yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
+QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
+H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
+QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
+i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
+nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
+rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
+hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
+GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
+lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
+TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
+nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
+gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
+G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
+zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
+L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD
+TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2
+MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF
+Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh
+IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6
+dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO
+V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC
+GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN
+v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB
+AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB
+Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO
+76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK
+OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH
+ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi
+yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL
+buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj
+2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
+UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
+2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
+Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
+nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
+/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
+PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
+QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
+SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
+IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
+zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
+BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
+ZQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
+IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
+MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
+ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
+T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
+FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
+cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
+BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
+fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
+GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
+6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
+pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
+9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
+/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
+Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
+qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
+SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
+u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
+Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
+crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
+FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
+/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
+wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
+4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
+2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
+FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
+CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
+boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
+jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
+S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
+QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
+0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
+NVOFBkpdn627G190
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
+MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
+ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
+b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
+MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
+ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
+IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
+AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
+unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
+BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
+7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
+0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
+roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
+A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
+aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
+26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
+BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
+EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
+BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
+AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
+p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
+1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
+XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
+eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
+tGWaIZDgqtCYvDi1czyL+Nw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
+MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
+ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
+YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
+MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
+NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
+A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
+A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
+Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
+QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
+eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
+B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
+z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
+AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
+ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
+TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
+MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
+VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
+VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
+AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
+bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
+ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
+VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
+ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
+AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
+BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
+DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
+BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
+QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
+gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
+zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
+130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
+JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
+ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
+AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
+AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
+9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
+bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
+fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
+HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
+t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET
+MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk
+BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4
+Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl
+cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0
+aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY
+F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N
+8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe
+rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K
+/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu
+7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC
+28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6
+lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E
+nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB
+0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09
+5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj
+WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN
+jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s
+ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM
+OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q
+619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn
+2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj
+o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v
+nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG
+5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq
+pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb
+dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0
+BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
+PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
+cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
+MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
+IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
+ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
+VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
+kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
+EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
+H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
+HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
+DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
+QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
+Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
+AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
+yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
+FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
+ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
+kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
+l7+ijrRU
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
+MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
+QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
+MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
+QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
+jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
+ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
+ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
+Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
+AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
+HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
+uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
+TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
+xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
+CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
+O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
+6GAqm4VKQPNriiTsBhYscw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
+MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
+ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
+cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
+WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
+Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
+IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
+UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
+TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
+BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
+kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
+AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
+HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
+sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
+I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
+J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
+VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
+IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
+MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
+dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
+EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
+MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
+28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
+VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
+DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
+5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
+ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
+Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
+UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
+Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
+hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
+HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
+YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
+L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
+ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
+IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
+HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
+DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
+PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
+5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
+glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
+FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
+pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
+xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
+tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
+jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
+fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
+d0jQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
+Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g
+Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0
+aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa
+Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg
+SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo
+aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp
+ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z
+7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//
+DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx
+zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8
+hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs
+4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u
+gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY
+NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
+FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3
+j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG
+52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB
+echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
+ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI
+zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy
+wy39FCqQmbkHzJ8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw
+PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu
+MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx
+GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL
+MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf
+HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh
+gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW
+v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue
+Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr
+9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt
+6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7
+MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl
+Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58
+ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq
+hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p
+iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC
+dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL
+kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL
+hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
+YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
+GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
+BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
+3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
+YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
+rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
+ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
+oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
+QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
+b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
+AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
+GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
+G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
+l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
+smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
+ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
+fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
+BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
+cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
+HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
+CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
+3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
+6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
+HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
+EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
+Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
+Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
+DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
+5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
+gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
+aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
+izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
+aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
+MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
+VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
+fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
+TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
+fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
+1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
+kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
+A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
+ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
+dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
+Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
+HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
+jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
+xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
+dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
+A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
+bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
+ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
+b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
+7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
+J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
+HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
+t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
+FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
+XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
+MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
+hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
+MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
+A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
+Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
+XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
+omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
+A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
+ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
+HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
+UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
+tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
+ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
+lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
+/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
+A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
+A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
+dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
+MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
+cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
+L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
+BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
+acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
+zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
+PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
+Johw1+qRzT65ysCQblrGXnRl11z+o+I=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
+NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
+BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
+ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
+3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
+qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
+p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
+HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
+ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
+HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
+Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
+c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
+RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
+dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
+Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
+3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
+CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
+xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
+KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
+ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
+MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
+VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
+FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
+ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
+gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
+fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
+ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
+ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
+MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
+c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
+dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
+aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
+hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
+QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
+h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
+rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
+9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
+PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
+Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
+rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
+OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
+xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
+7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
+aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
+SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
+ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
+AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
+R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
+JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
+Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
+MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
+IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
+IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
+RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
+U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
+IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
+ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
+QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
+rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
+NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
+QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
+txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
+BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
+AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
+tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
+IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
+6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
+Cm26OWMohpLzGITY+9HPBVZkVw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
+JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
+mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
+VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
+AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
+AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
+pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
+dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
+fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
+NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
+H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
+n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
+biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
+EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
+bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
+YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
+AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
+BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
+QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
+0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
+lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
+B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
+ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
+IhNzbM8m9Yop5w==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
+RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
+Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
+RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
+AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
+JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
+6pZjamVFkpUBtA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
+5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
+1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
+NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
+Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
+8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
+pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
+MrY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
+Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
+EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
+IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
+fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
+Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
+BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
+AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
+oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
+sycX
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
+RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
+ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
+xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
+ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
+DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
+jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
+CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
+EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
+fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
+uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
+chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
+9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
+ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
+SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
+fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
+sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
+cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
+0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
+4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
+r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
+/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
+gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1
+MQswCQYDVQQGEwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxp
+Z2kgQS5TLjE8MDoGA1UEAxMzZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZp
+a2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3MDEwNDExMzI0OFoXDTE3MDEwNDEx
+MzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0cm9uaWsgQmlsZ2kg
+R3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9uaWsg
+U2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdU
+MZTe1RK6UxYC6lhj71vY8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlT
+L/jDj/6z/P2douNffb7tC+Bg62nsM+3YjfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H
+5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAIJjjcJRFHLfO6IxClv7wC
+90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk9Ok0oSy1
+c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoE
+VtstxNulMA0GCSqGSIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLP
+qk/CaOv/gKlR6D1id4k9CnU58W5dF4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S
+/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwqD2fK/A+JYZ1lpTzlvBNbCNvj
+/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4Vwpm+Vganf2X
+KWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
+fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV
+BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC
+aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV
+BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1
+Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz
+MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+
+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp
+em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
+ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY
+B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH
+D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF
+Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo
+q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D
+k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH
+fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut
+dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM
+ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8
+zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
+rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX
+U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6
+Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5
+XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF
+Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR
+HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY
+GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c
+77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3
++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK
+vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6
+FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl
+yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P
+AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD
+y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d
+NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV
+BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt
+ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4
+MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg
+SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl
+a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h
+4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk
+tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s
+tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL
+dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4
+c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um
+TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z
++kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O
+Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW
+OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW
+fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2
+l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw
+FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+
+8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI
+6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO
+TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME
+wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY
+Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn
+xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q
+DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q
+Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t
+hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4
+7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7
+QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB
+8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy
+dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1
+YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3
+dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh
+IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD
+LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG
+EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g
+KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD
+ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu
+bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg
+ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R
+85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm
+4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV
+HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd
+QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t
+lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB
+o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4
+opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo
+dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW
+ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN
+AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y
+/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k
+SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy
+Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS
+Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
+nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
+MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
+czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
+CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
+MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
+ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
+b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
+euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
+bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
+WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
+MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
+1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
+zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
+BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
+BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
+v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
+E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
+iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
+GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
+MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
+LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
+YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
+A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
+K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
+sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
+MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
+XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
+HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
+4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
+j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
+U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
+u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
+bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
+fF6adulZkMV8gzURZVE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
+NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
+BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
+Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
+4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
+KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
+rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
+94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
+sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
+gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
+kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
+vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
+O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
+AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
+9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
+eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
+0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
+UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
+dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
+MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
+dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
+BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
+cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
+AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
+MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
+aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
+ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
+IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
+MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
+A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
+1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
+ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
+MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
+dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
+c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
+UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
+58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
+o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
+MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
+aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
+A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
+Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
+8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
+R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
+9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
+fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
+iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
+1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
+MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
+ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
+uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
+Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
+tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
+PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
+hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
+5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
+IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
+R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
+PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
+Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
+TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
+5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
+S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
+2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
+EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
+EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
+/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
+A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
+abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
+I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
+4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
+R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
+MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
+AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
+ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
+7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
+kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
+mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
+KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
+6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
+4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
+oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
+UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
+AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
+MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
+KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
+MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
+NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
+BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
+MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
+So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
+tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
+CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
+qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
+rD6ogRLQy7rQkgu2npaqBA+K
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
+mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
+MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
+eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
+BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
+BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
+hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
+5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
+JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
+DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
+huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
+AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
+zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
+kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
+SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
+spki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
+IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
+VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
+cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
+QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
+F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
+c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
+mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
+VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
+teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
+f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
+Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
+nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
+/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
+MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
+9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
+IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
+ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
+uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
+Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
+QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
+koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
+ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
+DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
+bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
+VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
+c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
+WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
+FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
+XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
+se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
+KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
+IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
+y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
+hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
+QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
+Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
+HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
+KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
+L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
+Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
+ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
+T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
+GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
+1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
+OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
+6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
+QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
+FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
+uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
+kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
+ewv4n4Q=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
+8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
+hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
+KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
+515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
+xwy8p2Fp8fc74SrL+SvzZpA3
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
+MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
+aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
+jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
+xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
+1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
+snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
+U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
+9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
+AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
+yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
+38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
+AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
+DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
+HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
+MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
+v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
+eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
+tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
+C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
+zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
+mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
+V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
+bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
+3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
+J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
+291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
+ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
+AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
+MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
+RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
+KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
+QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
+XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
+LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
+RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
+jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
+mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
+Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
+WD9f
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
+MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
+cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
+A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
+BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
+KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
+G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
+zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
+ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
+HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
+Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
+yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
+beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
+6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
+zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
+BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
+ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
+ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
+cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
+YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
+CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
+KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
+hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
+UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
+X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
+fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
+a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
+Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
+SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
+AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
+M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
+v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
+MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
+YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
+MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
+ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
+MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
+ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
+PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
+wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
+EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
+avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
+sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
+/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
+IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
+OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
+TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
+dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
+ReYNnyicsbkqWletNw+vHX/bvZ8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
+EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
+ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
+NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
+EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
+AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
+E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
+/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
+DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
+GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
+tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
+WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
+9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
+gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
+2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
+4uJEvlz36hz1
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
+RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
+dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
+YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
+NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
+EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
+cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
+dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
+fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
+bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
+75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
+FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
+HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
+5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
+b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
+A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
+6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
+dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
+Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
+l7WdmplNsDz4SgCbZN2fOUvRJ9e4
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
+FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
+Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
+A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
+b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
+jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
+PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
+ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
+nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
+q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
+MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
+mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
+7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
+oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
+EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
+fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
+AmvZWg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
+AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
+TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
+9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
+MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
+BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
+MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
+LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
+s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
+xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
+u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
+F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
+Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
+PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
+HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
+NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
+AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
+L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
+YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
+NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
+0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
+MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
+ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
+VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
+b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
+scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
+xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
+LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
+uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
+yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
+rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
+BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
+hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
+QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
+Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
+QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
+BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
+A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
+laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
+awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
+JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
+LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
+VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
+LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
+UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
+QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
+QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN
+AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp
+dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw
+MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw
+CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ
+MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB
+SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz
+ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH
+LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP
+PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL
+2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w
+ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC
+MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk
+AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0
+AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz
+AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz
+AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f
+BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY
+P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi
+CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g
+kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95
+HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS
+na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q
+qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z
+TbvGRNs2yyqcjg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw
+cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy
+b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z
+ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4
+NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN
+TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p
+Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u
+uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+
+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA
+vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770
+Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx
+62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB
+AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw
+LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP
+BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB
+AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov
+MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5
+ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT
+AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh
+ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo
+AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa
+AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln
+bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p
+Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP
+PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv
+Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB
+EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu
+w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj
+cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV
+HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI
+VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS
+BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS
+b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS
+8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds
+ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl
+7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR
+hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/
+MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
+VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
+ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
+CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
+OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
+FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
+Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
+kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
+cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
+fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
+N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
+xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
+Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
+SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
+mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
+ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
+2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
+HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
+EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
+MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
+cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
+dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
+pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
+b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
+IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
+lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
+AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
+VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
+ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
+BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
+AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
+U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
+bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
+uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
+XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV
+MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe
+TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0
+dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB
+KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0
+N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC
+dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu
+MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL
+b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD
+zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi
+3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8
+WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY
+Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi
+NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC
+ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4
+QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0
+YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz
+aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
+IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm
+ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg
+ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs
+amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv
+IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3
+Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6
+ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1
+YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg
+dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs
+b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G
+CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO
+xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP
+0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ
+QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk
+f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK
+8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
+MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
+dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
+UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
+ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
+c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
+OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
+mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
+BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
+qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
+gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
+bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
+dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
+6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
+h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
+/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
+pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
+ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
+aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
+ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
+NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
+A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
+VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
+SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
+VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
+w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
+mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
+4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
+4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
+EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
+SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
+ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
+vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
+Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
+/L7fCg0=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1
+dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s
+YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz
+dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0
+aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh
+IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ
+KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw
+MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy
+b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx
+KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG
+A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u
+aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9
+7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74
+BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G
+ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9
+JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0
+PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2
+0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
+0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/
+6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m
+v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7
+K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev
+bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw
+MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w
+MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD
+gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0
+b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh
+bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0
+cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp
+ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg
+ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq
+hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD
+AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w
+MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag
+RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t
+UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl
+cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
+Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG
+AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN
+AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS
+1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB
+3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv
+Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh
+HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm
+pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz
+sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE
+qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb
+mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9
+opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H
+YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
+TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
+MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
+IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
+dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
+li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
+rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
+WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
+F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
+xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
+Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
+dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
+ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
+IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
+c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
+ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
+KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
+KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
+y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
+dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
+VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
+MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
+fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
+7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
+cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
+mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
+xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
+SnQ2+Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
+MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
+wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
+rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
+68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
+4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
+UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
+abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
+3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
+KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
+hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
+Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
+zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
+ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
+MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
+cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
+qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
+YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
+b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
+8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
+NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
+ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
+q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
+nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
+GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
+Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
+WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
+rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
+ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
+Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
+PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
+/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
+oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
+yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
+EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
+A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
+MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
+BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
+g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
+fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
+WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
+B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
+hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
+TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
+mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
+ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
+4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
+8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
+MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
+qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
+n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
+c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
+o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
+IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
+IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
+8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
+vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
+7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
+cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
+ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
+AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
+roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
+W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
+lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
+csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
+dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
+KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
+HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
+WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
+V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
+4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
+H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
+8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
+vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
+mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
+btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
+T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
+WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
+c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
+4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
+VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
+CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
+aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
+dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
+czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
+A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
+TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
+Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
+7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
+d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
+4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
+t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
+DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
+k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
+zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
+Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
+mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
+4SVhM7JZG+Ju1zdXtg2pEto=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
+MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
+/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
+FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
+U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
+ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
+FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
+A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
+eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
+sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
+VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
+A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
+ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
+KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
+FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
+oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
+u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
+0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
+3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
+8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
+DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
+PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
+ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6
+MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
+dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX
+BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy
+MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp
+eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg
+/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl
+wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh
+AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2
+PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu
+AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR
+MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc
+HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/
+Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+
+f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO
+rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch
+6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3
+7CAFYd4=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF
+UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ
+R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN
+MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G
+A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw
+JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+
+WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj
+SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl
+u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy
+A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk
+Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7
+MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr
+aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC
+IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A
+cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA
+YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA
+bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA
+bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
+aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA
+aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA
+ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA
+YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA
+ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA
+LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6
+Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y
+eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw
+CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G
+A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu
+Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn
+lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt
+b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg
+9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF
+ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC
+IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
+MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
+A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
+MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
+Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
+QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
+i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
+h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
+MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
+UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
+8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
+h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
+AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
+KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
+X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
+QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
+pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
+QSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
+MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
+cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
+Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
+0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
+wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
+7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
+8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
+BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
+JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
+NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
+6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
+3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
+D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
+CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
+MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
+Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
+iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
+/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
+jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
+HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
+sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
+gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
+KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
+AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
+URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
+H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
+I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
+iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
+MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
+IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
+bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
+RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
+zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
+bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
+MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
+VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
+OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
+tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
+q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
+EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
+VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
+DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
+dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
+YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
+OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
+zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
+VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
+hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
+ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
+awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
+OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
+coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
+okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
+t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
+1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
+SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
+MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
+dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
+WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
+VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
+9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
+DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
+Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
+QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
+xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
+A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
+kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
+Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
+Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
+JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
+RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
+MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
+MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
+BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
+Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
+5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
+3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
+vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
+8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
+DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
+MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
+zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
+3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
+FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
+Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
+ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO
+TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy
+MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk
+ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn
+ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71
+9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO
+hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U
+tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o
+BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh
+SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww
+OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv
+cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA
+7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k
+/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm
+eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6
+u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy
+7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
+iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
+TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
+DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
+ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
+b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
+qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
+uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
+Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
+pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
+5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
+UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
+GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
+5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
+6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
+eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
+B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
+BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
+L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
+SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
+CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
+5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
+IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
+gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
+vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
+bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
+N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
+Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
+ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
+ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
+MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
+aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
+Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
+nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
+HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
+Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
+dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
+HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
+CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
+sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
+4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
+8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
+mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
+ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
+VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
+ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
+dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
+OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
+8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
+Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
+hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
+6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
+DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
+AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
+bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
+ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
+qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
+0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
+sSi6
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
+MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
+U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
+cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
+pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
+OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
+Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
+Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
+HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
+Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
+Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
+26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
+AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
+ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
+LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
+BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
+dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
+cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
+YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
+dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
+bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
+YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
+TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
+9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
+jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
+FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
+ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
+ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
+EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
+L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
+O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
+um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
+NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9
+MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
+U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
+cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
+pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
+OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
+Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
+Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
+HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
+Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
+Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
+26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
+AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC
+ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w
+ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk
+aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0
+YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg
+c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93
+d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG
+CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF
+wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS
+Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst
+0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc
+pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl
+CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF
+P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK
+1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm
+KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ
+8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm
+fyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1
+OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG
+A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ
+JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD
+vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo
+D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/
+Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW
+RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK
+HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN
+nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM
+0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i
+UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9
+Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg
+TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL
+BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX
+UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl
+6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK
+9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ
+HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI
+wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY
+XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l
+IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo
+hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr
+so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
+biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
+MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
+d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
+76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
+bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
+6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
+emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
+MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
+MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
+MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
+FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
+aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
+gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
+qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
+lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
+8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
+45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
+UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
+O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
+bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
+GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
+77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
+hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
+92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
+Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
+ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
+Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
+BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
+IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
+RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
+U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
+Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
+YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
+nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
+6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
+eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
+c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
+MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
+HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
+jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
+5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
+rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
+wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
+AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
+WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
+xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
+2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
+IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
+aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
+em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
+dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
+OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
+tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk
+MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
+Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT
+AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
+Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9
+m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih
+FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/
+TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F
+EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco
+kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu
+HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF
+vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo
+19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC
+L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW
+bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX
+JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
+FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc
+K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf
+ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik
+Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB
+sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e
+3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR
+ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip
+mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH
+b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf
+rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms
+hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y
+zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6
+MBr1mmz0DlP5OlvRHA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk
+MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
+Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT
+AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
+Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr
+jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r
+0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f
+2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP
+ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF
+y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA
+tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL
+6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0
+uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL
+acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh
+k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q
+VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
+FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
+BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh
+b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R
+fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv
+/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI
+REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx
+srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv
+aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT
+woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n
+Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W
+t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N
+8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2
+9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5
+wSsSnqaeG8XmDtkx2Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw
+ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp
+dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290
+IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD
+VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy
+dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg
+MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx
+UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD
+1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH
+oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR
+HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/
+5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv
+idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL
+OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC
+NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f
+46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB
+UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth
+7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G
+A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
+MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB
+bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x
+XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T
+PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0
+Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70
+WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL
+Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm
+7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S
+nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN
+vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB
+WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI
+fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb
+I+2ksx0WckNLIOFZfsLorSa/ovc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
+AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
+FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
+1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
+jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
+wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
+WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
+NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
+uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
+IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
+g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
+BSeOE6Fuwg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
+8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
+RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
+hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
+ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
+EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
+A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
+WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
+1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
+6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
+91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
+TpPDpFQUWw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf
+tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg
+uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J
+XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK
+8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99
+5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3
+kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS
+GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt
+ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8
+au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV
+hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI
+dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW
+Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q
+Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2
+1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq
+ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1
+Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX
+XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN
+irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8
+TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6
+g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB
+95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj
+S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
+BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1
+c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx
+MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg
+R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD
+VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR
+JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T
+fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu
+jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z
+wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ
+fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD
+VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G
+CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1
+7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn
+8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs
+ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/
+2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg
+MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
+dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz
+MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy
+dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD
+VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg
+xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu
+xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7
+XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k
+heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J
+YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C
+urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1
+JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51
+b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV
+9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7
+kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh
+fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
+B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA
+aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS
+RGQDJereW26fyfJOrN3H
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS
+S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
+SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx
+OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry
+b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC
+VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE
+sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F
+ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY
+KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG
++7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG
+HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P
+IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M
+733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk
+Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW
+AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
+aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5
+mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa
+XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ
+qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS
+S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
+SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3
+WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv
+bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU
+UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw
+bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe
+LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef
+J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh
+R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ
+Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX
+JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p
+zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S
+Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
+KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq
+ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
+Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz
+gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH
+uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS
+y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
+EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
+VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
+NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
+B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
+10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
+0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
+MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
+zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
+46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
+yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
+laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
+oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
+BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
+qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
+4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
+1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
+H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
+RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
+15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
+6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
+nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
+wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
+aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
+KwbQBM0=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
+MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
+V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
+WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
+LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
+AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
+K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
+RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
+rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
+3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
+hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
+MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
+XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
+lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
+aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
+YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
+MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
+PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
+IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
+gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
+yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
+F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
+jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
+ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
+VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
+YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
+EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
+Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
+DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
+MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
+UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
+qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
+ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
+JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
+hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
+EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
+nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
+udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
+ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
+LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
+pYYsfPQS
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
+NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
+b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
+VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
+VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
+7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
+Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
+/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
+81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
+dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
+Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
+sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
+pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
+slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
+arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
+VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
+9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
+dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
+0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
+TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
+Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
+Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
+OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
+vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
+t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
+HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
+SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF
+MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL
+ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx
+MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc
+MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+
+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH
+iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj
+vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA
+0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB
+OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/
+BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E
+FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01
+GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW
+zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4
+1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE
+f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F
+jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN
+ZetX2fNXlrtIzYE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
+MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
+bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
+VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
+YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
+dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
+Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
+GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
+aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
+QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
+xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
+aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
+IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
+gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
+O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
+fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
+lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
+AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
+NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
+wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
+7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
+gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
+oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
+yZyQ2uypQjyttgI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
+eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
+JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
+VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
+I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
+o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
+A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
+zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
+RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
+MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
+3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
+tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
+Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
+VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
+79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
+c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
+Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
+c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
+UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
+Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
+Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
+VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
+ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
+8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
+iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
+Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
+XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
+qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
+VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
+L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
+jjxDah2nGN59PRbxYvnKkKj9
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
+kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
+IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
+VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
+dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
+E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
+D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
+4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
+lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
+bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
+o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
+MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
+LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
+BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
+AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
+j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
+KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
+2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
+mfnGV/TJVTl4uix5yaaIK/QI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
+MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
+d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
+cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
+0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
+M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
+MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
+oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
+DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
+oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
+dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
+bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
+BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
+CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
+CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
+3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
+KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
+U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
+SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
+biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
+GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
+fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
+aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
+aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
+kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
+4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
+FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
+nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
+t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
+SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
+BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
+rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
+NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
+BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
+MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
+p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
+5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
+WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
+4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
+hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
+vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
+ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
+IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
+IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
+bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
+9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
+H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
+LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
+/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
+rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
+WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
+exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
+sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
+seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
+4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
+lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
+7M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
+N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
+KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
+kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
+CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
+Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
+imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
+2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
+DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
+F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
+TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
+GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
+U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
+NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
+ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
+ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
+CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
+g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
+2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
+bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
+MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
+cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
+bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
+CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
+dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
+cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
+2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
+lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
+ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
+299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
+vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
+dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
+AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
+AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
+zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
+LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
+7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
+++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
+398znM/jra6O1I7mT1GvFpLgXPYHDw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx
+IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs
+cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v
+dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0
+MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl
+bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD
+DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r
+WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU
+Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs
+HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj
+z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf
+SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl
+AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG
+KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P
+AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j
+BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC
+VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX
+ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB
+ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd
+/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB
+A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn
+k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9
+iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv
+2G0xffX8oRAHh84vWdw+WNs=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV
+MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV
+BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw
+MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX
+b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN
+rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U
+fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc
+f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2
+ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M
+x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR
+aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch
+zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar
+uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K
+mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA
+Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv
+HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H
+EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1
+LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ
+MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e
+JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN
+g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp
+dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab
+R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ
+PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce
+xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+
+J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl
+OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT
+ee5Ehr7XHuQe+w==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG
+MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV
+BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw
+MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl
+ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r
+D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1
+9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf
+v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk
+UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L
+NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb
++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V
+qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K
+yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G
+AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK
+J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC
+AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
+BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4
+WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6
+yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj
+/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6
+jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2
+ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX
+X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n
+FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D
+u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l
+O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le
+ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1
+2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
+gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
+MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
+UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
+NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
+dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
+dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
+38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
+KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
+DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
+qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
+JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
+PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
+BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
+jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
+eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
+vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
+IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
+i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
+O+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
+AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
+QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
+MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
+0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
+UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
+RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
+OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
+JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
+AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
+BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
+LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
+MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
+44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
+Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
+i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
+9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
+IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
+SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
+SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
+ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
+DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
+TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
+fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
+sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
+WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
+nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
+dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
+NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
+AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
+MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
+uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
+PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
+JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
+gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
+j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
+5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
+o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
+/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
+Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
+W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
+hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
+NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
+LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
+A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
+W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
+3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
+6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
+Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
+NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
+r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
+DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
+YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
+/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
+LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
+jVaMaA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
+IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
+BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
+MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
+YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
+dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
+BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
+papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
+DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
+KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
+XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
+rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
+BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
+Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
+LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
+MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
+ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
+gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
+YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
+b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
+9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
+zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
+OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
+HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
+2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
+oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
+KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
+m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
+MdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
diff --git a/src/seastar/tests/unit/tls_test.cc b/src/seastar/tests/unit/tls_test.cc
new file mode 100644
index 000000000..838c4062a
--- /dev/null
+++ b/src/seastar/tests/unit/tls_test.cc
@@ -0,0 +1,845 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <iostream>
+
+#include <seastar/core/do_with.hh>
+#include <seastar/core/sstring.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/loop.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/core/temporary_buffer.hh>
+#include <seastar/core/iostream.hh>
+#include <seastar/util/std-compat.hh>
+#include <seastar/net/tls.hh>
+#include <seastar/net/dns.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/testing/test_case.hh>
+#include <seastar/testing/thread_test_case.hh>
+
+#include <boost/dll.hpp>
+
+#include "loopback_socket.hh"
+#include "tmpdir.hh"
+
+#include <gnutls/gnutls.h>
+
+#if 0
+
+static void enable_gnutls_logging() {
+ gnutls_global_set_log_level(99);
+ gnutls_global_set_log_function([](int lv, const char * msg) {
+ std::cerr << "GNUTLS (" << lv << ") " << msg << std::endl;
+ });
+}
+#endif
+
+static const auto cert_location = boost::dll::program_location().parent_path();
+
+static std::string certfile(const std::string& file) {
+ return (cert_location / file).string();
+}
+
+using namespace seastar;
+
+static future<> connect_to_ssl_addr(::shared_ptr<tls::certificate_credentials> certs, socket_address addr) {
+ return tls::connect(certs, addr, "www.google.com").then([](connected_socket s) {
+ return do_with(std::move(s), [](connected_socket& s) {
+ return do_with(s.output(), [&s](auto& os) {
+ static const sstring msg("GET / HTTP/1.0\r\n\r\n");
+ auto f = os.write(msg);
+ return f.then([&s, &os]() mutable {
+ auto f = os.flush();
+ return f.then([&s]() mutable {
+ return do_with(s.input(), [](auto& in) {
+ auto f = in.read();
+ return f.then([](temporary_buffer<char> buf) {
+ // std::cout << buf.get() << std::endl;
+
+ // Avoid passing a nullptr as an argument of strncmp().
+ // If the temporary_buffer is empty (e.g. due to the underlying TCP connection
+ // being reset) passing the buf.get() (which would be a nullptr) to strncmp()
+ // causes a runtime error which masks the actual issue.
+ if (buf) {
+ BOOST_CHECK(strncmp(buf.get(), "HTTP/", 5) == 0);
+ }
+ BOOST_CHECK(buf.size() > 8);
+ });
+ });
+ });
+ }).finally([&os] {
+ return os.close();
+ });
+ });
+ });
+ });
+}
+
+static future<> connect_to_ssl_google(::shared_ptr<tls::certificate_credentials> certs) {
+ static socket_address google;
+
+ if (google.is_unspecified()) {
+ return net::dns::resolve_name("www.google.com", net::inet_address::family::INET).then([certs](net::inet_address addr) {
+ google = socket_address(addr, 443);
+ return connect_to_ssl_google(certs);
+ });
+ }
+ return connect_to_ssl_addr(std::move(certs), google);
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client) {
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ return certs->set_x509_trust_file(certfile("tls-ca-bundle.pem"), tls::x509_crt_format::PEM).then([certs]() {
+ return connect_to_ssl_google(certs);
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_system_trust) {
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ return certs->set_system_trust().then([certs]() {
+ return connect_to_ssl_google(certs);
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ return connect_to_ssl_google(b.build_certificate_credentials());
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust_multiple) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ auto creds = b.build_certificate_credentials();
+
+ return parallel_for_each(boost::irange(0, 20), [creds](auto i) { return connect_to_ssl_google(creds); });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_priority_strings) {
+ static std::vector<sstring> prios( { "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
+ "NORMAL:+ARCFOUR-128", // means normal ciphers plus ARCFOUR-128.
+ "SECURE128:-VERS-SSL3.0:+COMP-DEFLATE", // means that only secure ciphers are enabled, SSL3.0 is disabled, and libz compression enabled.
+ "NONE:+VERS-TLS-ALL:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1",
+ "SECURE256:+SECURE128",
+ "NORMAL:%COMPAT",
+ "NORMAL:-MD5",
+ "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
+ "NORMAL:+ARCFOUR-128",
+ "SECURE128:-VERS-TLS1.0:+COMP-DEFLATE",
+ "SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2"
+ });
+ return do_for_each(prios, [](const sstring & prio) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ b.set_priority_string(prio);
+ return connect_to_ssl_google(b.build_certificate_credentials());
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_priority_strings_fail) {
+ static std::vector<sstring> prios( { "NONE",
+ "NONE:+CURVE-SECP256R1"
+ });
+ return do_for_each(prios, [](const sstring & prio) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ b.set_priority_string(prio);
+ try {
+ return connect_to_ssl_google(b.build_certificate_credentials()).then([] {
+ BOOST_FAIL("Expected exception");
+ }).handle_exception([](auto ep) {
+ // ok.
+ });
+ } catch (...) {
+ // also ok
+ }
+ return make_ready_future<>();
+ });
+}
+
+SEASTAR_TEST_CASE(test_failed_connect) {
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+ return connect_to_ssl_addr(b.build_certificate_credentials(), ipv4_addr()).handle_exception([](auto) {});
+}
+
+SEASTAR_TEST_CASE(test_non_tls) {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = server_socket(seastar::listen(addr, opts));
+
+ auto c = server.accept();
+
+ tls::credentials_builder b;
+ (void)b.set_system_trust();
+
+ auto f = connect_to_ssl_addr(b.build_certificate_credentials(), addr);
+
+
+ return c.then([f = std::move(f)](accept_result ar) mutable {
+ ::connected_socket s = std::move(ar.connection);
+ std::cerr << "Established connection" << std::endl;
+ auto sp = std::make_unique<::connected_socket>(std::move(s));
+ timer<> t([s = std::ref(*sp)] {
+ std::cerr << "Killing server side" << std::endl;
+ s.get() = ::connected_socket();
+ });
+ t.arm(timer<>::clock::now() + std::chrono::seconds(5));
+ return std::move(f).finally([t = std::move(t), sp = std::move(sp)] {});
+ }).handle_exception([server = std::move(server)](auto ep) {
+ std::cerr << "Got expected exception" << std::endl;
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_before_handshake) {
+ auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
+ return certs->set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).then([certs] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = server_socket(tls::listen(certs, addr, opts));
+ auto c = server.accept();
+ BOOST_CHECK(!c.available()); // should not be finished
+
+ server.abort_accept();
+
+ return c.then([](auto) { BOOST_FAIL("Should not reach"); }).handle_exception([](auto) {
+ // ok
+ }).finally([server = std::move(server)] {});
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_after_handshake) {
+ return async([] {
+ auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
+ certs->set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
+
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = tls::listen(certs, addr, opts);
+ auto sa = server.accept();
+
+ tls::credentials_builder b;
+ b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+
+ auto c = tls::connect(b.build_certificate_credentials(), addr).get0();
+ server.abort_accept(); // should not affect the socket we got.
+
+ auto s = sa.get0();
+ auto out = c.output();
+ auto in = s.connection.input();
+
+ out.write("apa").get();
+ auto f = out.flush();
+ auto buf = in.read().get0();
+ f.get();
+ BOOST_CHECK(sstring(buf.begin(), buf.end()) == "apa");
+
+ out.close().get();
+ in.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_on_server_before_handshake) {
+ return async([] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = server_socket(seastar::listen(addr, opts));
+ auto sa = server.accept();
+
+ tls::credentials_builder b;
+ b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+
+ auto creds = b.build_certificate_credentials();
+ auto f = tls::connect(creds, addr);
+
+ server.abort_accept();
+ try {
+ sa.get();
+ } catch (...) {
+ }
+ server = {};
+
+ try {
+ // the connect as such should succeed, but the handshare following it
+ // should not.
+ auto c = f.get0();
+ auto out = c.output();
+ out.write("apa").get();
+ out.flush().get();
+ out.close().get();
+
+ BOOST_FAIL("Expected exception");
+ } catch (...) {
+ // ok
+ }
+ });
+}
+
+
+struct streams {
+ ::connected_socket s;
+ input_stream<char> in;
+ output_stream<char> out;
+
+ // note: using custom output_stream, because we don't want polled flush
+ streams(::connected_socket cs) : s(std::move(cs)), in(s.input()), out(s.output().detach(), 8192)
+ {}
+};
+
+static const sstring message = "hej lilla fisk du kan dansa fint";
+
+class echoserver {
+ ::server_socket _socket;
+ ::shared_ptr<tls::server_credentials> _certs;
+ seastar::gate _gate;
+ bool _stopped = false;
+ size_t _size;
+ std::exception_ptr _ex;
+public:
+ echoserver(size_t message_size, bool use_dh_params = true)
+ : _certs(
+ use_dh_params
+ ? ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>())
+ : ::make_shared<tls::server_credentials>()
+ )
+ , _size(message_size)
+ {}
+
+ future<> listen(socket_address addr, sstring crtfile, sstring keyfile, tls::client_auth ca = tls::client_auth::NONE, sstring trust = {}) {
+ _certs->set_client_auth(ca);
+ auto f = _certs->set_x509_key_file(crtfile, keyfile, tls::x509_crt_format::PEM);
+ if (!trust.empty()) {
+ f = f.then([this, trust = std::move(trust)] {
+ return _certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
+ });
+ }
+ return f.then([this, addr] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+
+ _socket = tls::listen(_certs, addr, opts);
+
+ (void)try_with_gate(_gate, [this] {
+ return _socket.accept().then([this](accept_result ar) {
+ ::connected_socket s = std::move(ar.connection);
+ auto strms = ::make_lw_shared<streams>(std::move(s));
+ return repeat([strms, this]() {
+ return strms->in.read_exactly(_size).then([strms](temporary_buffer<char> buf) {
+ if (buf.empty()) {
+ return make_ready_future<stop_iteration>(stop_iteration::yes);
+ }
+ sstring tmp(buf.begin(), buf.end());
+ return strms->out.write(tmp).then([strms]() {
+ return strms->out.flush();
+ }).then([] {
+ return make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ });
+ }).finally([strms]{
+ return strms->out.close();
+ }).finally([strms]{});
+ }).handle_exception([this](auto ep) {
+ if (_stopped) {
+ return make_ready_future<>();
+ }
+ _ex = ep;
+ return make_ready_future<>();
+ });
+ }).handle_exception_type([] (const gate_closed_exception&) {/* ignore */});
+ return make_ready_future<>();
+ });
+ }
+
+ future<> stop() {
+ _stopped = true;
+ _socket.abort_accept();
+ return _gate.close().handle_exception([this] (std::exception_ptr ignored) {
+ if (_ex) {
+ std::rethrow_exception(_ex);
+ }
+ });
+ }
+};
+
+static future<> run_echo_test(sstring message,
+ int loops,
+ sstring trust,
+ sstring name,
+ sstring crt = certfile("test.crt"),
+ sstring key = certfile("test.key"),
+ tls::client_auth ca = tls::client_auth::NONE,
+ sstring client_crt = {},
+ sstring client_key = {},
+ bool do_read = true,
+ bool use_dh_params = true,
+ tls::dn_callback distinguished_name_callback = {}
+)
+{
+ static const auto port = 4711;
+
+ auto msg = ::make_shared<sstring>(std::move(message));
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ auto server = ::make_shared<seastar::sharded<echoserver>>();
+ auto addr = ::make_ipv4_address( {0x7f000001, port});
+
+ assert(do_read || loops == 1);
+
+ future<> f = make_ready_future();
+
+ if (!client_crt.empty() && !client_key.empty()) {
+ f = certs->set_x509_key_file(client_crt, client_key, tls::x509_crt_format::PEM);
+ if (distinguished_name_callback) {
+ certs->set_dn_verification_callback(std::move(distinguished_name_callback));
+ }
+ }
+
+ return f.then([=] {
+ return certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
+ }).then([=] {
+ return server->start(msg->size(), use_dh_params).then([=]() {
+ sstring server_trust;
+ if (ca != tls::client_auth::NONE) {
+ server_trust = trust;
+ }
+ return server->invoke_on_all(&echoserver::listen, addr, crt, key, ca, server_trust);
+ }).then([=] {
+ return tls::connect(certs, addr, name).then([loops, msg, do_read](::connected_socket s) {
+ auto strms = ::make_lw_shared<streams>(std::move(s));
+ auto range = boost::irange(0, loops);
+ return do_for_each(range, [strms, msg](auto) {
+ auto f = strms->out.write(*msg);
+ return f.then([strms, msg]() {
+ return strms->out.flush().then([strms, msg] {
+ return strms->in.read_exactly(msg->size()).then([msg](temporary_buffer<char> buf) {
+ if (buf.empty()) {
+ throw std::runtime_error("Unexpected EOF");
+ }
+ sstring tmp(buf.begin(), buf.end());
+ BOOST_CHECK(*msg == tmp);
+ });
+ });
+ });
+ }).then_wrapped([strms, do_read] (future<> f1) {
+ // Always call close()
+ return (do_read ? strms->out.close() : make_ready_future<>()).then_wrapped([strms, f1 = std::move(f1)] (future<> f2) mutable {
+ // Verification errors will be reported by the call to output_stream::close(),
+ // which waits for the flush to actually happen. They can also be reported by the
+ // input_stream::read_exactly() call. We want to keep only one and avoid nested exception mess.
+ if (f1.failed()) {
+ (void)f2.handle_exception([] (std::exception_ptr ignored) { });
+ return std::move(f1);
+ }
+ (void)f1.handle_exception([] (std::exception_ptr ignored) { });
+ return f2;
+ }).finally([strms] { });
+ });
+ });
+ }).finally([server] {
+ return server->stop().finally([server]{});
+ });
+ });
+}
+
+/*
+ * Certificates:
+ *
+ * make -f tests/unit/mkcert.gmk domain=scylladb.org server=test
+ *
+ * -> test.crt
+ * test.csr
+ * catest.pem
+ * catest.key
+ *
+ * catest == snakeoil root authority for these self-signed certs
+ *
+ */
+SEASTAR_TEST_CASE(test_simple_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org");
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_again) {
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org");
+}
+
+#if GNUTLS_VERSION_NUMBER >= 0x030600
+// Test #769 - do not set dh_params in server certs - let gnutls negotiate.
+SEASTAR_TEST_CASE(test_simple_server_default_dhparams) {
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org",
+ certfile("test.crt"), certfile("test.key"), tls::client_auth::NONE,
+ {}, {}, true, /* use_dh_params */ false
+ );
+}
+#endif
+
+SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail) {
+ // Load a real trust authority here, which out certs are _not_ signed with.
+ return run_echo_test(message, 1, certfile("tls-ca-bundle.pem"), {}).then([] {
+ BOOST_FAIL("Should have gotten validation error");
+ }).handle_exception([](auto ep) {
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error&) {
+ // ok.
+ } catch (...) {
+ BOOST_FAIL("Unexpected exception");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail_name) {
+ // Use trust store with our signer, but wrong host name
+ return run_echo_test(message, 1, certfile("tls-ca-bundle.pem"), "nils.holgersson.gov").then([] {
+ BOOST_FAIL("Should have gotten validation error");
+ }).handle_exception([](auto ep) {
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error&) {
+ // ok.
+ } catch (...) {
+ BOOST_FAIL("Unexpected exception");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_large_message_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ sstring msg = uninitialized_string(512 * 1024);
+ for (size_t i = 0; i < msg.size(); ++i) {
+ msg[i] = '0' + char(i % 30);
+ }
+ return run_echo_test(std::move(msg), 20, certfile("catest.pem"), "test.scylladb.org");
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_fail_client_auth) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ // Server will require certificate auth. We supply none, so should fail connection
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE).then([] {
+ BOOST_FAIL("Expected exception");
+ }).handle_exception([](auto ep) {
+ // ok.
+ });
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ // Server will require certificate auth. We supply one, so should succeed with connection
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"));
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth_with_dn_callback) {
+ // In addition to the above test, the certificate's subject and issuer
+ // Distinguished Names (DNs) will be checked for the occurrence of a specific
+ // substring (in this case, the test.scylladb.org url)
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"), true, true, [](tls::session_type t, sstring subject, sstring issuer) {
+ BOOST_REQUIRE(t == tls::session_type::CLIENT);
+ BOOST_REQUIRE(subject.find("test.scylladb.org") != sstring::npos);
+ BOOST_REQUIRE(issuer.find("test.scylladb.org") != sstring::npos);
+ });
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth_dn_callback_fails) {
+ // Test throwing an exception from within the Distinguished Names callback
+ return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"), true, true, [](tls::session_type, sstring, sstring) {
+ throw tls::verification_error("to test throwing from within the callback");
+ }).then([] {
+ BOOST_FAIL("Should have gotten a verification_error exception");
+ }).handle_exception([](auto) {
+ // ok.
+ });
+}
+
+SEASTAR_TEST_CASE(test_many_large_message_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ sstring msg = uninitialized_string(4 * 1024 * 1024);
+ for (size_t i = 0; i < msg.size(); ++i) {
+ msg[i] = '0' + char(i % 30);
+ }
+ // Sending a huge-ish message a and immediately closing the session (see params)
+ // provokes case where tls::vec_push entered race and asserted on broken IO state
+ // machine.
+ auto range = boost::irange(0, 20);
+ return do_for_each(range, [msg = std::move(msg)](auto) {
+ return run_echo_test(std::move(msg), 1, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::NONE, {}, {}, false);
+ });
+}
+
+SEASTAR_THREAD_TEST_CASE(test_close_timout) {
+ tls::credentials_builder b;
+
+ b.set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
+ b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+ b.set_dh_level();
+ b.set_system_trust().get();
+
+ auto creds = b.build_certificate_credentials();
+ auto serv = b.build_server_credentials();
+
+ semaphore sem(0);
+
+ class my_loopback_connected_socket_impl : public loopback_connected_socket_impl {
+ public:
+ semaphore& _sem;
+ bool _close = false;
+
+ my_loopback_connected_socket_impl(semaphore& s, lw_shared_ptr<loopback_buffer> tx, lw_shared_ptr<loopback_buffer> rx)
+ : loopback_connected_socket_impl(tx, rx)
+ , _sem(s)
+ {}
+ ~my_loopback_connected_socket_impl() {
+ _sem.signal();
+ }
+ class my_sink_impl : public data_sink_impl {
+ public:
+ data_sink _sink;
+ my_loopback_connected_socket_impl& _impl;
+ promise<> _p;
+ my_sink_impl(data_sink sink, my_loopback_connected_socket_impl& impl)
+ : _sink(std::move(sink))
+ , _impl(impl)
+ {}
+ future<> flush() override {
+ return _sink.flush();
+ }
+ using data_sink_impl::put;
+ future<> put(net::packet p) override {
+ if (std::exchange(_impl._close, false)) {
+ return _p.get_future().then([this, p = std::move(p)]() mutable {
+ return put(std::move(p));
+ });
+ }
+ return _sink.put(std::move(p));
+ }
+ future<> close() override {
+ _p.set_value();
+ return make_ready_future<>();
+ }
+ };
+ data_sink sink() override {
+ return data_sink(std::make_unique<my_sink_impl>(loopback_connected_socket_impl::sink(), *this));
+ }
+ };
+
+ auto constexpr iterations = 500;
+
+ for (int i = 0; i < iterations; ++i) {
+ auto b1 = ::make_lw_shared<loopback_buffer>(nullptr, loopback_buffer::type::SERVER_TX);
+ auto b2 = ::make_lw_shared<loopback_buffer>(nullptr, loopback_buffer::type::CLIENT_TX);
+ auto ssi = std::make_unique<my_loopback_connected_socket_impl>(sem, b1, b2);
+ auto csi = std::make_unique<my_loopback_connected_socket_impl>(sem, b2, b1);
+
+ auto& ssir = *ssi;
+ auto& csir = *csi;
+
+ auto ss = tls::wrap_server(serv, connected_socket(std::move(ssi))).get0();
+ auto cs = tls::wrap_client(creds, connected_socket(std::move(csi))).get0();
+
+ auto os = cs.output().detach();
+ auto is = ss.input();
+
+ auto f1 = os.put(temporary_buffer<char>(10));
+ auto f2 = is.read();
+ f1.get();
+ f2.get();
+
+ // block further writes
+ ssir._close = true;
+ csir._close = true;
+ }
+
+ sem.wait(2 * iterations).get();
+}
+
+SEASTAR_THREAD_TEST_CASE(test_reload_certificates) {
+ tmpdir tmp;
+
+ namespace fs = std::filesystem;
+
+ // copy the wrong certs. We don't trust these
+ // blocking calls, but this is a test and seastar does not have a copy
+ // util and I am lazy...
+ fs::copy_file(certfile("other.crt"), tmp.path() / "test.crt");
+ fs::copy_file(certfile("other.key"), tmp.path() / "test.key");
+
+ auto cert = (tmp.path() / "test.crt").native();
+ auto key = (tmp.path() / "test.key").native();
+ std::unordered_set<sstring> changed;
+ promise<> p;
+
+ tls::credentials_builder b;
+ b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
+ b.set_dh_level();
+
+ auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) {
+ if (ep) {
+ return;
+ }
+ changed.insert(files.begin(), files.end());
+ if (changed.count(cert) && changed.count(key)) {
+ p.set_value();
+ }
+ }).get0();
+
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = tls::listen(certs, addr, opts);
+
+ tls::credentials_builder b2;
+ b2.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
+
+ {
+ auto sa = server.accept();
+ auto c = tls::connect(b2.build_certificate_credentials(), addr).get0();
+ auto s = sa.get0();
+ auto in = s.connection.input();
+
+ output_stream<char> out(c.output().detach(), 4096);
+
+ try {
+ out.write("apa").get();
+ auto f = out.flush();
+ auto f2 = in.read();
+
+ try {
+ f.get();
+ BOOST_FAIL("should not reach");
+ } catch (tls::verification_error&) {
+ // ok
+ }
+ try {
+ out.close().get();
+ } catch (...) {
+ }
+
+ try {
+ f2.get();
+ BOOST_FAIL("should not reach");
+ } catch (...) {
+ // ok
+ }
+ try {
+ in.close().get();
+ } catch (...) {
+ }
+ } catch (tls::verification_error&) {
+ // ok
+ }
+ }
+
+ // copy the right (trusted) certs over the old ones.
+ fs::copy_file(certfile("test.crt"), tmp.path() / "test0.crt");
+ fs::copy_file(certfile("test.key"), tmp.path() / "test0.key");
+
+ rename_file((tmp.path() / "test0.crt").native(), (tmp.path() / "test.crt").native()).get();
+ rename_file((tmp.path() / "test0.key").native(), (tmp.path() / "test.key").native()).get();
+
+ p.get_future().get();
+
+ // now it should work
+ {
+ auto sa = server.accept();
+ auto c = tls::connect(b2.build_certificate_credentials(), addr).get0();
+ auto s = sa.get0();
+ auto in = s.connection.input();
+
+ output_stream<char> out(c.output().detach(), 4096);
+
+ out.write("apa").get();
+ auto f = out.flush();
+ auto buf = in.read().get0();
+ f.get();
+ out.close().get();
+ in.read().get(); // ignore - just want eof
+ in.close().get();
+
+ BOOST_CHECK_EQUAL(sstring(buf.begin(), buf.end()), "apa");
+ }
+}
+
+SEASTAR_THREAD_TEST_CASE(test_reload_broken_certificates) {
+ tmpdir tmp;
+
+ namespace fs = std::filesystem;
+
+ fs::copy_file(certfile("test.crt"), tmp.path() / "test.crt");
+ fs::copy_file(certfile("test.key"), tmp.path() / "test.key");
+
+ auto cert = (tmp.path() / "test.crt").native();
+ auto key = (tmp.path() / "test.key").native();
+ std::unordered_set<sstring> changed;
+ promise<> p;
+
+ tls::credentials_builder b;
+ b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
+ b.set_dh_level();
+
+ queue<std::exception_ptr> q(10);
+
+ auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) {
+ if (ep) {
+ q.push(std::move(ep));
+ return;
+ }
+ changed.insert(files.begin(), files.end());
+ if (changed.count(cert) && changed.count(key)) {
+ p.set_value();
+ }
+ }).get0();
+
+ // very intentionally use blocking calls. We want all our modifications to happen
+ // before any other continuation is allowed to process.
+
+ fs::remove(cert);
+ fs::remove(key);
+
+ // should get one or two exceptions
+ q.pop_eventually().get();
+
+ fs::copy_file(certfile("test.crt"), cert);
+ fs::copy_file(certfile("test.key"), key);
+
+ // now it should reload
+ p.get_future().get();
+}
diff --git a/src/seastar/tests/unit/tmpdir.hh b/src/seastar/tests/unit/tmpdir.hh
new file mode 100644
index 000000000..0da29a7f1
--- /dev/null
+++ b/src/seastar/tests/unit/tmpdir.hh
@@ -0,0 +1,49 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2020 ScyllaDB Ltd.
+ */
+
+#include <seastar/util/tmp_file.hh>
+
+namespace seastar {
+
+/**
+ * Temp dir helper for RAII usage when doing tests
+ * in seastar threads. Will not work in "normal" mode.
+ * Just use tmp_dir::do_with for that.
+ */
+class tmpdir {
+ seastar::tmp_dir _tmp;
+public:
+ tmpdir(tmpdir&&) = default;
+ tmpdir(const tmpdir&) = delete;
+
+ tmpdir(const sstring& name = sstring(seastar::default_tmpdir()) + "/testXXXX") {
+ _tmp.create(std::filesystem::path(name)).get();
+ }
+ ~tmpdir() {
+ _tmp.remove().get();
+ }
+ auto path() const {
+ return _tmp.get_path();
+ }
+};
+
+}
diff --git a/src/seastar/tests/unit/tuple_utils_test.cc b/src/seastar/tests/unit/tuple_utils_test.cc
new file mode 100644
index 000000000..6a278f01c
--- /dev/null
+++ b/src/seastar/tests/unit/tuple_utils_test.cc
@@ -0,0 +1,99 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <seastar/util/tuple_utils.hh>
+
+#include <boost/test/included/unit_test.hpp>
+
+#include <sstream>
+#include <type_traits>
+
+using namespace seastar;
+
+BOOST_AUTO_TEST_CASE(map) {
+ const auto pairs = tuple_map(std::make_tuple(10, 5.5, true), [](auto&& e) { return std::make_tuple(e, e); });
+
+ BOOST_REQUIRE(pairs == std::make_tuple(std::make_tuple(10, 10),
+ std::make_tuple(5.5, 5.5),
+ std::make_tuple(true, true)));
+}
+
+BOOST_AUTO_TEST_CASE(for_each) {
+ std::ostringstream os;
+
+ tuple_for_each(std::make_tuple('a', 10, false, 5.4), [&os](auto&& e) {
+ os << e;
+ });
+
+ BOOST_REQUIRE_EQUAL(os.str(), "a1005.4");
+}
+
+namespace {
+
+template <typename T>
+struct transform_type final {
+ using type = T;
+};
+
+template <>
+struct transform_type<bool> final { using type = int; };
+
+template <>
+struct transform_type<double> final { using type = char; };
+
+}
+
+BOOST_AUTO_TEST_CASE(map_types) {
+ using before_tuple = std::tuple<double, bool, const char*>;
+ using after_tuple = typename tuple_map_types<transform_type, before_tuple>::type;
+
+ BOOST_REQUIRE((std::is_same<after_tuple, std::tuple<char, int, const char*>>::value));
+}
+
+namespace {
+
+//
+// Strip all `bool` fields.
+//
+
+template <typename>
+struct keep_type final {
+ static constexpr auto value = true;
+};
+
+template <>
+struct keep_type<bool> final {
+ static constexpr auto value = false;
+};
+
+}
+
+BOOST_AUTO_TEST_CASE(filter_by_type) {
+ using before_tuple = std::tuple<bool, int, bool, double, bool, char>;
+
+ const auto t = tuple_filter_by_type<keep_type>(before_tuple{true, 10, false, 5.5, true, 'a'});
+ using filtered_type = typename std::decay<decltype(t)>::type;
+
+ BOOST_REQUIRE((std::is_same<filtered_type, std::tuple<int, double, char>>::value));
+ BOOST_REQUIRE(t == std::make_tuple(10, 5.5, 'a'));
+}
diff --git a/src/seastar/tests/unit/uname_test.cc b/src/seastar/tests/unit/uname_test.cc
new file mode 100644
index 000000000..1e1b1a40c
--- /dev/null
+++ b/src/seastar/tests/unit/uname_test.cc
@@ -0,0 +1,76 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2019 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include "../../src/core/uname.hh"
+
+using namespace seastar::internal;
+
+BOOST_AUTO_TEST_CASE(test_nowait_aio_fix) {
+ auto check = [] (const char* uname) {
+ return parse_uname(uname).whitelisted({"5.1", "5.0.8", "4.19.35", "4.14.112"});
+ };
+ BOOST_REQUIRE_EQUAL(check("5.1.0"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.1"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.1-44.distro"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.1-44.7.distro"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.0"), false);
+ BOOST_REQUIRE_EQUAL(check("5.0.7"), false);
+ BOOST_REQUIRE_EQUAL(check("5.0.7-55.el19"), false);
+ BOOST_REQUIRE_EQUAL(check("5.0.8"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.9"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.8-200.fedora"), true);
+ BOOST_REQUIRE_EQUAL(check("5.0.9-200.fedora"), true);
+ BOOST_REQUIRE_EQUAL(check("5.2.0"), true);
+ BOOST_REQUIRE_EQUAL(check("5.2.9"), true);
+ BOOST_REQUIRE_EQUAL(check("5.2.9-77.el153"), true);
+ BOOST_REQUIRE_EQUAL(check("6.0.0"), true);
+ BOOST_REQUIRE_EQUAL(check("3.9.0"), false);
+ BOOST_REQUIRE_EQUAL(check("4.19"), false);
+ BOOST_REQUIRE_EQUAL(check("4.19.34"), false);
+ BOOST_REQUIRE_EQUAL(check("4.19.35"), true);
+ BOOST_REQUIRE_EQUAL(check("4.19.36"), true);
+ BOOST_REQUIRE_EQUAL(check("4.20.36"), false);
+ BOOST_REQUIRE_EQUAL(check("4.14.111"), false);
+ BOOST_REQUIRE_EQUAL(check("4.14.112"), true);
+ BOOST_REQUIRE_EQUAL(check("4.14.113"), true);
+}
+
+
+BOOST_AUTO_TEST_CASE(test_xfs_concurrency_fix) {
+ auto check = [] (const char* uname) {
+ return parse_uname(uname).whitelisted({"3.15", "3.10.0-325.el7"});
+ };
+ BOOST_REQUIRE_EQUAL(check("3.15.0"), true);
+ BOOST_REQUIRE_EQUAL(check("5.1.0"), true);
+ BOOST_REQUIRE_EQUAL(check("3.14.0"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.14"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325.ubuntu"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325.el7"), true);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-326.el7"), true);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-324.el7"), false);
+ BOOST_REQUIRE_EQUAL(check("3.10.0-325.665.el7"), true);
+}
diff --git a/src/seastar/tests/unit/unix_domain_test.cc b/src/seastar/tests/unit/unix_domain_test.cc
new file mode 100644
index 000000000..fb71e3f3f
--- /dev/null
+++ b/src/seastar/tests/unit/unix_domain_test.cc
@@ -0,0 +1,234 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/core/seastar.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/core/print.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+#include <seastar/util/std-compat.hh>
+
+using namespace seastar;
+using std::string;
+using namespace std::string_literals;
+using namespace std::chrono_literals;
+
+static logger iplog("unix_domain");
+
+class ud_server_client {
+public:
+ ud_server_client(string server_path, std::optional<string> client_path, int rounds) :
+ ud_server_client(server_path, client_path, rounds, 0) {};
+
+ ud_server_client(string server_path, std::optional<string> client_path, int rounds,
+ int abort_run) :
+ server_addr{unix_domain_addr{server_path}}, client_path{client_path},
+ rounds{rounds},
+ rounds_left{rounds}, abort_after{abort_run} {}
+
+ future<> run();
+ ud_server_client(ud_server_client&&) = default;
+ ud_server_client(const ud_server_client&) = delete;
+
+private:
+ const string test_message{"are you still the same?"s};
+ future<> init_server();
+ future<> client_round();
+ const socket_address server_addr;
+
+ const std::optional<string> client_path;
+ server_socket server;
+ const int rounds;
+ int rounds_left;
+ server_socket* lstn_sock;
+ seastar::thread th;
+ int abort_after; // if set - force the listening socket down after that number of rounds
+ bool planned_abort{false}; // set when abort_accept() is called
+};
+
+future<> ud_server_client::init_server() {
+ return do_with(seastar::listen(server_addr), [this](server_socket& lstn) mutable {
+
+ lstn_sock = &lstn; // required when aborting (on some tests)
+
+ // start the clients here, where we know the server is listening
+
+ th = seastar::thread([this]{
+ for (int i=0; i<rounds; ++i) {
+ if (abort_after) {
+ if (--abort_after == 0) {
+ planned_abort = true;
+ lstn_sock->abort_accept();
+ break;
+ }
+ }
+ (void)client_round().get0();
+ }
+ });
+
+ return do_until([this](){return rounds_left<=0;}, [&lstn,this]() {
+ return lstn.accept().then([this](accept_result from_accept) {
+ connected_socket cn = std::move(from_accept.connection);
+ socket_address cn_addr = std::move(from_accept.remote_address);
+ --rounds_left;
+ // verify the client address
+ if (client_path) {
+ socket_address tmmp{unix_domain_addr{*client_path}};
+ BOOST_REQUIRE_EQUAL(cn_addr, socket_address{unix_domain_addr{*client_path}});
+ }
+
+ return do_with(cn.input(), cn.output(), [](auto& inp, auto& out) {
+
+ return inp.read().then([&out](auto bb) {
+ string ans = "-"s;
+ if (bb && bb.size()) {
+ ans = "+"s + string{bb.get(), bb.size()};
+ }
+ return out.write(ans).then([&out](){return out.flush();}).
+ then([&out](){return out.close();});
+ }).then([&inp]() { return inp.close(); }).
+ then([]() { return make_ready_future<>(); });
+
+ }).then([]{ return make_ready_future<>();});
+ });
+ }).handle_exception([this](auto e) {
+ // OK to get here only if the test was a "planned abort" one
+ if (!planned_abort) {
+ std::rethrow_exception(e);
+ }
+ }).finally([this]{
+ return th.join();
+ });
+ });
+}
+
+/// Send a message to the server, and expect (almost) the same string back.
+/// If 'client_path' is set, the client binds to the named path.
+future<> ud_server_client::client_round() {
+ auto cc = client_path ?
+ engine().net().connect(server_addr, socket_address{unix_domain_addr{*client_path}}).get0() :
+ engine().net().connect(server_addr).get0();
+
+ return do_with(cc.input(), cc.output(), [this](auto& inp, auto& out) {
+
+ return out.write(test_message).then(
+ [&out](){ return out.flush(); }).then(
+ [&inp](){ return inp.read(); }).then(
+ [this,&inp](auto bb){
+ BOOST_REQUIRE_EQUAL(std::string_view(bb.begin(), bb.size()), "+"s+test_message);
+ return inp.close();
+ }).then([&out](){return out.close();}).then(
+ []{ return make_ready_future<>(); });
+ });
+
+}
+
+future<> ud_server_client::run() {
+ return async([this] {
+ auto serverfut = init_server();
+ (void)serverfut.get();
+ });
+
+}
+
+// testing the various address types, both on the server and on the
+// client side
+
+SEASTAR_TEST_CASE(unixdomain_server) {
+ system("rm -f /tmp/ry");
+ ud_server_client uds("/tmp/ry", std::nullopt, 3);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(unixdomain_abs) {
+ char sv_name[]{'\0', '1', '1', '1'};
+ //ud_server_client uds(string{"\0111",4}, string{"\0112",4}, 1);
+ ud_server_client uds(string{sv_name,4}, std::nullopt, 4);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+ //return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(unixdomain_abs_bind) {
+ char sv_name[]{'\0', '1', '1', '1'};
+ char cl_name[]{'\0', '1', '1', '2'};
+ ud_server_client uds(string{sv_name,4}, string{cl_name,4}, 1);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+SEASTAR_TEST_CASE(unixdomain_abs_bind_2) {
+ char sv_name[]{'\0', '1', '\0', '\12', '1'};
+ char cl_name[]{'\0', '1', '\0', '\12', '2'};
+ ud_server_client uds(string{sv_name,5}, string{cl_name,5}, 2);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+SEASTAR_TEST_CASE(unixdomain_text) {
+ socket_address addr1{unix_domain_addr{"abc"}};
+ BOOST_REQUIRE_EQUAL(format("{}", addr1), "abc");
+ socket_address addr2{unix_domain_addr{""}};
+ BOOST_REQUIRE_EQUAL(format("{}", addr2), "{unnamed}");
+ socket_address addr3{unix_domain_addr{std::string("\0abc", 5)}};
+ BOOST_REQUIRE_EQUAL(format("{}", addr3), "@abc_");
+ return make_ready_future<>();
+}
+
+SEASTAR_TEST_CASE(unixdomain_bind) {
+ system("rm -f 111 112");
+ ud_server_client uds("111"s, "112"s, 1);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+SEASTAR_TEST_CASE(unixdomain_short) {
+ system("rm -f 3");
+ ud_server_client uds("3"s, std::nullopt, 10);
+ return do_with(std::move(uds), [](auto& uds){
+ return uds.run();
+ });
+}
+
+// test our ability to abort the accept()'ing on a socket.
+// The test covers a specific bug in the handling of abort_accept()
+SEASTAR_TEST_CASE(unixdomain_abort) {
+ std::string sockname{"7"s}; // note: no portable & warnings-free option
+ std::ignore = ::unlink(sockname.c_str());
+ ud_server_client uds(sockname, std::nullopt, 10, 4);
+ return do_with(std::move(uds), [sockname](auto& uds){
+ return uds.run().finally([sockname](){
+ std::ignore = ::unlink(sockname.c_str());
+ return seastar::make_ready_future<>();
+ });
+ });
+}
+
diff --git a/src/seastar/tests/unit/unwind_test.cc b/src/seastar/tests/unit/unwind_test.cc
new file mode 100644
index 000000000..b2f2e7f55
--- /dev/null
+++ b/src/seastar/tests/unit/unwind_test.cc
@@ -0,0 +1,70 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <pthread.h>
+#include <seastar/util/defer.hh>
+#include <seastar/core/posix.hh>
+#include <seastar/util/backtrace.hh>
+
+using namespace seastar;
+
+void foo() {
+ throw std::runtime_error("foo");
+}
+
+// Exploits issue #1725
+BOOST_AUTO_TEST_CASE(test_signal_mask_is_preserved_on_unwinding) {
+ sigset_t mask;
+ sigset_t old;
+ sigfillset(&mask);
+ auto res = ::pthread_sigmask(SIG_SETMASK, &mask, &old);
+ throw_pthread_error(res);
+
+ // Check which signals we actually managed to block
+ res = ::pthread_sigmask(SIG_SETMASK, NULL, &mask);
+ throw_pthread_error(res);
+
+ try {
+ foo();
+ } catch (...) {
+ // ignore
+ }
+
+ // Check backtrace()
+ {
+ size_t count = 0;
+ backtrace([&count] (auto) { ++count; });
+ BOOST_REQUIRE(count > 0);
+ }
+
+ {
+ sigset_t mask2;
+ auto res = ::pthread_sigmask(SIG_SETMASK, &old, &mask2);
+ throw_pthread_error(res);
+
+ for (int i = 1; i < NSIG; ++i) {
+ BOOST_REQUIRE(sigismember(&mask2, i) == sigismember(&mask, i));
+ }
+ }
+}
diff --git a/src/seastar/tests/unit/weak_ptr_test.cc b/src/seastar/tests/unit/weak_ptr_test.cc
new file mode 100644
index 000000000..769c27481
--- /dev/null
+++ b/src/seastar/tests/unit/weak_ptr_test.cc
@@ -0,0 +1,136 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright 2016 ScyllaDB
+ */
+
+#define BOOST_TEST_MODULE core
+
+#include <boost/test/included/unit_test.hpp>
+#include <seastar/core/weak_ptr.hh>
+
+using namespace seastar;
+
+class myclass : public weakly_referencable<myclass> {};
+
+static_assert(std::is_nothrow_default_constructible_v<myclass>);
+
+static_assert(std::is_nothrow_default_constructible_v<weak_ptr<myclass>>);
+static_assert(std::is_nothrow_move_constructible_v<weak_ptr<myclass>>);
+
+BOOST_AUTO_TEST_CASE(test_weak_ptr_is_empty_when_default_initialized) {
+ weak_ptr<myclass> wp;
+ BOOST_REQUIRE(!bool(wp));
+}
+
+BOOST_AUTO_TEST_CASE(test_weak_ptr_is_reset) {
+ auto owning_ptr = std::make_unique<myclass>();
+ weak_ptr<myclass> wp = owning_ptr->weak_from_this();
+ BOOST_REQUIRE(bool(wp));
+ BOOST_REQUIRE(&*wp == &*owning_ptr);
+ owning_ptr = {};
+ BOOST_REQUIRE(!bool(wp));
+}
+
+BOOST_AUTO_TEST_CASE(test_weak_ptr_can_be_moved) {
+ auto owning_ptr = std::make_unique<myclass>();
+ weak_ptr<myclass> wp1 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp2 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp3 = owning_ptr->weak_from_this();
+
+ auto wp3_moved = std::move(wp3);
+ auto wp1_moved = std::move(wp1);
+ auto wp2_moved = std::move(wp2);
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+ BOOST_REQUIRE(bool(wp1_moved));
+ BOOST_REQUIRE(bool(wp2_moved));
+ BOOST_REQUIRE(bool(wp3_moved));
+
+ owning_ptr = {};
+
+ BOOST_REQUIRE(!bool(wp1_moved));
+ BOOST_REQUIRE(!bool(wp2_moved));
+ BOOST_REQUIRE(!bool(wp3_moved));
+}
+
+BOOST_AUTO_TEST_CASE(test_multipe_weak_ptrs) {
+ auto owning_ptr = std::make_unique<myclass>();
+
+ weak_ptr<myclass> wp1 = owning_ptr->weak_from_this();
+ BOOST_REQUIRE(bool(wp1));
+ BOOST_REQUIRE(&*wp1 == &*owning_ptr);
+
+ weak_ptr<myclass> wp2 = owning_ptr->weak_from_this();
+ BOOST_REQUIRE(bool(wp2));
+ BOOST_REQUIRE(&*wp2 == &*owning_ptr);
+
+ owning_ptr = {};
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+}
+
+BOOST_AUTO_TEST_CASE(test_multipe_weak_ptrs_going_away_first) {
+ auto owning_ptr = std::make_unique<myclass>();
+
+ weak_ptr<myclass> wp1 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp2 = owning_ptr->weak_from_this();
+ weak_ptr<myclass> wp3 = owning_ptr->weak_from_this();
+
+ BOOST_REQUIRE(bool(wp1));
+ BOOST_REQUIRE(bool(wp2));
+ BOOST_REQUIRE(bool(wp3));
+
+ wp2 = {};
+
+ owning_ptr = std::make_unique<myclass>();
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+
+ wp1 = owning_ptr->weak_from_this();
+ wp2 = owning_ptr->weak_from_this();
+ wp3 = owning_ptr->weak_from_this();
+
+ BOOST_REQUIRE(bool(wp1));
+ BOOST_REQUIRE(bool(wp2));
+ BOOST_REQUIRE(bool(wp3));
+
+ wp3 = {};
+ owning_ptr = std::make_unique<myclass>();
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+
+ wp1 = owning_ptr->weak_from_this();
+ wp2 = owning_ptr->weak_from_this();
+ wp3 = owning_ptr->weak_from_this();
+
+ wp1 = {};
+ wp3 = {};
+ owning_ptr = std::make_unique<myclass>();
+
+ BOOST_REQUIRE(!bool(wp1));
+ BOOST_REQUIRE(!bool(wp2));
+ BOOST_REQUIRE(!bool(wp3));
+}