diff options
Diffstat (limited to 'src/seastar/tests/unit')
83 files changed, 26538 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..62b614809 --- /dev/null +++ b/src/seastar/tests/unit/CMakeLists.txt @@ -0,0 +1,681 @@ +# +# 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 + SEASTAR_TESTING_WITH_NETWORKING=$<BOOL:${Seastar_ENABLE_TESTS_ACCESSING_INTERNET}>) + + 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 (chunk_parsers + SOURCES chunk_parsers_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 (condition_variable + SOURCES condition_variable_test.cc) + +seastar_add_test (connect + SOURCES connect_test.cc) + +seastar_add_test (content_source + SOURCES content_source_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 (abortable_fifo + SOURCES abortable_fifo_test.cc) + +seastar_add_test (io_queue + SOURCES io_queue_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 (websocket + SOURCES websocket_test.cc) + +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_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 (stream_reader + SOURCES stream_reader_test.cc) + +seastar_add_test (thread + SOURCES thread_test.cc + LIBRARIES Valgrind::valgrind) + +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) + +seastar_add_test (source_location + KIND BOOST + SOURCES source_location_test.cc) + +seastar_add_test (shared_token_bucket + SOURCES shared_token_bucket_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 -quiet -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 -quiet -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() + +function(seastar_gen_mtls_certs) + find_program(OPENSSL openssl) + + set(SUBJECT "/C=GB/ST=London/L=London/O=Redpanda Data/OU=Core/CN=redpanda.com") + set(EXTENSION "subjectAltName = IP:127.0.0.1") + set(CLIENT1_SUBJECT "/C=GB/ST=London/L=London/O=Redpanda Data/OU=Core/CN=client1.org") + set(CLIENT2_SUBJECT "/C=GB/ST=London/L=London/O=Redpanda Data/OU=Core/CN=client2.org") + + add_custom_command(OUTPUT mtls_ca.key + COMMAND ${OPENSSL} ecparam -name prime256v1 -genkey -noout -out mtls_ca.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_command(OUTPUT mtls_ca.crt + COMMAND ${OPENSSL} req -new -x509 -sha256 -key mtls_ca.key -out mtls_ca.crt -subj ${SUBJECT} -addext ${EXTENSION} + DEPENDS mtls_ca.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + # server certificates + add_custom_command(OUTPUT mtls_server.key + COMMAND ${OPENSSL} ecparam -name prime256v1 -genkey -noout -out mtls_server.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_command(OUTPUT mtls_server.csr + COMMAND ${OPENSSL} req -new -sha256 -key mtls_server.key -out mtls_server.csr -subj ${SUBJECT} + DEPENDS mtls_server.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_command(OUTPUT mtls_server.crt + COMMAND ${OPENSSL} x509 -req -in mtls_server.csr -CA mtls_ca.crt -CAkey mtls_ca.key -CAcreateserial -out mtls_server.crt -days 1000 -sha256 + DEPENDS mtls_server.csr mtls_ca.crt mtls_ca.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + # client1 certificates + add_custom_command(OUTPUT mtls_client1.key + COMMAND ${OPENSSL} ecparam -name prime256v1 -genkey -noout -out mtls_client1.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_command(OUTPUT mtls_client1.csr + COMMAND ${OPENSSL} req -new -sha256 -key mtls_client1.key -out mtls_client1.csr -subj ${CLIENT1_SUBJECT} + DEPENDS mtls_client1.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_command(OUTPUT mtls_client1.crt + COMMAND ${OPENSSL} x509 -req -in mtls_client1.csr -CA mtls_ca.crt -CAkey mtls_ca.key -CAcreateserial -out mtls_client1.crt -days 1000 -sha256 + DEPENDS mtls_client1.csr mtls_ca.crt mtls_ca.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + # client2 certificates + add_custom_command(OUTPUT mtls_client2.key + COMMAND ${OPENSSL} ecparam -name prime256v1 -genkey -noout -out mtls_client2.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_command(OUTPUT mtls_client2.csr + COMMAND ${OPENSSL} req -new -sha256 -key mtls_client2.key -out mtls_client2.csr -subj ${CLIENT2_SUBJECT} + DEPENDS mtls_client2.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_command(OUTPUT mtls_client2.crt + COMMAND ${OPENSSL} x509 -req -in mtls_client2.csr -CA mtls_ca.crt -CAkey mtls_ca.key -CAcreateserial -out mtls_client2.crt -days 1000 -sha256 + DEPENDS mtls_client2.csr mtls_ca.crt mtls_ca.key + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + + add_custom_target(mtls_certs + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/mtls_client1.crt" "${CMAKE_CURRENT_BINARY_DIR}/mtls_client2.crt" "${CMAKE_CURRENT_BINARY_DIR}/mtls_server.crt" + ) +endfunction() + +seastar_add_certgen(testcrt DOMAIN scylladb.org SERVER test) +seastar_add_certgen(othercrt DOMAIN apa.org SERVER other) +seastar_gen_mtls_certs() + +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} +) + +add_custom_command ( + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/https-server.py + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/https-server.py + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/https-server.py ${CMAKE_CURRENT_BINARY_DIR}) + +add_custom_target (https_server + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/https-server.py) + +seastar_add_test (tls + DEPENDS tls_files testcrt othercrt mtls_certs https_server + 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) + +seastar_add_test (exception_logging + KIND BOOST + SOURCES exception_logging_test.cc) + +seastar_add_test (closeable + SOURCES closeable_test.cc) + +seastar_add_test (pipe + SOURCES pipe_test.cc) + +seastar_add_test (spawn + SOURCES spawn_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..57fbcdad5 --- /dev/null +++ b/src/seastar/tests/unit/abort_source_test.cc @@ -0,0 +1,176 @@ +/* + * 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/testing/thread_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); + BOOST_REQUIRE_EQUAL(false, bool(st_opt)); + BOOST_REQUIRE_THROW(as.check(), abort_requested_exception); + 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); + }); +} + +SEASTAR_TEST_CASE(test_request_abort_with_exception) { + abort_source as; + optimized_optional<abort_source::subscription> st_opt; + std::optional<std::exception_ptr> aborted_ex; + auto expected_message = "expected"; + + auto make_abort_source = [&] () { + as = abort_source(); + st_opt = as.subscribe([&aborted_ex] (const std::optional<std::exception_ptr>& opt_ex) noexcept { + aborted_ex = opt_ex; + }); + aborted_ex = std::nullopt; + }; + + make_abort_source(); + auto ex = make_exception_ptr(std::runtime_error(expected_message)); + as.request_abort_ex(ex); + BOOST_REQUIRE(aborted_ex.has_value()); + bool caught_exception = false; + try { + std::rethrow_exception(*aborted_ex); + } catch (const std::runtime_error& e) { + BOOST_REQUIRE_EQUAL(e.what(), expected_message); + caught_exception = true; + } + BOOST_REQUIRE(caught_exception); + BOOST_REQUIRE_THROW(as.check(), std::runtime_error); + + make_abort_source(); + as.request_abort_ex(make_exception_ptr(std::runtime_error(expected_message))); + BOOST_REQUIRE(aborted_ex.has_value()); + caught_exception = false; + try { + std::rethrow_exception(*aborted_ex); + } catch (const std::runtime_error& e) { + BOOST_REQUIRE_EQUAL(e.what(), expected_message); + caught_exception = true; + } + BOOST_REQUIRE(caught_exception); + BOOST_REQUIRE_THROW(as.check(), std::runtime_error); + + + make_abort_source(); + as.request_abort_ex(std::runtime_error(expected_message)); + BOOST_REQUIRE(aborted_ex.has_value()); + caught_exception = false; + try { + std::rethrow_exception(*aborted_ex); + } catch (const std::runtime_error& e) { + BOOST_REQUIRE_EQUAL(e.what(), expected_message); + caught_exception = true; + } + BOOST_REQUIRE(caught_exception); + BOOST_REQUIRE_THROW(as.check(), std::runtime_error); + + return make_ready_future<>(); +} + +SEASTAR_THREAD_TEST_CASE(test_sleep_abortable_with_exception) { + abort_source as; + auto f = sleep_abortable(10s, as); + auto expected_message = "expected"; + as.request_abort_ex(std::runtime_error(expected_message)); + + bool caught_exception = false; + try { + f.get(); + } catch (const std::runtime_error& e) { + BOOST_REQUIRE_EQUAL(e.what(), expected_message); + caught_exception = true; + } + BOOST_REQUIRE(caught_exception); +} + +SEASTAR_THREAD_TEST_CASE(test_destroy_with_moved_subscriptions) { + auto as = std::make_unique<abort_source>(); + int aborted = 0; + auto sub1 = as->subscribe([&] () noexcept { ++aborted; }); + auto sub2 = std::move(sub1); + optimized_optional<abort_source::subscription> sub3; + sub3 = std::move(sub2); + auto sub4 = as->subscribe([&] () noexcept { ++aborted; }); + sub4 = std::move(sub3); + as.reset(); + BOOST_REQUIRE_EQUAL(aborted, 0); +} diff --git a/src/seastar/tests/unit/abortable_fifo_test.cc b/src/seastar/tests/unit/abortable_fifo_test.cc new file mode 100644 index 000000000..41aec6f12 --- /dev/null +++ b/src/seastar/tests/unit/abortable_fifo_test.cc @@ -0,0 +1,194 @@ +/* + * 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) 2022 ScyllaDB + */ + +#include <seastar/core/thread.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/testing/thread_test_case.hh> +#include <seastar/core/abortable_fifo.hh> +#include <seastar/util/later.hh> +#include <boost/range/irange.hpp> + +using namespace seastar; + +SEASTAR_TEST_CASE(test_no_abortable_operations) { + internal::abortable_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_THREAD_TEST_CASE(test_abortable_operations) { + std::vector<int> expired; + struct my_expiry { + std::vector<int>& e; + void operator()(int& v) noexcept { e.push_back(v); } + }; + + internal::abortable_fifo<int, my_expiry> fifo(my_expiry{expired}); + abort_source as; + + fifo.push_back(1, as); + + BOOST_REQUIRE(!fifo.empty()); + BOOST_REQUIRE_EQUAL(fifo.size(), 1u); + BOOST_REQUIRE(bool(fifo)); + BOOST_REQUIRE_EQUAL(fifo.front(), 1); + + as.request_abort(); + yield().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(); + as = abort_source(); + + fifo.push_back(1); + fifo.push_back(2, as); + fifo.push_back(3); + + as.request_abort(); + yield().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(); + + abort_source as1, as2; + + fifo.push_back(1, as1); + fifo.push_back(2, as1); + fifo.push_back(3); + fifo.push_back(4, as2); + + as1.request_abort(); + yield().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(); + + as = abort_source(); + + fifo.push_back(1); + fifo.push_back(2, as); + fifo.push_back(3, as); + fifo.push_back(4, as); + + as.request_abort(); + yield().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(); + as = abort_source(); + + fifo.push_back(1); + fifo.push_back(2, as); + fifo.push_back(3, as); + fifo.push_back(4, as); + fifo.push_back(5); + + as.request_abort(); + yield().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/alien_test.cc b/src/seastar/tests/unit/alien_test.cc new file mode 100644 index 000000000..334f8df19 --- /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); + seastar::app_template app; + + // use the raw fd, because seastar engine want to take over the fds, if it + // polls on them. + auto zim = std::async([&app, 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(app.alien(), i, [i] { + return seastar::make_ready_future<int>(i); + })); + } + // std::future<void> + alien::submit_to(app.alien(), 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; + }); + + 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..f7c33a0eb --- /dev/null +++ b/src/seastar/tests/unit/alloc_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/core/memory.hh> +#include <seastar/core/smp.hh> +#include <seastar/core/temporary_buffer.hh> +#include <seastar/util/memory_diagnostics.hh> +#include <seastar/util/log.hh> + +#include <memory> +#include <new> +#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<>(); +} + +SEASTAR_TEST_CASE(test_memory_diagnostics) { + auto report = memory::generate_memory_diagnostics_report(); +#ifdef SEASTAR_DEFAULT_ALLOCATOR + BOOST_REQUIRE(report.length() == 0); // empty report with default allocator +#else + // since the output format is unstructured text, not much + // to do except test that we get a non-empty string + BOOST_REQUIRE(report.length() > 0); + // useful while debugging diagnostics + // fmt::print("--------------------\n{}--------------------", report); +#endif + 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<>(); +} + +// So the compiler won't optimize the call to realloc(nullptr, size) +// and call malloc directly. +void* test_nullptr = nullptr; + +SEASTAR_TEST_CASE(test_realloc_nullptr) { + auto p0 = realloc(test_nullptr, 8); + BOOST_REQUIRE(p0 != nullptr); + BOOST_REQUIRE_EQUAL(realloc(p0, 0), nullptr); + + p0 = realloc(test_nullptr, 0); + BOOST_REQUIRE(p0 != nullptr); + auto p1 = malloc(0); + BOOST_REQUIRE(p1 != nullptr); + free(p0); + free(p1); + + return make_ready_future<>(); +} + +void * volatile sink; + +SEASTAR_TEST_CASE(test_bad_alloc_throws) { + // test that a large allocation throws bad_alloc + auto stats = seastar::memory::stats(); + + // this allocation cannot be satisfied (at least when the seastar + // allocator is used, which it is for this test) + size_t size = stats.total_memory() * 2; + + auto failed_allocs = [&stats]() { + return seastar::memory::stats().failed_allocations() - stats.failed_allocations(); + }; + + // test that new throws + stats = seastar::memory::stats(); + BOOST_REQUIRE_THROW(sink = operator new(size), std::bad_alloc); + BOOST_CHECK_EQUAL(failed_allocs(), 1); + + // test that huge malloc returns null + stats = seastar::memory::stats(); + BOOST_REQUIRE_EQUAL(malloc(size), nullptr); + BOOST_CHECK_EQUAL(failed_allocs(), 1); + + // test that huge realloc on nullptr returns null + stats = seastar::memory::stats(); + BOOST_REQUIRE_EQUAL(realloc(nullptr, size), nullptr); + BOOST_CHECK_EQUAL(failed_allocs(), 1); + + // test that huge realloc on an existing ptr returns null + stats = seastar::memory::stats(); + void *p = malloc(1); + BOOST_REQUIRE(p); + void *p2 = realloc(p, size); + BOOST_REQUIRE_EQUAL(p2, nullptr); + BOOST_CHECK_EQUAL(failed_allocs(), 1); + free(p2 ?: p); + + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(test_diagnostics_failures) { + // test that an allocation failure is reflected in the diagnostics + auto stats = seastar::memory::stats(); + + size_t size = stats.total_memory() * 2; // cannot be satisfied + + // we expect that the failure is immediately reflected in the diagnostics + try { + sink = operator new(size); + } catch (const std::bad_alloc&) {} + + auto report = memory::generate_memory_diagnostics_report(); + + // +1 because we caused one additional hard failure from the allocation above + auto expected = fmt::format("Hard failures: {}", stats.failed_allocations() + 1); + + if (report.find(expected) == seastar::sstring::npos) { + BOOST_FAIL(fmt::format("Did not find expected message: {} in\n{}\n", expected, report)); + } + + return seastar::make_ready_future(); +} + +template <typename Func> +SEASTAR_CONCEPT(requires requires (Func fn) { fn(); }) +void check_function_allocation(const char* name, size_t expected_allocs, Func f) { + auto before = seastar::memory::stats(); + f(); + auto after = seastar::memory::stats(); + + BOOST_TEST_INFO("After function: " << name); + BOOST_REQUIRE_EQUAL(expected_allocs, after.mallocs() - before.mallocs()); +} + +SEASTAR_TEST_CASE(test_diagnostics_allocation) { + + check_function_allocation("empty", 0, []{}); + + check_function_allocation("operator new", 1, []{ + // note that many pairs of malloc/free-alikes can just be optimized + // away, but not operator new(size_t), per the standard + void * volatile p = operator new(1); + operator delete(p); + }); + + // The meat of this test. Dump the diagnostics report to the log and ensure it + // doesn't allocate. Doing it lots is important because it may alloc only occasionally: + // a real example being the optimized timestamp logging which (used to) make an allocation + // only once a second. + check_function_allocation("log_memory_diagnostics_report", 0, [&]{ + for (int i = 0; i < 1000; i++) { + seastar::memory::internal::log_memory_diagnostics_report(log_level::info); + } + }); + + return seastar::make_ready_future(); +} + + +#endif // #ifndef SEASTAR_DEFAULT_ALLOCATOR + +SEASTAR_TEST_CASE(test_large_allocation_warning_off_by_one) { +#ifndef SEASTAR_DEFAULT_ALLOCATOR + constexpr size_t large_alloc_threshold = 1024*1024; + seastar::memory::scoped_large_allocation_warning_threshold mtg(large_alloc_threshold); + BOOST_REQUIRE(seastar::memory::get_large_allocation_warning_threshold() == large_alloc_threshold); + auto old_large_allocs_count = memory::stats().large_allocations(); + volatile auto obj = (char*)malloc(large_alloc_threshold); + *obj = 'c'; // to prevent compiler from considering this a dead allocation and optimizing it out + + // Verify large allocation was detected by allocator. + BOOST_REQUIRE(memory::stats().large_allocations() == old_large_allocs_count+1); + + free(obj); +#endif + return make_ready_future<>(); +} diff --git a/src/seastar/tests/unit/allocator_test.cc b/src/seastar/tests/unit/allocator_test.cc new file mode 100644 index 000000000..59e61ada0 --- /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<int> 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..8e3cabf05 --- /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/chunk_parsers_test.cc b/src/seastar/tests/unit/chunk_parsers_test.cc new file mode 100644 index 000000000..e15b01f04 --- /dev/null +++ b/src/seastar/tests/unit/chunk_parsers_test.cc @@ -0,0 +1,120 @@ +/* + * 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/thread.hh> +#include <seastar/core/temporary_buffer.hh> +#include <seastar/http/chunk_parsers.hh> +#include <seastar/http/internal/content_source.hh> +#include <seastar/testing/test_case.hh> +#include <tuple> +#include <utility> +#include <vector> + +using namespace seastar; + +SEASTAR_TEST_CASE(test_size_and_extensions_parsing) { + struct test_set { + sstring msg; + bool parsable; + sstring size = ""; + std::vector<std::pair<sstring, sstring>> extensions; + + temporary_buffer<char> buf() { + return temporary_buffer<char>(msg.c_str(), msg.size()); + } + }; + + std::vector<test_set> tests = { + { "14;name=value\r\n", true, "14", { {"name", "value"} } }, + { "abcdef;name=value;name2=\"value2\"\r\n", true, "abcdef" }, + { "1efg;name=value\r\n", false }, + { "aa;tchars.^_`|123=t1!#$%&'*+-.~\r\n", true, "aa", { {"tchars.^_`|123", "t1!#$%&'*+-.~"} } }, + { "1;quoted=\"hello world\";quoted-pair=\"\\a\\b\\cd\\\\ef\"\r\n", true, "1", { {"quoted", "hello world"}, {"quoted-pair", "abcd\\ef"} } }, + { "2;bad-quoted-pair=\"abc\\\"\r\n", false }, + { "3;quoted-pair-outside-quoted-string=\\q\\p\r\n", false }, + { "4;whitespace-outside-quoted-string=quoted string\r\n", false }, + { "5;quotation-mark-inside-quoted-string=\"quoted\"mark\"\r\n", false }, + { "6; bad=space\r\n", false }, + { "7;sole-name\r\n", true, "7", { { "sole-name", ""} } }, + { "8;empty_value=\"\"\r\n", true, "8", { { "empty_value", ""} } }, + { "0\r\n", true, "0" } + }; + + http_chunk_size_and_ext_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) { + BOOST_REQUIRE_EQUAL(parser.get_size(), std::move(tset.size)); + auto exts = parser.get_parsed_extensions(); + for (auto& ext : tset.extensions) { + BOOST_REQUIRE_EQUAL(exts[ext.first], ext.second); + } + } + } + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(test_trailer_headers_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 = { + // the headers follow the same rules as in the request parser + { "Host: test\r\n\r\n", true, "Host", "test" }, + { "Header: Field\r\n\r\n", true, "Header", "Field" }, + { "Header: \r\n\r\n", true, "Header", "" }, + { "Header: f i e l d \r\n\r\n", true, "Header", "f i e l d" }, + { "Header: fiel\r\n d\r\n\r\n", true, "Header", "fiel d" }, + { "tchars.^_`|123: printable!@#%^&*()obs_text\x80\x81\xff\r\n\r\n", true, + "tchars.^_`|123", "printable!@#%^&*()obs_text\x80\x81\xff" }, + { "Header: Field\r\nHeader: Field2\r\n\r\n", true, "Header", "Field,Field2" }, + { "Header : Field\r\n\r\n", false }, + { "Header Field\r\n\r\n", false }, + { "Header@: Field\r\n\r\n", false }, + { "Header: fiel\r\nd \r\n\r\n", false }, + { "\r\n", true } + }; + + http_chunk_trailer_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 heads = parser.get_parsed_headers(); + BOOST_REQUIRE_EQUAL(heads[std::move(tset.header_name)], std::move(tset.header_value)); + } + } + return make_ready_future<>(); +} 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..3564ea0e8 --- /dev/null +++ b/src/seastar/tests/unit/chunked_fifo_test.cc @@ -0,0 +1,378 @@ +/* + * 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())); + } +} + +BOOST_AUTO_TEST_CASE(chunked_fifo_const_iterator) { + constexpr auto items_per_chunk = 8; + auto fifo = chunked_fifo<int, items_per_chunk>{}; + auto reference = std::deque<int>{}; + + BOOST_REQUIRE(fifo.cbegin() == fifo.cend()); + + for (int i = 0; i < items_per_chunk * 4; ++i) { + fifo.push_back(i); + reference.push_back(i); + BOOST_REQUIRE(std::equal(fifo.cbegin(), fifo.cend(), reference.cbegin(), reference.cend())); + } + + for (int i = 0; i < items_per_chunk * 2; ++i) { + fifo.pop_front(); + reference.pop_front(); + BOOST_REQUIRE(std::equal(fifo.cbegin(), fifo.cend(), reference.cbegin(), reference.cend())); + } +} 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..e4469134c --- /dev/null +++ b/src/seastar/tests/unit/circular_buffer_test.cc @@ -0,0 +1,179 @@ +/* + * 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 <random> +#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()); +} + +BOOST_AUTO_TEST_CASE(test_underflow_index_iterator_comparison) { + circular_buffer<int> buf; + + const auto seed = std::random_device()(); + std::cout << "seed=" << seed << std::endl; + auto rnd_engine = std::mt19937(seed); + std::uniform_int_distribution<unsigned> count_dist(0, 20); + std::uniform_int_distribution<unsigned> bool_dist(false, true); + + auto push_back = [&buf] (unsigned n) { + for (unsigned i = 0; i < n; ++i) { + buf.push_back(i); + } + }; + auto push_front = [&buf] (unsigned n) { + for (unsigned i = 0; i < n; ++i) { + buf.push_front(i); + } + }; + + for (unsigned i = 0; i < 16; ++i) { + const auto push_back_count = count_dist(rnd_engine); + const auto push_front_count = count_dist(rnd_engine); + std::cout << "round[" << i << "]: " << buf.size() << " front: " << push_front_count << " back: " << push_back_count << std::endl; + if (bool_dist(rnd_engine)) { + push_back(push_back_count); + push_front(std::max(20 - push_back_count, push_front_count)); + } else { + push_front(push_front_count); + push_back(std::max(20 - push_front_count, push_back_count)); + } + + if (buf.empty()) { + continue; + } + + for (auto it1 = buf.begin(); it1 != buf.end(); ++it1) { + bool bypass = false; + for (auto it2 = buf.end(); it2 != buf.begin(); --it2) { + auto itl = it1; + auto ith = it2; + if (bypass) { + std::swap(itl, ith); + } + if (itl == ith) { + bypass = true; + } else { + BOOST_REQUIRE(itl < ith); + BOOST_REQUIRE(ith > itl); + BOOST_REQUIRE(!(ith < itl)); + BOOST_REQUIRE(!(itl > ith)); + } + + BOOST_REQUIRE(itl <= ith); + BOOST_REQUIRE(itl <= itl); + BOOST_REQUIRE(ith <= ith); + BOOST_REQUIRE(ith >= itl); + BOOST_REQUIRE(itl >= itl); + BOOST_REQUIRE(ith >= ith); + } + } + + const auto erase_count = count_dist(rnd_engine); + const auto offset = count_dist(rnd_engine); + std::cout << "round[" << i << "]: " << erase_count << " @ " << offset << std::endl; + buf.erase(buf.begin() + offset, buf.begin() + std::min(size_t(offset + erase_count), buf.size())); + } +} diff --git a/src/seastar/tests/unit/closeable_test.cc b/src/seastar/tests/unit/closeable_test.cc new file mode 100644 index 000000000..ef81f5938 --- /dev/null +++ b/src/seastar/tests/unit/closeable_test.cc @@ -0,0 +1,380 @@ +/* + * 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 2021 ScyllaDB + */ + +#include <exception> + +#include <boost/range/irange.hpp> + +#include <seastar/testing/test_case.hh> +#include <seastar/testing/thread_test_case.hh> + +#include <seastar/core/gate.hh> +#include <seastar/util/closeable.hh> +#include <seastar/core/loop.hh> + +using namespace seastar; + +class expected_exception : public std::runtime_error { +public: + expected_exception() : runtime_error("expected") {} +}; + +SEASTAR_TEST_CASE(deferred_close_test) { + return do_with(gate(), 0, 42, [] (gate& g, int& count, int& expected) { + return async([&] { + auto close_gate = deferred_close(g); + + for (auto i = 0; i < expected; i++) { + (void)with_gate(g, [&count] { + ++count; + }); + } + }).then([&] { + // destroying close_gate should invoke g.close() + // and wait for all background continuations to complete + BOOST_REQUIRE(g.is_closed()); + BOOST_REQUIRE_EQUAL(count, expected); + }); + }); +} + +SEASTAR_TEST_CASE(move_deferred_close_test) { + return do_with(gate(), [] (gate& g) { + return async([&] { + auto close_gate = make_shared(deferred_close(g)); + // g.close() should not be called when deferred_close is moved away + BOOST_REQUIRE(!g.is_closed()); + }).then([&] { + // Before this test is exercised, gate::close() would run into a + // assert failure when leaving previous continuation, if gate::close() + // is called twice, so this test only verifies the behavior with the + // release build. + BOOST_REQUIRE(g.is_closed()); + }); + }); +} + +SEASTAR_TEST_CASE(close_now_test) { + return do_with(gate(), 0, 42, [] (gate& g, int& count, int& expected) { + return async([&] { + auto close_gate = deferred_close(g); + + for (auto i = 0; i < expected; i++) { + (void)with_gate(g, [&count] { + ++count; + }); + } + + close_gate.close_now(); + BOOST_REQUIRE(g.is_closed()); + BOOST_REQUIRE_EQUAL(count, expected); + // gate must not be double-closed. + }); + }); +} + +SEASTAR_TEST_CASE(cancel_deferred_close_test) { + gate g; + { + auto close_gate = deferred_close(g); + close_gate.cancel(); + } + g.check(); // should not throw + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(with_closeable_test) { + return do_with(0, 42, [] (int& count, int& expected) { + return with_closeable(gate(), [&] (gate& g) { + for (auto i = 0; i < expected; i++) { + (void)with_gate(g, [&count] { + ++count; + }); + } + return 17; + }).then([&] (int res) { + // res should be returned by the function called + // by with_closeable. + BOOST_REQUIRE_EQUAL(res, 17); + // closing the gate should wait for + // all background continuations to complete + BOOST_REQUIRE_EQUAL(count, expected); + }); + }); +} + +SEASTAR_TEST_CASE(with_closeable_exception_test) { + return do_with(0, 42, [] (int& count, int& expected) { + return with_closeable(gate(), [&] (gate& g) { + for (auto i = 0; i < expected; i++) { + (void)with_gate(g, [&count] { + ++count; + }); + } + throw expected_exception(); + }).handle_exception_type([&] (const expected_exception&) { + // closing the gate should also happen when func throws, + // waiting for all background continuations to complete + BOOST_REQUIRE_EQUAL(count, expected); + }); + }); +} + +namespace { + +class count_stops { + int _count = -1; + int* _ptr = nullptr; +public: + count_stops(int* ptr = nullptr) noexcept + : _ptr(ptr ? ptr : &_count) + { + *_ptr = 0; + } + + count_stops(count_stops&& o) noexcept { + std::exchange(_count, o._count); + if (o._ptr == &o._count) { + _ptr = &_count; + } else { + std::exchange(_ptr, o._ptr); + } + } + + future<> stop() noexcept { + ++*_ptr; + return make_ready_future<>(); + } + + int stopped() const noexcept { + return *_ptr; + } +}; + +} // anonymous namespace + +SEASTAR_TEST_CASE(cancel_deferred_stop_test) { + count_stops cs; + { + auto stop = deferred_stop(cs); + stop.cancel(); + } + BOOST_REQUIRE_EQUAL(cs.stopped(), 0); + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(deferred_stop_test) { + return do_with(count_stops(), [] (count_stops& cs) { + return async([&] { + auto stop_counting = deferred_stop(cs); + }).then([&] { + // cs.stop() should be called when stop_counting is destroyed + BOOST_REQUIRE_EQUAL(cs.stopped(), 1); + }); + }); +} + +SEASTAR_TEST_CASE(move_deferred_stop_test) { + return do_with(count_stops(), [] (count_stops& cs) { + return async([&] { + auto stop = make_shared(deferred_stop(cs)); + }).then([&] { + // cs.stop() should be called once and only once + // when stop is destroyed + BOOST_REQUIRE_EQUAL(cs.stopped(), 1); + }); + }); +} + +SEASTAR_TEST_CASE(stop_now_test) { + return do_with(count_stops(), [] (count_stops& cs) { + return async([&] { + auto stop_counting = deferred_stop(cs); + + stop_counting.stop_now(); + // cs.stop() should not be called again + // when stop_counting is destroyed + BOOST_REQUIRE_EQUAL(cs.stopped(), 1); + }).then([&] { + // cs.stop() should be called exactly once + BOOST_REQUIRE_EQUAL(cs.stopped(), 1); + }); + }); +} + +SEASTAR_TEST_CASE(with_stoppable_test) { + return do_with(0, [] (int& stopped) { + return with_stoppable(count_stops(&stopped), [] (count_stops& cs) { + return 17; + }).then([&] (int res) { + // res should be returned by the function called + // by with_closeable. + BOOST_REQUIRE_EQUAL(res, 17); + // cs.stop() should be called before count_stops is destroyed + BOOST_REQUIRE_EQUAL(stopped, 1); + }); + }); +} + +SEASTAR_TEST_CASE(with_stoppable_exception_test) { + return do_with(0, [] (int& stopped) { + return with_stoppable(count_stops(&stopped), [] (count_stops& cs) { + throw expected_exception(); + }).handle_exception_type([&] (const expected_exception&) { + // cs.stop() should be called before count_stops is destroyed + // also when func throws + BOOST_REQUIRE_EQUAL(stopped, 1); + }); + }); +} + +SEASTAR_THREAD_TEST_CASE(move_open_gate_test) { + gate g1; + g1.enter(); + // move an open gate + gate g2 = std::move(g1); + // the state in g1 should be moved into g2 + BOOST_CHECK_EQUAL(g1.get_count(), 0); + BOOST_REQUIRE_EQUAL(g2.get_count(), 1); + g2.leave(); + g2.close().get(); + BOOST_CHECK(!g1.is_closed()); + BOOST_CHECK(g2.is_closed()); +} + +SEASTAR_THREAD_TEST_CASE(move_closing_gate_test) { + gate g1; + g1.enter(); + auto fut = g1.close(); + // move a closing gate + gate g2 = std::move(g1); + BOOST_CHECK_EQUAL(g1.get_count(), 0); + BOOST_REQUIRE_EQUAL(g2.get_count(), 1); + g2.leave(); + fut.get(); + BOOST_CHECK(!g1.is_closed()); + BOOST_CHECK(g2.is_closed()); +} + +SEASTAR_THREAD_TEST_CASE(move_closed_gate_test) { + gate g1; + g1.close().get(); + // move a closed gate + gate g2 = std::move(g1); + BOOST_CHECK_EQUAL(g1.get_count(), 0); + BOOST_CHECK_EQUAL(g2.get_count(), 0); + BOOST_CHECK(!g1.is_closed()); + BOOST_CHECK(g2.is_closed()); +} + +SEASTAR_THREAD_TEST_CASE(gate_holder_basic_test) { + gate g; + auto gh = g.hold(); + auto fut = g.close(); + BOOST_CHECK(!fut.available()); + gh.release(); + fut.get(); +} + +SEASTAR_THREAD_TEST_CASE(gate_holder_closed_test) { + gate g; + g.close().get(); + BOOST_REQUIRE_THROW(g.hold(), gate_closed_exception); +} + +SEASTAR_THREAD_TEST_CASE(gate_holder_move_test) { + gate g; + auto gh0 = g.hold(); + auto fut = g.close(); + BOOST_CHECK(!fut.available()); + auto gh1 = std::move(gh0); + BOOST_CHECK(!fut.available()); + gh1.release(); + fut.get(); +} + +SEASTAR_THREAD_TEST_CASE(gate_holder_copy_test) { + gate g; + auto gh0 = g.hold(); + auto gh1 = gh0; + auto fut = g.close(); + BOOST_CHECK(!fut.available()); + gh0.release(); + BOOST_CHECK(!fut.available()); + gh1.release(); + fut.get(); +} + +SEASTAR_THREAD_TEST_CASE(gate_holder_copy_and_move_test) { + gate g0; + auto gh00 = g0.hold(); + auto gh01 = gh00; + auto fut0 = g0.close(); + BOOST_CHECK(!fut0.available()); + gate g1; + auto gh1 = g1.hold(); + auto fut1 = g1.close(); + BOOST_CHECK(!fut1.available()); + gh01.release(); + BOOST_CHECK(!fut0.available()); + BOOST_CHECK(!fut1.available()); + gh00 = std::move(gh1); + fut0.get(); + BOOST_CHECK(!fut1.available()); + gh00.release(); + fut1.get(); +} + +SEASTAR_THREAD_TEST_CASE(gate_holder_copy_after_close_test) { + gate g; + auto gh0 = g.hold(); + auto fut = g.close(); + BOOST_CHECK(g.is_closed()); + gate::holder gh1 = gh0; + BOOST_CHECK(!fut.available()); + gh0.release(); + BOOST_CHECK(!fut.available()); + gh1.release(); + fut.get(); +} + +SEASTAR_TEST_CASE(gate_holder_parallel_copy_test) { + constexpr int expected = 42; + return do_with(0, [expected] (int& count) { + return with_closeable(gate(), [&] (gate& g) { + auto gh = g.hold(); + // Copying the gate::holder in the lambda below should keep it open + // until all instances complete + (void)parallel_for_each(boost::irange(0, expected), [&count, gh = gh] (int) { + count++; + return make_ready_future<>(); + }); + return 17; + }).then([&, expected] (int res) { + // res should be returned by the function called + // by with_closeable. + BOOST_REQUIRE_EQUAL(res, 17); + // closing the gate should wait for + // all background continuations to complete + BOOST_REQUIRE_EQUAL(count, expected); + }); + }); +} diff --git a/src/seastar/tests/unit/condition_variable_test.cc b/src/seastar/tests/unit/condition_variable_test.cc new file mode 100644 index 000000000..708ed8c4a --- /dev/null +++ b/src/seastar/tests/unit/condition_variable_test.cc @@ -0,0 +1,369 @@ +/* + * 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/condition-variable.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 <seastar/core/when_all.hh> +#include <seastar/core/when_any.hh> +#include <seastar/core/with_timeout.hh> +#include <boost/range/irange.hpp> + +using namespace seastar; +using namespace std::chrono_literals; +using steady_clock = std::chrono::steady_clock; + +SEASTAR_THREAD_TEST_CASE(test_condition_variable_signal_consume) { + condition_variable cv; + + cv.signal(); + auto f = cv.wait(); + + BOOST_REQUIRE_EQUAL(f.available(), true); + f.get(); + + auto f2 = cv.wait(); + + BOOST_REQUIRE_EQUAL(f2.available(), false); + + cv.signal(); + + with_timeout(steady_clock::now() + 10ms, std::move(f2)).get(); + + std::vector<future<>> waiters; + waiters.emplace_back(cv.wait()); + waiters.emplace_back(cv.wait()); + waiters.emplace_back(cv.wait()); + + BOOST_REQUIRE_EQUAL(std::count_if(waiters.begin(), waiters.end(), std::mem_fn(&future<>::available)), 0u); + + cv.signal(); + + BOOST_REQUIRE_EQUAL(std::count_if(waiters.begin(), waiters.end(), std::mem_fn(&future<>::available)), 1u); + // FIFO + BOOST_REQUIRE_EQUAL(waiters.front().available(), true); + + cv.broadcast(); + + BOOST_REQUIRE_EQUAL(std::count_if(waiters.begin(), waiters.end(), std::mem_fn(&future<>::available)), 3u); +} + +SEASTAR_THREAD_TEST_CASE(test_condition_variable_pred) { + condition_variable cv; + bool ready = false; + + try { + cv.wait(100ms, [&] { return ready; }).get(); + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + // should not affect outcome. + cv.signal(); + + try { + cv.wait(100ms, [&] { return ready; }).get(); + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + +} + +SEASTAR_THREAD_TEST_CASE(test_condition_variable_signal_break) { + condition_variable cv; + + std::vector<future<>> waiters; + waiters.emplace_back(cv.wait()); + waiters.emplace_back(cv.wait()); + waiters.emplace_back(cv.wait()); + + BOOST_REQUIRE_EQUAL(std::count_if(waiters.begin(), waiters.end(), std::mem_fn(&future<>::available)), 0u); + + cv.broken(); + + for (auto& f : waiters) { + try { + f.get(); + } catch (broken_condition_variable&) { + // ok + continue; + } + BOOST_FAIL("should not reach"); + } + + try { + auto f = cv.wait(); + f.get(); + BOOST_FAIL("should not reach"); + } catch (broken_condition_variable&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } +} + +SEASTAR_THREAD_TEST_CASE(test_condition_variable_timeout) { + condition_variable cv; + + auto f = cv.wait(100ms); + BOOST_REQUIRE_EQUAL(f.available(), false); + + sleep(200ms).get(); + BOOST_REQUIRE_EQUAL(f.available(), true); + + try { + f.get(); + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } +} + +SEASTAR_THREAD_TEST_CASE(test_condition_variable_pred_wait) { + condition_variable cv; + + bool ready = false; + + timer<> t; + t.set_callback([&] { ready = true; cv.signal(); }); + t.arm(100ms); + + cv.wait([&] { return ready; }).get(); + + ready = false; + + try { + cv.wait(10ms, [&] { return ready; }).get(); + BOOST_FAIL("should not reach"); + } catch (timed_out_error&) { + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + + ready = true; + cv.signal(); + + cv.wait(10ms, [&] { return ready; }).get(); + + for (int i = 0; i < 2; ++i) { + ready = false; + t.set_callback([&] { cv.broadcast();}); + t.arm_periodic(10ms); + + try { + cv.wait(300ms, [&] { return ready; }).get(); + BOOST_FAIL("should not reach"); + } catch (timed_out_error&) { + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + t.cancel(); + cv.signal(); + } + + ready = true; + cv.signal(); + + cv.wait([&] { return ready; }).get(); + // signal state should remain on + cv.wait().get(); +} + +SEASTAR_THREAD_TEST_CASE(test_condition_variable_has_waiter) { + condition_variable cv; + + BOOST_REQUIRE_EQUAL(cv.has_waiters(), false); + + auto f = cv.wait(); + BOOST_REQUIRE_EQUAL(cv.has_waiters(), true); + + cv.signal(); + f.get(); + BOOST_REQUIRE_EQUAL(cv.has_waiters(), false); +} + +#ifdef SEASTAR_COROUTINES_ENABLED + +SEASTAR_TEST_CASE(test_condition_variable_signal_consume_coroutine) { + condition_variable cv; + + cv.signal(); + co_await with_timeout(steady_clock::now() + 10ms, [&]() -> future<> { + co_await cv.when(); + }()); + + try { + co_await with_timeout(steady_clock::now() + 10ms, [&]() -> future<> { + co_await cv.when(); + }()); + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + BOOST_FAIL("should not reach"); + } catch (timed_out_error&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + + try { + co_await with_timeout(steady_clock::now() + 10s, [&]() -> future<> { + co_await cv.when(100ms); + }()); + BOOST_FAIL("should not reach"); + } catch (timed_out_error&) { + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + +} + +SEASTAR_TEST_CASE(test_condition_variable_pred_when) { + condition_variable cv; + + bool ready = false; + + timer<> t; + t.set_callback([&] { ready = true; cv.signal(); }); + t.arm(100ms); + + co_await cv.when([&] { return ready; }); + + ready = false; + + try { + co_await cv.when(10ms, [&] { return ready; }); + BOOST_FAIL("should not reach"); + } catch (timed_out_error&) { + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + + ready = true; + cv.signal(); + + co_await cv.when(10ms, [&] { return ready; }); + + for (int i = 0; i < 2; ++i) { + ready = false; + t.set_callback([&] { cv.broadcast();}); + t.arm_periodic(10ms); + + try { + co_await cv.when(300ms, [&] { return ready; }); + BOOST_FAIL("should not reach"); + } catch (timed_out_error&) { + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + // ok + } catch (...) { + BOOST_FAIL("should not reach"); + } + t.cancel(); + cv.signal(); + } + + ready = true; + cv.signal(); + + co_await cv.when([&] { return ready; }); + // signal state should remain on + co_await cv.when(); +} + + + +SEASTAR_TEST_CASE(test_condition_variable_when_signal) { + condition_variable cv; + + bool ready = false; + + timer<> t; + t.set_callback([&] { cv.signal(); ready = true; }); + t.arm(100ms); + + co_await cv.when(); + // ensure we did not resume before timer ran fully + BOOST_REQUIRE_EQUAL(ready, true); +} + +SEASTAR_TEST_CASE(test_condition_variable_when_timeout) { + condition_variable cv; + + bool ready = false; + + // create "background" fiber + auto f = [&]() -> future<> { + try { + co_await cv.when(100ms, [&] { return ready; }); + } catch (timed_out_error&) { + BOOST_FAIL("should not reach"); + } catch (condition_variable_timed_out&) { + BOOST_FAIL("should not reach"); + } catch (...) { + BOOST_FAIL("should not reach"); + } + }(); + + // ensure we wake up waiter before timeuot + ready = true; + cv.signal(); + + // now busy-spin until the timer should be expired + while (cv.has_waiters()) { + } + + // he should not have run yet... + BOOST_REQUIRE_EQUAL(f.available(), false); + // now, if the code is broken, the timer will run once we switch out, + // and cause the wait to time out, even though it did not. -> assert + + co_await std::move(f); +} + +#endif diff --git a/src/seastar/tests/unit/connect_test.cc b/src/seastar/tests/unit/connect_test.cc new file mode 100644 index 000000000..be15ef568 --- /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("127.0.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/content_source_test.cc b/src/seastar/tests/unit/content_source_test.cc new file mode 100644 index 000000000..daeff8348 --- /dev/null +++ b/src/seastar/tests/unit/content_source_test.cc @@ -0,0 +1,174 @@ +/* + * 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/sstring.hh> +#include <seastar/core/thread.hh> +#include <seastar/core/temporary_buffer.hh> +#include <seastar/http/internal/content_source.hh> +#include <seastar/testing/test_case.hh> +#include <tuple> + +using namespace seastar; + +class buf_source_impl : public data_source_impl { + temporary_buffer<char> _tmp; +public: + buf_source_impl(sstring str) : _tmp(str.c_str(), str.size()) {}; + virtual future<temporary_buffer<char>> get() override { + if (_tmp.empty()) { + return make_ready_future<temporary_buffer<char>>(); + } + return make_ready_future<temporary_buffer<char>>(std::move(_tmp)); + } + virtual future<temporary_buffer<char>> skip(uint64_t n) override { + _tmp.trim_front(std::min(_tmp.size(), n)); + return make_ready_future<temporary_buffer<char>>(); + } +}; + +SEASTAR_TEST_CASE(test_incomplete_content) { + return seastar::async([] { + auto inp = input_stream<char>(data_source(std::make_unique<buf_source_impl>(sstring("asdfghjkl;")))); + auto content_strm = input_stream<char>(data_source(std::make_unique<httpd::internal::content_length_source_impl>(inp, 20))); + + auto content1 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("asdfghjkl;", 10) == content1); + auto content2 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>() == content2); + BOOST_REQUIRE(content_strm.eof()); + BOOST_REQUIRE(inp.eof()); + + inp = input_stream<char>(data_source(std::make_unique<buf_source_impl>(sstring("4\r\n132")))); + std::unordered_map<sstring, sstring> tmp, tmp2; + content_strm = input_stream<char>(data_source(std::make_unique<httpd::internal::chunked_source_impl>(inp, tmp, tmp2))); + + content1 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("132", 3) == content1); + content2 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>() == content2); + BOOST_REQUIRE(content_strm.eof()); + }); +} + +SEASTAR_TEST_CASE(test_complete_content) { + return seastar::async([] { + auto inp = input_stream<char>(data_source(std::make_unique<buf_source_impl>(sstring("asdfghjkl;1234567890")))); + auto content_strm = input_stream<char>(data_source(std::make_unique<httpd::internal::content_length_source_impl>(inp, 20))); + + auto content1 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("asdfghjkl;1234567890", 20) == content1); + auto content2 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>() == content2); + BOOST_REQUIRE(content_strm.eof()); + + inp = input_stream<char>(data_source(std::make_unique<buf_source_impl>(sstring("4\r\n1324\r\n0\r\n\r\n")))); + std::unordered_map<sstring, sstring> tmp, tmp2; + content_strm = input_stream<char>(data_source(std::make_unique<httpd::internal::chunked_source_impl>(inp, tmp, tmp2))); + + content1 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("1324", 4) == content1); + content2 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>() == content2); + BOOST_REQUIRE(content_strm.eof()); + }); +} + +SEASTAR_TEST_CASE(test_more_than_requests_content) { + return seastar::async([] { + auto inp = input_stream<char>(data_source(std::make_unique<buf_source_impl>(sstring("asdfghjkl;1234567890xyz")))); + auto content_strm = input_stream<char>(data_source(std::make_unique<httpd::internal::content_length_source_impl>(inp, 20))); + + auto content1 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("asdfghjkl;1234567890", 20) == content1); + auto content2 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>() == content2); + BOOST_REQUIRE(content_strm.eof()); + auto content3 = inp.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("xyz", 3) == content3); + + inp = input_stream<char>(data_source(std::make_unique<buf_source_impl>(sstring("4\r\n1324\r\n0\r\n\r\nxyz")))); + std::unordered_map<sstring, sstring> tmp, tmp2; + content_strm = input_stream<char>(data_source(std::make_unique<httpd::internal::chunked_source_impl>(inp, tmp, tmp2))); + + content1 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("1324", 4) == content1); + content2 = content_strm.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>() == content2); + BOOST_REQUIRE(content_strm.eof()); + content3 = inp.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>("xyz", 3) == content3); + }); +} + +class single_bytes_source_impl : public data_source_impl { + temporary_buffer<char> _tmp; +public: + single_bytes_source_impl(temporary_buffer<char> tmp) + : _tmp(std::move(tmp)) { + } + virtual future<temporary_buffer<char>> get() override { + if (_tmp.empty()) { + return make_ready_future<temporary_buffer<char>>(); + } + auto byte = _tmp.share(0, 1); + _tmp.trim_front(1); + return make_ready_future<temporary_buffer<char>>(std::move(byte)); + } + virtual future<temporary_buffer<char>> skip(uint64_t n) override { + _tmp.trim_front(std::min(_tmp.size(), n)); + return make_ready_future<temporary_buffer<char>>(); + } +}; + +SEASTAR_TEST_CASE(test_single_bytes_source) { + return seastar::async([] { + sstring input_str = "test input"; + auto ds = data_source(std::make_unique<single_bytes_source_impl>(temporary_buffer<char>(input_str.c_str(), input_str.size()))); + for (auto& ch : input_str) { + temporary_buffer<char> one_letter_buf(1); + *one_letter_buf.get_write() = ch; + auto get_buf = ds.get().get0(); + BOOST_REQUIRE(one_letter_buf == get_buf); + } + }); +} + +SEASTAR_TEST_CASE(test_fragmented_chunks) { + // Test if a message that cannot be parsed as a http request is being replied with a 400 Bad Request response + return seastar::async([] { + sstring request_string = "a;chunk=ext\r\n1234567890\r\n0\r\ntrailer: part\r\n\r\n"; + auto inp = input_stream<char>(data_source(std::make_unique<single_bytes_source_impl>(temporary_buffer<char>(request_string.c_str(), request_string.size())))); + std::unordered_map<sstring, sstring> chunk_extensions; + std::unordered_map<sstring, sstring> trailing_headers; + auto content_stream = input_stream<char>(data_source(std::make_unique<httpd::internal::chunked_source_impl>(inp, chunk_extensions, trailing_headers))); + for (auto& ch : sstring("1234567890")) { + temporary_buffer<char> one_letter_buf(1); + *one_letter_buf.get_write() = ch; + auto read_buf = content_stream.read().get0(); + BOOST_REQUIRE(one_letter_buf == read_buf); + } + auto read_buf = content_stream.read().get0(); + BOOST_REQUIRE(temporary_buffer<char>() == read_buf); + BOOST_REQUIRE(chunk_extensions[sstring("chunk")] == sstring("ext")); + BOOST_REQUIRE(trailing_headers[sstring("trailer")] == sstring("part")); + }); +}
\ No newline at end of file diff --git a/src/seastar/tests/unit/coroutines_test.cc b/src/seastar/tests/unit/coroutines_test.cc new file mode 100644 index 000000000..1d1382a2c --- /dev/null +++ b/src/seastar/tests/unit/coroutines_test.cc @@ -0,0 +1,925 @@ +/* + * 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 <exception> +#include <numeric> +#include <ranges> + +#include <seastar/core/circular_buffer.hh> +#include <seastar/core/future-util.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/core/sleep.hh> +#include <seastar/util/later.hh> +#include <seastar/core/thread.hh> +#include <seastar/testing/random.hh> + +using namespace seastar; +using namespace std::chrono_literals; + +#ifndef SEASTAR_COROUTINES_ENABLED + +SEASTAR_TEST_CASE(test_coroutines_not_compiled_in) { + return make_ready_future<>(); +} + +#else + +#include <seastar/core/coroutine.hh> +#include <seastar/coroutine/all.hh> +#include <seastar/coroutine/maybe_yield.hh> +#include <seastar/coroutine/switch_to.hh> +#include <seastar/coroutine/parallel_for_each.hh> +#include <seastar/coroutine/as_future.hh> +#include <seastar/coroutine/exception.hh> +#include <seastar/coroutine/generator.hh> + +namespace { + +future<int> old_fashioned_continuations() { + return yield().then([] { + return 42; + }); +} + +future<int> simple_coroutine() { + co_await yield(); + 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 yield(); + throw 42; +} + +[[gnu::noinline]] int throw_exception(int x) { + throw x; +} + +future<int> failing_coroutine2() noexcept { + co_await yield(); + co_return throw_exception(17); +} + +} + +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; }); + BOOST_CHECK_EQUAL(co_await failing_coroutine().then_wrapped([] (future<int> f) -> future<int> { + BOOST_REQUIRE(f.failed()); + try { + std::rethrow_exception(f.get_exception()); + } catch (int v) { + co_return v; + } + }), 42); + BOOST_REQUIRE_EXCEPTION((void)co_await failing_coroutine2(), int, [] (auto v) { return v == 17; }); + BOOST_CHECK_EQUAL(co_await failing_coroutine2().then_wrapped([] (future<int> f) -> future<int> { + BOOST_REQUIRE(f.failed()); + try { + std::rethrow_exception(f.get_exception()); + } catch (int v) { + co_return v; + } + }), 17); +} + +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); + std::exception_ptr ex; + + try { + 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()); + } catch (...) { + ex = std::current_exception(); + } + co_await destroy_scheduling_group(other_sg); + if (ex) { + std::rethrow_exception(std::move(ex)); + } +} + +future<scheduling_group> switch_to_with_context(scheduling_group& sg) { + scheduling_group new_sg = co_await coroutine::switch_to(sg); + BOOST_REQUIRE(current_scheduling_group() == sg); + co_return new_sg; +} + +SEASTAR_TEST_CASE(test_switch_to) { + auto other_sg0 = co_await create_scheduling_group("other group 0", 10.f); + auto other_sg1 = co_await create_scheduling_group("other group 1", 10.f); + auto other_sg2 = co_await create_scheduling_group("other group 2", 10.f); + std::exception_ptr ex; + + try { + auto base_sg = current_scheduling_group(); + + auto prev_sg = co_await coroutine::switch_to(other_sg0); + BOOST_REQUIRE(current_scheduling_group() == other_sg0); + BOOST_REQUIRE(prev_sg == base_sg); + + auto same_sg = co_await coroutine::switch_to(other_sg0); + BOOST_REQUIRE(current_scheduling_group() == other_sg0); + BOOST_REQUIRE(same_sg == other_sg0); + + auto nested_sg = co_await coroutine::switch_to(other_sg1); + BOOST_REQUIRE(current_scheduling_group() == other_sg1); + BOOST_REQUIRE(nested_sg == other_sg0); + + co_await switch_to_with_context(other_sg2); + + co_await coroutine::switch_to(base_sg); + BOOST_REQUIRE(current_scheduling_group() == base_sg); + } catch (...) { + ex = std::current_exception(); + } + + co_await destroy_scheduling_group(other_sg1); + co_await destroy_scheduling_group(other_sg0); + if (ex) { + std::rethrow_exception(std::move(ex)); + } +} + +future<> check_thread_inherits_sg_from_coroutine_frame(scheduling_group expected_sg) { + return seastar::async([expected_sg] { + BOOST_REQUIRE(current_scheduling_group() == expected_sg); + }); +} + +future<> check_coroutine_inherits_sg_from_another_one(scheduling_group expected_sg) { + co_await yield(); + BOOST_REQUIRE(current_scheduling_group() == expected_sg); +} + +future<> switch_to_sg_and_perform_inheriting_checks(scheduling_group base_sg, scheduling_group new_sg) { + BOOST_REQUIRE(current_scheduling_group() == base_sg); + co_await coroutine::switch_to(new_sg); + BOOST_REQUIRE(current_scheduling_group() == new_sg); + + co_await check_thread_inherits_sg_from_coroutine_frame(new_sg); + co_await check_coroutine_inherits_sg_from_another_one(new_sg); + + // don't restore previous sg on purpose, expecting it will be restored once coroutine goes out of scope +} + +SEASTAR_TEST_CASE(test_switch_to_sg_restoration_and_inheriting) { + auto new_sg = co_await create_scheduling_group("other group 0", 10.f); + std::exception_ptr ex; + + try { + auto base_sg = current_scheduling_group(); + + co_await switch_to_sg_and_perform_inheriting_checks(base_sg, new_sg); + // seastar automatically restores base_sg once it goes out of coroutine frame + BOOST_REQUIRE(current_scheduling_group() == base_sg); + + co_await seastar::async([base_sg, new_sg] { + switch_to_sg_and_perform_inheriting_checks(base_sg, new_sg).get(); + BOOST_REQUIRE(current_scheduling_group() == base_sg); + }); + + co_await switch_to_sg_and_perform_inheriting_checks(base_sg, new_sg).finally([base_sg] { + BOOST_REQUIRE(current_scheduling_group() == base_sg); + }); + } catch (...) { + ex = std::current_exception(); + } + + co_await destroy_scheduling_group(new_sg); + if (ex) { + std::rethrow_exception(std::move(ex)); + } +} + +SEASTAR_TEST_CASE(test_preemption) { + bool x = false; + unsigned preempted = 0; + auto f = yield().then([&x] { + x = true; + }); + + // try to preempt 1000 times. 1 should be enough if not for + // task queue shaffling in debug mode which may cause co-routine + // continuation to run first. + while(preempted < 1000 && !x) { + preempted += need_preempt(); + co_await make_ready_future<>(); + } + auto save_x = x; + // wait for yield() to complete + co_await std::move(f); + BOOST_REQUIRE(save_x); + co_return; +} + +SEASTAR_TEST_CASE(test_no_preemption) { + bool x = false; + unsigned preempted = 0; + auto f = yield().then([&x] { + x = true; + }); + + // preemption should not happen, we explicitly asked for continuing if possible + while(preempted < 1000 && !x) { + preempted += need_preempt(); + co_await coroutine::without_preemption_check(make_ready_future<>()); + } + auto save_x = x; + // wait for yield() to complete + co_await std::move(f); + BOOST_REQUIRE(!save_x); + co_return; +} + +SEASTAR_TEST_CASE(test_all_simple) { + auto [a, b] = co_await coroutine::all( + [] { return make_ready_future<int>(1); }, + [] { return make_ready_future<int>(2); } + ); + BOOST_REQUIRE_EQUAL(a, 1); + BOOST_REQUIRE_EQUAL(b, 2); +} + +SEASTAR_TEST_CASE(test_all_permutations) { + std::vector<std::chrono::milliseconds> delays = { 0ms, 0ms, 2ms, 2ms, 4ms, 6ms }; + auto make_delayed_future_returning_nr = [&] (int nr) { + return [=] { + auto delay = delays[nr]; + return delay == 0ms ? make_ready_future<int>(nr) : sleep(delay).then([nr] { return make_ready_future<int>(nr); }); + }; + }; + do { + auto [a, b, c, d, e, f] = co_await coroutine::all( + make_delayed_future_returning_nr(0), + make_delayed_future_returning_nr(1), + make_delayed_future_returning_nr(2), + make_delayed_future_returning_nr(3), + make_delayed_future_returning_nr(4), + make_delayed_future_returning_nr(5) + ); + BOOST_REQUIRE_EQUAL(a, 0); + BOOST_REQUIRE_EQUAL(b, 1); + BOOST_REQUIRE_EQUAL(c, 2); + BOOST_REQUIRE_EQUAL(d, 3); + BOOST_REQUIRE_EQUAL(e, 4); + BOOST_REQUIRE_EQUAL(f, 5); + } while (std::ranges::next_permutation(delays).found); +} + +SEASTAR_TEST_CASE(test_all_ready_exceptions) { + try { + co_await coroutine::all( + [] () -> future<> { throw 1; }, + [] () -> future<> { throw 2; } + ); + } catch (int e) { + BOOST_REQUIRE(e == 1 || e == 2); + } +} + +SEASTAR_TEST_CASE(test_all_nonready_exceptions) { + try { + co_await coroutine::all( + [] () -> future<> { + co_await sleep(1ms); + throw 1; + }, + [] () -> future<> { + co_await sleep(1ms); + throw 2; + } + ); + } catch (int e) { + BOOST_REQUIRE(e == 1 || e == 2); + } +} + +SEASTAR_TEST_CASE(test_all_heterogeneous_types) { + auto [a, b] = co_await coroutine::all( + [] () -> future<int> { + co_await sleep(1ms); + co_return 1; + }, + [] () -> future<> { + co_await sleep(1ms); + }, + [] () -> future<long> { + co_await sleep(1ms); + co_return 2L; + } + ); + BOOST_REQUIRE_EQUAL(a, 1); + BOOST_REQUIRE_EQUAL(b, 2L); +} + +SEASTAR_TEST_CASE(test_all_noncopyable_types) { + auto [a] = co_await coroutine::all( + [] () -> future<std::unique_ptr<int>> { + co_return std::make_unique<int>(6); + } + ); + BOOST_REQUIRE_EQUAL(*a, 6); +} + +SEASTAR_TEST_CASE(test_all_throw_in_input_func) { + int nr_completed = 0; + bool exception_seen = false; + try { + co_await coroutine::all( + [&] () -> future<int> { + co_await sleep(1ms); + ++nr_completed; + co_return 7; + }, + [&] () -> future<int> { + throw 9; + }, + [&] () -> future<int> { + co_await sleep(1ms); + ++nr_completed; + co_return 7; + } + ); + } catch (int n) { + BOOST_REQUIRE_EQUAL(n, 9); + exception_seen = true; + } + BOOST_REQUIRE_EQUAL(nr_completed, 2); + BOOST_REQUIRE(exception_seen); +} + +struct counter_ref { +private: + int& _counter; + +public: + explicit counter_ref(int& cnt) + : _counter(cnt) { + ++_counter; + } + + ~counter_ref() { + --_counter; + } +}; + +template<typename Ex> +static future<> check_coroutine_throws(auto fun) { + // The counter keeps track of the number of alive "counter_ref" objects. + // If it is not zero then it means that some destructors weren't run + // while quitting the coroutine. + int counter = 0; + BOOST_REQUIRE_THROW(co_await fun(counter), Ex); + BOOST_REQUIRE_EQUAL(counter, 0); + co_await fun(counter).then_wrapped([&counter] (auto f) { + BOOST_REQUIRE(f.failed()); + BOOST_REQUIRE_THROW(std::rethrow_exception(f.get_exception()), Ex); + BOOST_REQUIRE_EQUAL(counter, 0); + }); +} + +SEASTAR_TEST_CASE(test_coroutine_exception) { + co_await check_coroutine_throws<std::runtime_error>([] (int& counter) -> future<int> { + counter_ref ref{counter}; + co_return coroutine::exception(std::make_exception_ptr(std::runtime_error("threw"))); + }); + co_await check_coroutine_throws<std::runtime_error>([] (int& counter) -> future<int> { + counter_ref ref{counter}; + co_await coroutine::exception(std::make_exception_ptr(std::runtime_error("threw"))); + co_return 42; + }); + co_await check_coroutine_throws<std::logic_error>([] (int& counter) -> future<> { + counter_ref ref{counter}; + co_await coroutine::return_exception(std::logic_error("threw")); + co_return; + }); + co_await check_coroutine_throws<int>([] (int& counter) -> future<> { + counter_ref ref{counter}; + co_await coroutine::return_exception(42); + co_return; + }); +} + +SEASTAR_TEST_CASE(test_coroutine_return_exception_ptr) { + co_await check_coroutine_throws<std::runtime_error>([] (int& counter) -> future<> { + co_await coroutine::return_exception(std::runtime_error("threw")); + }); + co_await check_coroutine_throws<std::runtime_error>([] (int& counter) -> future<> { + auto ex = std::make_exception_ptr(std::runtime_error("threw")); + co_await coroutine::return_exception_ptr(std::move(ex)); + }); + co_await check_coroutine_throws<std::runtime_error>([] (int& counter) -> future<> { + auto ex = std::make_exception_ptr(std::runtime_error("threw")); + co_await coroutine::return_exception_ptr(ex); + }); + co_await check_coroutine_throws<int>([] (int& counter) -> future<> { + co_await coroutine::return_exception_ptr(std::make_exception_ptr(3)); + }); +} + +SEASTAR_TEST_CASE(test_maybe_yield) { + int var = 0; + bool done = false; + auto spinner = [&] () -> future<> { + // increment a variable continuously, but yield so an observer can see it. + while (!done) { + ++var; + co_await coroutine::maybe_yield(); + } + }; + auto spinner_fut = spinner(); + int snapshot = var; + for (int nr_changes = 0; nr_changes < 10; ++nr_changes) { + // Try to observe the value changing in time, yield to + // allow the spinner to advance it. + while (snapshot == var) { + co_await coroutine::maybe_yield(); + } + snapshot = var; + } + done = true; + co_await std::move(spinner_fut); + BOOST_REQUIRE(true); // the test will hang if it doesn't work. +} + +#if __has_include(<coroutine>) && !defined(__clang__) + +#include "tl-generator.hh" +tl::generator<int> simple_generator(int max) +{ + for (int i = 0; i < max; ++i) { + co_yield i; + } +} + +SEASTAR_TEST_CASE(generator) +{ + // test ability of seastar::parallel_for_each to deal with move-only views + int accum = 0; + co_await seastar::parallel_for_each(simple_generator(10), [&](int i) { + accum += i; + return seastar::make_ready_future<>(); + }); + BOOST_REQUIRE_EQUAL(accum, 45); + + // test ability of seastar::max_concurrent_for_each to deal with move-only views + accum = 0; + co_await seastar::max_concurrent_for_each(simple_generator(10), 10, [&](int i) { + accum += i; + return seastar::make_ready_future<>(); + }); + BOOST_REQUIRE_EQUAL(accum, 45); +} + +#endif + +SEASTAR_TEST_CASE(test_parallel_for_each_empty) { + std::vector<int> values; + int count = 0; + + co_await coroutine::parallel_for_each(values, [&] (int x) { + ++count; + }); + BOOST_REQUIRE_EQUAL(count, 0); // the test will hang if it doesn't work. +} + +SEASTAR_TEST_CASE(test_parallel_for_each_exception) { + std::array<int, 5> values = { 10, 2, 1, 4, 8 }; + int count = 0; + auto& eng = testing::local_random_engine; + auto dist = std::uniform_int_distribution<unsigned>(); + int throw_at = dist(eng) % values.size(); + + BOOST_TEST_MESSAGE(fmt::format("Will throw at value #{}/{}", throw_at, values.size())); + + auto f0 = coroutine::parallel_for_each(values, [&] (int x) { + if (count++ == throw_at) { + BOOST_TEST_MESSAGE("throw"); + throw std::runtime_error("test"); + } + }); + // An exception thrown by the functor must be propagated + BOOST_REQUIRE_THROW(co_await std::move(f0), std::runtime_error); + // Functor must be called on all values, even if there's an exception + BOOST_REQUIRE_EQUAL(count, values.size()); + + count = 0; + throw_at = dist(eng) % values.size(); + BOOST_TEST_MESSAGE(fmt::format("Will throw at value #{}/{}", throw_at, values.size())); + + auto f1 = coroutine::parallel_for_each(values, [&] (int x) -> future<> { + co_await sleep(std::chrono::milliseconds(x)); + if (count++ == throw_at) { + throw std::runtime_error("test"); + } + }); + BOOST_REQUIRE_THROW(co_await std::move(f1), std::runtime_error); + BOOST_REQUIRE_EQUAL(count, values.size()); +} + +SEASTAR_TEST_CASE(test_parallel_for_each) { + std::vector<int> values = { 3, 1, 4 }; + int sum_of_squares = 0; + + int expected = std::accumulate(values.begin(), values.end(), 0, [] (int sum, int x) { + return sum + x * x; + }); + + // Test all-ready futures + co_await coroutine::parallel_for_each(values, [&sum_of_squares] (int x) { + sum_of_squares += x * x; + }); + BOOST_REQUIRE_EQUAL(sum_of_squares, expected); + + // Test non-ready futures + sum_of_squares = 0; + co_await coroutine::parallel_for_each(values, [&sum_of_squares] (int x) -> future<> { + if (x > 1) { + co_await sleep(std::chrono::milliseconds(x)); + } + sum_of_squares += x * x; + }); + BOOST_REQUIRE_EQUAL(sum_of_squares, expected); + + // Test legacy subrange + sum_of_squares = 0; + co_await coroutine::parallel_for_each(values.begin(), values.end() - 1, [&sum_of_squares] (int x) -> future<> { + if (x > 1) { + co_await sleep(std::chrono::milliseconds(x)); + } + sum_of_squares += x * x; + }); + BOOST_REQUIRE_EQUAL(sum_of_squares, 10); + + // clang 13.0.1 doesn't support subrange + // so provide also a Iterator/Sentinel based constructor. + // See https://github.com/llvm/llvm-project/issues/46091 +#ifndef __clang__ + // Test std::ranges::subrange + sum_of_squares = 0; + co_await coroutine::parallel_for_each(std::ranges::subrange(values.begin(), values.end() - 1), [&sum_of_squares] (int x) -> future<> { + if (x > 1) { + co_await sleep(std::chrono::milliseconds(x)); + } + sum_of_squares += x * x; + }); + BOOST_REQUIRE_EQUAL(sum_of_squares, 10); +#endif +} + +SEASTAR_TEST_CASE(test_void_as_future) { + auto f = co_await coroutine::as_future(make_ready_future<>()); + BOOST_REQUIRE(f.available()); + + f = co_await coroutine::as_future(make_exception_future<>(std::runtime_error("exception"))); + BOOST_REQUIRE_THROW(f.get(), std::runtime_error); + + semaphore sem(0); + (void)sleep(1ms).then([&] { sem.signal(); }); + f = co_await coroutine::as_future(sem.wait()); + BOOST_REQUIRE(f.available()); + + f = co_await coroutine::as_future(sem.wait(duration_cast<semaphore::duration>(1ms))); + BOOST_REQUIRE_THROW(f.get(), semaphore_timed_out); +} + +SEASTAR_TEST_CASE(test_void_as_future_without_preemption_check) { + auto f = co_await coroutine::as_future_without_preemption_check(make_ready_future<>()); + BOOST_REQUIRE(f.available()); + + f = co_await coroutine::as_future_without_preemption_check(make_exception_future<>(std::runtime_error("exception"))); + BOOST_REQUIRE_THROW(f.get(), std::runtime_error); + + semaphore sem(0); + (void)sleep(1ms).then([&] { sem.signal(); }); + f = co_await coroutine::as_future_without_preemption_check(sem.wait()); + BOOST_REQUIRE(f.available()); + + f = co_await coroutine::as_future_without_preemption_check(sem.wait(duration_cast<semaphore::duration>(1ms))); + BOOST_REQUIRE_THROW(f.get(), semaphore_timed_out); +} + +SEASTAR_TEST_CASE(test_non_void_as_future) { + auto f = co_await coroutine::as_future(make_ready_future<int>(42)); + BOOST_REQUIRE_EQUAL(f.get0(), 42); + + f = co_await coroutine::as_future(make_exception_future<int>(std::runtime_error("exception"))); + BOOST_REQUIRE_THROW(f.get(), std::runtime_error); + + auto p = promise<int>(); + (void)sleep(1ms).then([&] { p.set_value(314); }); + f = co_await coroutine::as_future(p.get_future()); + BOOST_REQUIRE_EQUAL(f.get0(), 314); + + auto gen_exception = [] () -> future<int> { + co_await sleep(1ms); + co_return coroutine::exception(std::make_exception_ptr(std::runtime_error("exception"))); + }; + f = co_await coroutine::as_future(gen_exception()); + BOOST_REQUIRE_THROW(f.get(), std::runtime_error); +} + +SEASTAR_TEST_CASE(test_non_void_as_future_without_preemption_check) { + auto f = co_await coroutine::as_future_without_preemption_check(make_ready_future<int>(42)); + BOOST_REQUIRE_EQUAL(f.get0(), 42); + + f = co_await coroutine::as_future_without_preemption_check(make_exception_future<int>(std::runtime_error("exception"))); + BOOST_REQUIRE_THROW(f.get(), std::runtime_error); + + auto p = promise<int>(); + (void)sleep(1ms).then([&] { p.set_value(314); }); + f = co_await coroutine::as_future_without_preemption_check(p.get_future()); + BOOST_REQUIRE_EQUAL(f.get0(), 314); + + auto gen_exception = [] () -> future<int> { + co_await sleep(1ms); + co_return coroutine::exception(std::make_exception_ptr(std::runtime_error("exception"))); + }; + f = co_await coroutine::as_future_without_preemption_check(gen_exception()); + BOOST_REQUIRE_THROW(f.get(), std::runtime_error); +} + +SEASTAR_TEST_CASE(test_as_future_preemption) { + bool stop = false; + + auto get_ready_future = [&] { + return stop ? make_exception_future<>(std::runtime_error("exception")) : make_ready_future<>(); + }; + + auto wait_for_stop = [&] () -> future<bool> { + for (;;) { + auto f = co_await coroutine::as_future(get_ready_future()); + if (f.failed()) { + co_return coroutine::exception(f.get_exception()); + } + } + }; + + auto f0 = wait_for_stop(); + + auto set_stop = [&] () -> future<> { + for (;;) { + stop = true; + if (f0.available()) { + co_return; + } + co_await coroutine::maybe_yield(); + } + }; + + co_await set_stop(); + + BOOST_REQUIRE_THROW(f0.get(), std::runtime_error); +} + +#ifndef __clang__ + +template<template<typename> class Container> +coroutine::experimental::generator<int, Container> +fibonacci_sequence(coroutine::experimental::buffer_size_t size, unsigned count) { + auto a = 0, b = 1; + for (unsigned i = 0; i < count; ++i) { + if (std::numeric_limits<decltype(a)>::max() - a < b) { + throw std::out_of_range( + fmt::format("fibonacci[{}] is greater than the largest value of int", i)); + } + co_yield std::exchange(a, std::exchange(b, a + b)); + } +} + +template<template<typename> class Container> +seastar::future<> test_async_generator_drained() { + auto expected_fibs = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}; + auto fib = fibonacci_sequence<Container>(coroutine::experimental::buffer_size_t{2}, + std::size(expected_fibs)); + for (auto expected_fib : expected_fibs) { + auto actual_fib = co_await fib(); + BOOST_REQUIRE(actual_fib.has_value()); + BOOST_REQUIRE_EQUAL(actual_fib.value(), expected_fib); + } + auto sentinel = co_await fib(); + BOOST_REQUIRE(!sentinel.has_value()); +} + +template<typename T> +using buffered_container = circular_buffer<T>; + +SEASTAR_TEST_CASE(test_async_generator_drained_buffered) { + return test_async_generator_drained<buffered_container>(); +} + +SEASTAR_TEST_CASE(test_async_generator_drained_unbuffered) { + return test_async_generator_drained<std::optional>(); +} + +template<template<typename> class Container> +seastar::future<> test_async_generator_not_drained() { + auto fib = fibonacci_sequence<Container>(coroutine::experimental::buffer_size_t{2}, + 42); + auto actual_fib = co_await fib(); + BOOST_REQUIRE(actual_fib.has_value()); + BOOST_REQUIRE_EQUAL(actual_fib.value(), 0); +} + +SEASTAR_TEST_CASE(test_async_generator_not_drained_buffered) { + return test_async_generator_not_drained<buffered_container>(); +} + +SEASTAR_TEST_CASE(test_async_generator_not_drained_unbuffered) { + return test_async_generator_not_drained<std::optional>(); +} + +struct counter_t { + int n; + int* count; + counter_t(counter_t&& other) noexcept + : n{std::exchange(other.n, -1)}, + count{std::exchange(other.count, nullptr)} + {} + counter_t(int n, int* count) noexcept + : n{n}, count{count} { + ++(*count); + } + ~counter_t() noexcept { + if (count) { + --(*count); + } + } +}; +std::ostream& operator<<(std::ostream& os, const counter_t& c) { + return os << c.n; +} + +template<template<typename> class Container> +coroutine::experimental::generator<counter_t, Container> +fiddle(coroutine::experimental::buffer_size_t size, int n, int* total) { + int i = 0; + while (true) { + if (i++ == n) { + throw std::invalid_argument("Eureka from generator!"); + } + co_yield counter_t{i, total}; + } +} + +template<template<typename> class Container> +seastar::future<> test_async_generator_throws_from_generator() { + int total = 0; + auto count_to = [total=&total](unsigned n) -> seastar::future<> { + auto count = fiddle<Container>(coroutine::experimental::buffer_size_t{2}, + n, total); + for (unsigned i = 0; i < 2 * n; i++) { + co_await count(); + } + }; + co_await count_to(42).then_wrapped([&total] (auto f) { + BOOST_REQUIRE(f.failed()); + BOOST_REQUIRE_THROW(std::rethrow_exception(f.get_exception()), std::invalid_argument); + BOOST_REQUIRE_EQUAL(total, 0); + }); +} + +SEASTAR_TEST_CASE(test_async_generator_throws_from_generator_buffered) { + return test_async_generator_throws_from_generator<buffered_container>(); +} + +SEASTAR_TEST_CASE(test_async_generator_throws_from_generator_unbuffered) { + return test_async_generator_throws_from_generator<std::optional>(); +} + +template<template<typename> class Container> +seastar::future<> test_async_generator_throws_from_consumer() { + int total = 0; + auto count_to = [total=&total](unsigned n) -> seastar::future<> { + auto count = fiddle<Container>(coroutine::experimental::buffer_size_t{2}, + n, total); + for (unsigned i = 0; i < n; i++) { + if (i == n / 2) { + throw std::invalid_argument("Eureka from consumer!"); + } + co_await count(); + } + }; + co_await count_to(42).then_wrapped([&total] (auto f) { + BOOST_REQUIRE(f.failed()); + BOOST_REQUIRE_THROW(std::rethrow_exception(f.get_exception()), std::invalid_argument); + BOOST_REQUIRE_EQUAL(total, 0); + }); +} + +SEASTAR_TEST_CASE(test_async_generator_throws_from_consumer_buffered) { + return test_async_generator_throws_from_consumer<buffered_container>(); +} + +SEASTAR_TEST_CASE(test_async_generator_throws_from_consumer_unbuffered) { + return test_async_generator_throws_from_consumer<std::optional>(); +} + +#endif + +SEASTAR_TEST_CASE(test_lambda_coroutine_in_continuation) { + auto dist = std::uniform_real_distribution<>(0.0, 1.0); + auto rand_eng = std::default_random_engine(std::random_device()()); + double n = dist(rand_eng); + auto sin1 = std::sin(n); // avoid optimizer tricks + auto boo = std::array<char, 1025>(); // bias coroutine size towards 1024 size class + auto sin2 = co_await yield().then(coroutine::lambda([n, boo] () -> future<double> { + // Expect coroutine capture to be freed after co_await without coroutine::lambda + co_await yield(); + // Try to overwrite recently-release coroutine frame by allocating in similar size-class + std::vector<char*> garbage; + for (size_t sz = 1024; sz < 2048; ++sz) { + for (int ctr = 0; ctr < 100; ++ctr) { + auto p = static_cast<char*>(malloc(sz)); + std::memset(p, 0, sz); + garbage.push_back(p); + } + } + for (auto p : garbage) { + std::free(p); + } + (void)boo; + co_return std::sin(n); + })); + BOOST_REQUIRE_EQUAL(sin1, sin2); +} + +#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..e2fc7a482 --- /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([&] () noexcept { + ran = true; + }); + d.cancel(); + } + BOOST_REQUIRE(!ran); +} + +BOOST_AUTO_TEST_CASE(test_defer_runs) { + bool ran = false; + { + auto d = defer([&] () noexcept { + ran = true; + }); + } + BOOST_REQUIRE(ran); +} + +BOOST_AUTO_TEST_CASE(test_defer_runs_once_when_moved) { + int ran = 0; + { + auto d = defer([&] () noexcept { + ++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([&] () noexcept { + ++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..c817e791c --- /dev/null +++ b/src/seastar/tests/unit/distributed_test.cc @@ -0,0 +1,442 @@ +/* + * 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 <seastar/util/closeable.hh> +#include <seastar/util/later.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_map_reduce_lifetime) { + struct map { + bool destroyed = false; + ~map() { + destroyed = true; + } + auto operator()(const X& x) { + return yield().then([this, &x] { + BOOST_REQUIRE(!destroyed); + return x.cpu_id_squared(); + }); + } + }; + struct reduce { + long& res; + bool destroyed = false; + ~reduce() { + destroyed = true; + } + auto operator()(int x) { + return yield().then([this, x] { + BOOST_REQUIRE(!destroyed); + res += x; + }); + } + }; + return do_with_distributed<X>([] (distributed<X>& x) { + return x.start().then([&x] { + return do_with(0L, [&x] (auto& result) { + return x.map_reduce(reduce{result}, map{}).then([&result] { + long n = smp::count - 1; + long expected = (n * (n + 1) * (2*n + 1)) / 6; + BOOST_REQUIRE_EQUAL(result, expected); + }); + }); + }); + }); +} + +SEASTAR_TEST_CASE(test_map_reduce0_lifetime) { + struct map { + bool destroyed = false; + ~map() { + destroyed = true; + } + auto operator()(const X& x) const { + return yield().then([this, &x] { + BOOST_REQUIRE(!destroyed); + return x.cpu_id_squared(); + }); + } + }; + struct reduce { + bool destroyed = false; + ~reduce() { + destroyed = true; + } + auto operator()(long res, int x) { + BOOST_REQUIRE(!destroyed); + return res + x; + } + }; + return do_with_distributed<X>([] (distributed<X>& x) { + return x.start().then([&x] { + return x.map_reduce0(map{}, 0L, reduce{}).then([] (long result) { + long n = smp::count - 1; + long expected = (n * (n + 1) * (2*n + 1)) / 6; + BOOST_REQUIRE_EQUAL(result, expected); + }); + }); + }); +} + +SEASTAR_TEST_CASE(test_map_lifetime) { + struct map { + bool destroyed = false; + ~map() { + destroyed = true; + } + auto operator()(const X& x) const { + return yield().then([this, &x] { + BOOST_REQUIRE(!destroyed); + return x.cpu_id_squared(); + }); + } + }; + return do_with_distributed<X>([] (distributed<X>& x) { + return x.start().then([&x] { + return x.map(map{}).then([] (std::vector<int> result) { + BOOST_REQUIRE_EQUAL(result.size(), smp::count); + for (size_t i = 0; i < (size_t)smp::count; i++) { + BOOST_REQUIRE_EQUAL(result[i], i * i); + } + }); + }); + }); +} + +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(); + } + }); +} + +SEASTAR_TEST_CASE(test_smp_invoke_on_others) { + return seastar::async([] { + std::vector<std::vector<int>> calls; + calls.reserve(smp::count); + for (unsigned i = 0; i < smp::count; i++) { + auto& sv = calls.emplace_back(); + sv.reserve(smp::count); + } + + smp::invoke_on_all([&calls] { + return smp::invoke_on_others([&calls, from = this_shard_id()] { + calls[this_shard_id()].emplace_back(from); + }); + }).get(); + + for (unsigned i = 0; i < smp::count; i++) { + BOOST_REQUIRE_EQUAL(calls[i].size(), smp::count - 1); + for (unsigned f = 0; f < smp::count; f++) { + auto r = std::find(calls[i].begin(), calls[i].end(), f); + BOOST_REQUIRE_EQUAL(r == calls[i].end(), i == f); + } + } + }); +} + +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] () noexcept { + 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)] () noexcept { }); + + 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 = deferred_stop(s_dep); + + 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 = deferred_stop(s_service); + + 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..b8160027f --- /dev/null +++ b/src/seastar/tests/unit/dns_test.cc @@ -0,0 +1,183 @@ +/* + * This file is open source software, licensed to you under the terms + * of the Apache License, Version 2.0 (the "License"). See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. You may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Copyright (C) 2016 ScyllaDB. + */ +#include <vector> +#include <algorithm> + +#include <seastar/core/do_with.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/core/sstring.hh> +#include <seastar/core/reactor.hh> +#include <seastar/core/do_with.hh> +#include <seastar/core/when_all.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(); + }); +} + +// NOTE: cannot really test timeout in TCP mode, because seastar sockets do not support +// connect with timeout -> cannot complete connect future in dns::do_connect in reasonable +// time. + +// But we can test for connection refused working as expected. +SEASTAR_TEST_CASE(test_connection_refused_tcp) { + dns_resolver::options opts; + opts.servers = std::vector<inet_address>({ inet_address("127.0.0.1") }); + opts.use_tcp_query = true; + opts.tcp_port = 29953; // not a dns port + + 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(); + }); +} + +SEASTAR_TEST_CASE(test_resolve_tcp) { + dns_resolver::options opts; + opts.use_tcp_query = true; + return test_resolve(opts); +} + +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(); +} + + +SEASTAR_TEST_CASE(test_parallel_resolve_name) { + dns_resolver::options opts; + opts.use_tcp_query = true; + + auto d = ::make_lw_shared<dns_resolver>(std::move(opts)); + return when_all( + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com") + ).finally([d](auto&&...) {}).discard_result(); +} + +SEASTAR_TEST_CASE(test_parallel_resolve_name_udp) { + dns_resolver::options opts; + + auto d = ::make_lw_shared<dns_resolver>(std::move(opts)); + return when_all( + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com"), + d->resolve_name("www.google.com") + ).finally([d](auto&...) {}).discard_result(); +} diff --git a/src/seastar/tests/unit/exception_logging_test.cc b/src/seastar/tests/unit/exception_logging_test.cc new file mode 100644 index 000000000..c0d4ab6b7 --- /dev/null +++ b/src/seastar/tests/unit/exception_logging_test.cc @@ -0,0 +1,271 @@ +/* + * 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 + */ + +#include <exception> +#define BOOST_TEST_MODULE core + +#include <boost/test/included/unit_test.hpp> +#include <seastar/util/log.hh> +#include <seastar/util/backtrace.hh> +#include <ostream> +#include <regex> + + +using namespace seastar; + +// a class which is not derived from std::exception +// to play the part of the unknown object in the logging +// function. +class unknown_obj { + sstring _message; +public: + unknown_obj(std::string message) : _message(message) {} +}; + +// This functions generates an exception chain nesting_level+1 deep +// for each nesting level it throws one of two types of objects, an +// unknown (non std::exception) object or a runtime error which is +// derived from std::exception, it chooses the type of thrown object +// according to the bit in the `nesting_level` place in the +// `tests_instance` paramter or in other words according to: +// bool(test_instance & (1<<nesting_level)) +void exception_generator(uint32_t test_instance, int nesting_level) { + try { + if (nesting_level > 0) { + exception_generator(test_instance>>1, nesting_level-1); + } + } catch(...) { + auto msg = format("Exception Level {}", nesting_level); + if(test_instance&1) { + // Throw a non std::exception derived type + std::throw_with_nested(unknown_obj(msg)); + } else { + std::throw_with_nested(std::runtime_error(msg)); + } + } + if (nesting_level == 0) { + if (test_instance & 1) { + throw unknown_obj(format("Exception Level {}", nesting_level)); + } else { + throw std::runtime_error(format("Exception Level {}", nesting_level)); + } + } +} + +// This function generates the expected logging output string of an exception +// thrown by the exception generator function with a specific output. +std::string exception_generator_str(uint32_t test_instance,int nesting_level) { + std::ostringstream ret; + const std::string runtime_err_str = "std::runtime_error"; + const std::string exception_level_fmt_str = "Exception Level {}"; + const std::string unknown_obj_str = "unknown_obj"; + const std::string nested_exception_with_unknown_obj_str = "std::_Nested_exception<unknown_obj>"; + const std::string nested_exception_with_runtime_err_str = "std::_Nested_exception<std::runtime_error>"; + + for(; nesting_level > 0; nesting_level--) { + if (test_instance & 1) { + ret << nested_exception_with_unknown_obj_str; + } else { + ret << nested_exception_with_runtime_err_str << " (" << + format(exception_level_fmt_str.c_str(), nesting_level) << ")"; + } + ret << ": "; + test_instance >>= 1; + } + + + if (test_instance & 1) { + ret << unknown_obj_str; + } else { + ret << runtime_err_str << " (" << format(exception_level_fmt_str.c_str(), nesting_level) << ")"; + } + return ret.str(); +} + +// Test all variations of nested exceptions of some +// depth +BOOST_AUTO_TEST_CASE(nested_exception_logging1) { + + constexpr int levels_to_test = 3; + + for(int level = 0; level < levels_to_test; level++) { + for(int inst = (1 << (level + 1)) - 1; inst >= 0; inst--) { + std::ostringstream log_msg; + try { + exception_generator(inst, level); + } catch(...) { + log_msg << std::current_exception(); + } + BOOST_REQUIRE_EQUAL(log_msg.str(), exception_generator_str(inst, level)); + } + } +} + +// Test logging of nested exception not mixed in with anything +BOOST_AUTO_TEST_CASE(nested_exception_logging2) { + std::ostringstream log_msg; + try { + throw std::nested_exception(); + } catch(...) { + log_msg << std::current_exception(); + } + + BOOST_REQUIRE_EQUAL(log_msg.str(), std::string("std::nested_exception: <no exception>")); +} + +class very_important_exception : public std::exception { + const char* my_name = "very important information"; + +public: + const char* what() const noexcept { + return my_name; + } +}; + +// Test logging of nested exception that have std::system_error mixed with other exceptions +// so that std::system_error is in the middle of the exception chain. +BOOST_AUTO_TEST_CASE(nested_exception_logging3) { + std::ostringstream log_msg; + + try { + throw very_important_exception(); + } catch (...) { + try { + std::throw_with_nested(std::system_error(1, std::generic_category(), "my error")); + } catch (...) { + try { + std::throw_with_nested(unknown_obj("This is an unknown object")); + } catch (...) { + log_msg << std::current_exception(); + } + } + } + + std::string expected_string("std::_Nested_exception<unknown_obj>: std::_Nested_exception<std::system_error> (error generic:1, my error: Operation not permitted): very_important_exception (very important information)"); + + BOOST_REQUIRE_EQUAL(log_msg.str(), expected_string); +} + +BOOST_AUTO_TEST_CASE(unknown_object_thrown_test) { + std::ostringstream log_msg; + try { + throw unknown_obj("This is an unknown object"); + } catch(...) { + log_msg << std::current_exception(); + } + + BOOST_REQUIRE_EQUAL(log_msg.str(), std::string("unknown_obj")); + +} + +BOOST_AUTO_TEST_CASE(format_error_test) { + static seastar::logger l("format_error_test"); + + std::ostringstream log_msg; + l.set_ostream(log_msg); + + const char* fmt = "bad format string: {}"; + l.error(fmt); + + BOOST_TEST_MESSAGE(log_msg.str()); + BOOST_REQUIRE_NE(log_msg.str().find(__builtin_FILE()), std::string::npos); + BOOST_REQUIRE_NE(log_msg.str().find(__builtin_FUNCTION()), std::string::npos); + BOOST_REQUIRE_NE(log_msg.str().find(fmt), std::string::npos); +} + +BOOST_AUTO_TEST_CASE(throw_with_backtrace_exception_logging) { + std::ostringstream log_msg; + try { + throw_with_backtrace<std::runtime_error>("throw_with_backtrace_exception_logging"); + } catch(...) { + log_msg << std::current_exception(); + } + + auto regex_str = "backtraced<std::runtime_error> \\(throw_with_backtrace_exception_logging Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)"; + std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase); + BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re)); +} + +BOOST_AUTO_TEST_CASE(throw_with_backtrace_nested_exception_logging) { + std::ostringstream log_msg; + try { + throw_with_backtrace<std::runtime_error>("outer"); + } catch(...) { + try { + std::throw_with_nested(unknown_obj("This is an unknown object")); + } catch (...) { + log_msg << std::current_exception(); + } + } + + auto regex_str = "std::_Nested_exception<unknown_obj>.*backtraced<std::runtime_error> \\(outer Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)"; + std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase); + BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re)); +} + +BOOST_AUTO_TEST_CASE(throw_with_backtrace_seastar_nested_exception_logging) { + std::ostringstream log_msg; + try { + throw unknown_obj("This is an unknown object"); + } catch (...) { + auto outer = std::current_exception(); + try { + throw_with_backtrace<std::runtime_error>("inner"); + } catch (...) { + auto inner = std::current_exception(); + try { + throw seastar::nested_exception(std::move(inner), std::move(outer)); + } catch (...) { + log_msg << std::current_exception(); + } + } + } + + auto regex_str = "seastar::nested_exception:.*backtraced<std::runtime_error> \\(inner Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)" + " \\(while cleaning up after unknown_obj\\)"; + std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase); + BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re)); +} + +BOOST_AUTO_TEST_CASE(double_throw_with_backtrace_seastar_nested_exception_logging) { + std::ostringstream log_msg; + try { + throw_with_backtrace<std::runtime_error>("outer"); + } catch (...) { + auto outer = std::current_exception(); + try { + throw_with_backtrace<std::runtime_error>("inner"); + } catch (...) { + auto inner = std::current_exception(); + try { + throw seastar::nested_exception(std::move(inner), std::move(outer)); + } catch (...) { + log_msg << std::current_exception(); + } + } + } + + auto regex_str = "seastar::nested_exception:.*backtraced<std::runtime_error> \\(inner Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)" + " \\(while cleaning up after .*backtraced<std::runtime_error> \\(outer Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)\\)"; + std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase); + BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re)); +} 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..1ab933498 --- /dev/null +++ b/src/seastar/tests/unit/execution_stage_test.cc @@ -0,0 +1,335 @@ +/* + * 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/util/defer.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([&] () noexcept { seastar::destroy_scheduling_group(sg1).get(); }); + auto sg2 = seastar::create_scheduling_group("sg2", 100).get0(); + auto ksg2 = seastar::defer([&] () noexcept { 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..8d3b21a3c --- /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); + yield().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); + yield().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); + yield().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); + yield().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); + yield().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..b2e65230a --- /dev/null +++ b/src/seastar/tests/unit/fair_queue_test.cc @@ -0,0 +1,449 @@ +/* + * 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_entry fqent; + std::function<void(request& req)> handle; + unsigned index; + + template <typename Func> + request(unsigned weight, unsigned index, Func&& h) + : fqent(fair_queue_ticket(weight, 0)) + , handle(std::move(h)) + , index(index) + {} + + void submit() { + handle(*this); + delete this; + } +}; + +class test_env { + fair_group _fg; + fair_queue _fq; + std::vector<int> _results; + std::vector<std::vector<std::exception_ptr>> _exceptions; + fair_queue::class_id _nr_classes = 0; + std::vector<request> _inflight; + + static fair_group::config fg_config(unsigned cap) { + fair_group::config cfg; + cfg.weight_rate = 1'000'000; + cfg.size_rate = std::numeric_limits<int>::max(); + cfg.rate_limit_duration = std::chrono::microseconds(cap); + return cfg; + } + + static fair_queue::config fq_config() { + fair_queue::config cfg; + cfg.tau = std::chrono::microseconds(50); + return cfg; + } + + void drain() { + do {} while (tick() != 0); + } +public: + test_env(unsigned capacity) + : _fg(fg_config(capacity)) + , _fq(_fg, fq_config()) + {} + + // 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; + _fg.replenish_capacity(_fg.replenished_ts() + std::chrono::microseconds(1)); + _fq.dispatch_requests([] (fair_queue_entry& ent) { + boost::intrusive::get_parent_from_member(&ent, &request::fqent)->submit(); + }); + + for (unsigned i = 0; i < n; ++i) { + std::vector<request> curr; + curr.swap(_inflight); + + for (auto& req : curr) { + processed++; + _results[req.index]++; + _fq.notify_request_finished(req.fqent.ticket()); + } + + _fg.replenish_capacity(_fg.replenished_ts() + std::chrono::microseconds(1)); + _fq.dispatch_requests([] (fair_queue_entry& ent) { + boost::intrusive::get_parent_from_member(&ent, &request::fqent)->submit(); + }); + } + return processed; + } + + ~test_env() { + drain(); + for (fair_queue::class_id id = 0; id < _nr_classes; id++) { + _fq.unregister_priority_class(id); + } + } + + size_t register_priority_class(uint32_t shares) { + _results.push_back(0); + _exceptions.push_back(std::vector<std::exception_ptr>()); + _fq.register_priority_class(_nr_classes, shares); + return _nr_classes++; + } + + void do_op(fair_queue::class_id id, unsigned weight) { + unsigned index = id; + auto req = std::make_unique<request>(weight, index, [this, index] (request& req) mutable noexcept { + try { + _inflight.push_back(std::move(req)); + } catch (...) { + auto eptr = std::current_exception(); + _exceptions[index].push_back(eptr); + _fq.notify_request_finished(req.fqent.ticket()); + } + }); + + _fq.queue(id, req->fqent); + req.release(); + } + + void update_shares(fair_queue::class_id id, uint32_t shares) { + _fq.update_shares_for_class(id, 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); + } + + yield().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); + } + yield().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); + } + yield().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); + } + yield().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); + } + yield().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); + } + yield().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); + } + yield().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. Right after, don't expect Class1 to be able to do the same +SEASTAR_THREAD_TEST_CASE(test_fair_queue_forgiving_queue) { + test_env env(1); + + // The fair_queue preemption logic allows one class to gain exclusive + // queue access for at most tau duration. Test queue configures the + // request rate to be 1/us and tau to be 50us, so after (re-)activation + // a queue can overrun its peer by at most 50 requests. + + 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); + } + yield().get(); + + // consume all requests + env.tick(100); + env.reset_results(a); + + for (int i = 0; i < 100; ++i) { + env.do_op(a, 1); + env.do_op(b, 1); + } + yield().get(); + + // allow half the requests in + env.tick(100); + // 50 requests should be passed from b, other 100 should be shared 1:1 + env.verify("forgiving_queue", {1, 3}, 2); +} + +// 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); + } + + yield().get(); + // allow 25% of the requests in + env.tick(250); + env.update_shares(a, 10); + env.update_shares(b, 20); + + yield().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(3); + } + 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); + } + + yield().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..32c0f4b01 --- /dev/null +++ b/src/seastar/tests/unit/file_io_test.cc @@ -0,0 +1,950 @@ +/* + * 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/core/io_intent.hh> +#include <seastar/util/tmp_file.hh> +#include <seastar/util/alloc_failure_injector.hh> +#include <seastar/util/closeable.hh> +#include <seastar/util/internal/magic.hh> +#include <seastar/util/internal/iovec_utils.hh> + +#include <boost/range/adaptor/transformed.hpp> +#include <iostream> +#include <sys/statfs.h> +#include <fcntl.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(); + auto close_f = deferred_close(f); + // 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(); + close_f.close_now(); + 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(); + auto close_f = deferred_close(f); + 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())); + } + }); +} + +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 close_f = deferred_close(f); + auto st = 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)); + + umask(orig_umask); + }); +} + +SEASTAR_TEST_CASE(test_file_write_lifetime_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 f1 = open_file_dma(filename, oflags).get0(); + auto close_f1 = deferred_close(f1); + auto f2 = open_file_dma(filename, oflags).get0(); + auto close_f2 = deferred_close(f2); + + // Write life time hint values + std::vector<uint64_t> hint_set = {RWF_WRITE_LIFE_NOT_SET, + RWH_WRITE_LIFE_NONE, + RWH_WRITE_LIFE_SHORT, + RWH_WRITE_LIFE_MEDIUM, + RWH_WRITE_LIFE_LONG, + RWH_WRITE_LIFE_EXTREME}; + + for (auto i = 0ul; i < hint_set.size(); ++i) { + auto hint = hint_set[i]; + + // Set and verify the lifetime hint of the inode + f1.set_inode_lifetime_hint(hint).get(); + auto o_hint1 = f1.get_inode_lifetime_hint().get0(); + BOOST_CHECK_EQUAL(hint, o_hint1); + } + + // Perform invalid ops + uint64_t hint = RWH_WRITE_LIFE_EXTREME + 1; + BOOST_REQUIRE_THROW(f1.set_inode_lifetime_hint(hint).get(), std::system_error); + }); +} + +SEASTAR_TEST_CASE(test_file_fcntl) { + 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 close_f = deferred_close(f); + + // Set and verify a lease value + auto lease = F_WRLCK; + BOOST_REQUIRE(!f.fcntl(F_SETLEASE, lease).get0()); + auto o_lease = f.fcntl(F_GETLEASE).get0(); + BOOST_CHECK_EQUAL(lease, o_lease); + + // Use _short version and test the same + o_lease = f.fcntl_short(F_GETLEASE).get0(); + BOOST_CHECK_EQUAL(lease, o_lease); + + // Perform invalid ops + BOOST_REQUIRE_THROW(f.fcntl(F_SETLEASE, (uintptr_t)~0ul).get(), std::system_error); + BOOST_REQUIRE_THROW(f.fcntl_short(F_SETLEASE, (uintptr_t)~0ul).get(), std::system_error); + }); +} + +SEASTAR_TEST_CASE(test_file_ioctl) { + 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(); + uint64_t block_size = 0; + + auto f = open_file_dma(filename, oflags).get0(); + auto close_f = deferred_close(f); + + // Issueing an FS ioctl which is applicable on regular files + // and can be executed as normal user + try { + BOOST_REQUIRE(!f.ioctl(FIGETBSZ, &block_size).get0()); + BOOST_REQUIRE(block_size != 0); + + // Use _short version and test the same + BOOST_REQUIRE(!f.ioctl_short(FIGETBSZ, &block_size).get0()); + BOOST_REQUIRE(block_size != 0); + } catch (std::system_error& e) { + // anon_bdev filesystems do not support FIGETBSZ, and return EINVAL + BOOST_REQUIRE_EQUAL(e.code().value(), EINVAL); + } + + // Perform invalid ops + BOOST_REQUIRE_THROW(f.ioctl(FIGETBSZ, 0ul).get(), std::system_error); + BOOST_REQUIRE_THROW(f.ioctl_short(FIGETBSZ, 0ul).get(), std::system_error); + }); +} + +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 close_f = deferred_close(f); + 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()); + }); +} + +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 == internal::fs_magic::tmpfs; + }; + + 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 = deferred_close(f); + + 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_just_constructed_append_challenged_file_with_sloppy_size) { + 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; + file_open_options opt; + opt.sloppy_size = true; + auto f = open_file_dma(filename, oflags, opt).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)); + }); +} + +SEASTAR_TEST_CASE(test_intent) { + 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(); + auto buf = allocate_aligned_buffer<unsigned char>(1024, 1024); + std::fill(buf.get(), buf.get() + 1024, 'a'); + f.dma_write(0, buf.get(), 1024).get(); + std::fill(buf.get(), buf.get() + 1024, 'b'); + io_intent intent; + auto f1 = f.dma_write(0, buf.get(), 512); + auto f2 = f.dma_write(512, buf.get(), 512, default_priority_class(), &intent); + intent.cancel(); + + bool cancelled = false; + f1.get(); + try { + f2.get(); + } catch (cancelled_error& ex) { + cancelled = true; + } + auto rbuf = allocate_aligned_buffer<unsigned char>(1024, 1024); + f.dma_read(0, rbuf.get(), 1024).get(); + BOOST_REQUIRE(rbuf.get()[0] == 'b'); + if (cancelled) { + BOOST_REQUIRE(rbuf.get()[512] == 'a'); + } else { + // The file::dma_write doesn't preemt, but if it + // suddenly will, the 2nd write will pass before + // the intent would be cancelled + BOOST_TEST_WARN(0, "Write won the race with cancellation"); + BOOST_REQUIRE(rbuf.get()[512] == 'b'); + } + }); +} + +SEASTAR_TEST_CASE(parallel_overwrite) { + // Avoid /tmp for tmp_dir, since it can be tmpfs + return tmp_dir::do_with("XXXXXXXX.tmp", [] (tmp_dir& t) { + return async([&] { + // Check that overwrites at disk_overwrite_dma_alignment() do not cause stalls. First, + // create a file. + auto fname = (t.get_path() / "testfile.tmp").native(); + auto sz = uint64_t(1*1024*1024); + auto buffer_size = 128*1024; + + 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 buf = allocate_aligned_buffer<unsigned char>(buffer_size, f.memory_dma_alignment()); + for (uint64_t offset = 0; offset < sz; offset += buffer_size) { + f.dma_write(offset, buf.get(), buffer_size).get(); + } + + auto random_engine = testing::local_random_engine; + auto dist = std::uniform_int_distribution(uint64_t(0), sz-1); + auto offsets = std::vector<uint64_t>(); + std::generate_n(std::back_insert_iterator(offsets), 5000, [&] { return align_down(dist(random_engine), f.disk_overwrite_dma_alignment()); }); + auto stall_report = internal::report_reactor_stalls([&] { + return max_concurrent_for_each(offsets, 10, [&] (uint64_t offset) { + return f.dma_write(offset, buf.get(), f.disk_overwrite_dma_alignment()).discard_result(); + }); + }).get0(); + std::cout << "parallel_overwrite: " << stall_report << " (overwrite dma alignment " << f.disk_overwrite_dma_alignment() << ")\n"; + + f.close().get(); + remove_file(fname).get(); + }); + }); +} + +SEASTAR_TEST_CASE(test_oversized_io_works) { + 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(); + + size_t max_write = f.disk_write_max_length(); + size_t max_read = f.disk_read_max_length(); + size_t buf_size = std::max(max_write, max_read) + 4096; + + auto buf = allocate_aligned_buffer<unsigned char>(buf_size, 4096); + std::fill(buf.get(), buf.get() + buf_size, 'a'); + + f.dma_write(0, buf.get(), buf_size).get(); + f.flush().get0(); + f.close().get(); + + std::fill(buf.get(), buf.get() + buf_size, 'b'); + f = open_file_dma(filename, open_flags::rw).get0(); + f.dma_read(0, buf.get(), buf_size).get(); + + BOOST_REQUIRE((size_t)std::count_if(buf.get(), buf.get() + buf_size, [](auto x) { return x == 'a'; }) == buf_size); + }); +} 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..f54e1ae36 --- /dev/null +++ b/src/seastar/tests/unit/file_utils_test.cc @@ -0,0 +1,306 @@ +/* + * 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) { + 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")); +} + +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; + sstring inner_path_renamed; + set_default_tmpdir(outer_path.c_str()); + BOOST_REQUIRE_THROW(tmp_dir::do_with([&] (tmp_dir& inner) mutable { + inner_path = inner.get_path().native(); + inner_path_renamed = inner_path + ".renamed"; + return rename_file(inner_path, inner_path_renamed); + }).get(), std::system_error); + BOOST_REQUIRE(!file_exists(inner_path).get0()); + BOOST_REQUIRE(file_exists(inner_path_renamed).get0()); + 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; + sstring inner_path_renamed; + set_default_tmpdir(outer_path.c_str()); + BOOST_REQUIRE_THROW(tmp_dir::do_with_thread([&] (tmp_dir& inner) mutable { + inner_path = inner.get_path().native(); + inner_path_renamed = inner_path + ".renamed"; + return rename_file(inner_path, inner_path_renamed); + }).get(), std::system_error); + BOOST_REQUIRE(!file_exists(inner_path).get0()); + BOOST_REQUIRE(file_exists(inner_path_renamed).get0()); + set_default_tmpdir(saved_default_tmpdir.c_str()); + }); +} + +SEASTAR_TEST_CASE(test_read_entire_file_contiguous) { + return tmp_file::do_with([] (tmp_file& tf) { + return async([&tf] { + file& f = tf.get_file(); + auto& eng = testing::local_random_engine; + auto dist = std::uniform_int_distribution<unsigned>(); + size_t size = f.memory_dma_alignment() * (1 + dist(eng) % 1000); + auto wbuf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), size); + for (size_t i = 0; i < size; i++) { + static char chars[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + wbuf.get_write()[i] = chars[dist(eng) % sizeof(chars)]; + } + + BOOST_REQUIRE_EQUAL(f.dma_write(0, wbuf.begin(), wbuf.size()).get0(), wbuf.size()); + f.flush().get(); + + sstring res = util::read_entire_file_contiguous(tf.get_path()).get0(); + BOOST_REQUIRE_EQUAL(res, std::string_view(wbuf.begin(), wbuf.size())); + }); + }); +} 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..7e351e6d2 --- /dev/null +++ b/src/seastar/tests/unit/foreign_ptr_test.cc @@ -0,0 +1,165 @@ +/* + * 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/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); + }); +} + +SEASTAR_THREAD_TEST_CASE(foreign_ptr_destroy_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; + } + + using namespace std::chrono_literals; + + std::vector<bool> destroyed_on; + destroyed_on.resize(smp::count); + + struct deferred { + std::function<void()> on_destroy; + deferred(std::function<void()> on_destroy_func) + : on_destroy(std::move(on_destroy_func)) + {} + ~deferred() { + on_destroy(); + } + }; + + auto val = smp::submit_to(1, [&] () mutable { + return make_foreign(std::make_unique<deferred>([&] { + destroyed_on[this_shard_id()] = true; + })); + }).get0(); + + val.destroy().get(); + + BOOST_REQUIRE(destroyed_on[1]); + BOOST_REQUIRE(!destroyed_on[0]); +} diff --git a/src/seastar/tests/unit/fsnotifier_test.cc b/src/seastar/tests/unit/fsnotifier_test.cc new file mode 100644 index 000000000..3625e76d9 --- /dev/null +++ b/src/seastar/tests/unit/fsnotifier_test.cc @@ -0,0 +1,228 @@ +/* + * 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 <seastar/core/fsnotify.hh> + +#include "tmpdir.hh" + +namespace fs = std::filesystem; +using namespace seastar; +using experimental::fsnotifier; + +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..c876f61a2 --- /dev/null +++ b/src/seastar/tests/unit/fstream_test.cc @@ -0,0 +1,580 @@ +/* + * 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> +#include <seastar/util/closeable.hh> +#include <seastar/util/alloc_failure_injector.hh> + +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}); + auto close_r_in = deferred_close(r->in); + 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(); + }); +} + +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<int>(0, std::numeric_limits<char>::max()); + 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 close_f = deferred_close(f); + 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))); + } + }); +} + +#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 close_f = deferred_close(f); + auto buf = static_cast<char*>(aligned_alloc(4096, 4096)); + auto del = defer([&] () noexcept { ::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([&] () noexcept { ::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)); + }); +} + +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()); + }); +} + +#ifdef SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION + +SEASTAR_TEST_CASE(test_close_error) { + using namespace seastar::memory; + + return tmp_dir::do_with_thread([] (tmp_dir& t) { + bool done = false; + for (size_t i = 0; !done; i++) { + bool got_close_error = false; + sstring filename = (t.get_path() / format("testfile-{}.tmp", i).c_str()).native(); + file f = open_file_dma(filename, open_flags::rw | open_flags::create | open_flags::truncate).get0(); + auto opts = file_output_stream_options{}; + opts.write_behind = 16; + std::unique_ptr<output_stream<char>> out = std::make_unique<output_stream<char>>(make_file_output_stream(std::move(f), opts).get0()); + size_t size = 4096; + std::vector<char> buf(size); + std::iota(buf.begin(), buf.end(), 0); + size_t file_length = 1 * 1024 * 1024; + auto fut = make_ready_future<>(); + for (size_t len = 0; len < file_length; len += size) { + fut = fut.finally([&] { return out->write(buf.data(), size); }); + } + fut.get(); + try { + local_failure_injector().fail_after(i); + out->close().get(); + done = true; + local_failure_injector().cancel(); + } catch (const std::bad_alloc&) { + got_close_error = true; + } + BOOST_REQUIRE(got_close_error || done); + out.reset(); + remove_file(filename).get(); + } + }); +} + +#endif // SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION diff --git a/src/seastar/tests/unit/futures_test.cc b/src/seastar/tests/unit/futures_test.cc new file mode 100644 index 000000000..931a30e63 --- /dev/null +++ b/src/seastar/tests/unit/futures_test.cc @@ -0,0 +1,1985 @@ +/* + * 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 <cstddef> +#include <exception> +#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/when_any.hh> +#include <seastar/core/when_all.hh> +#include <seastar/core/gate.hh> +#include <seastar/util/log.hh> +#include <seastar/util/later.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> +#include <stdexcept> +#include <unistd.h> + +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(); + } +}; + +SEASTAR_TEST_CASE(test_get_on_exceptional_promise) { + auto p = promise<>(); + p.set_exception(test_exception("test")); + BOOST_REQUIRE_THROW(p.get_future().get(), test_exception); + return make_ready_future(); +} + +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_exception_can_be_thrown_from_do_until_condition) { + return do_until([] { throw expected_exception(); return false; }, [] { + 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<>() : yield(); + 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()); })); + }); +} + +// helper function for when_any tests +template<typename Container> +future<> when_all_but_one_succeed(Container& futures, size_t leave_out) +{ + auto sz = futures.size(); + assert(sz >= 1); + assert(leave_out < sz); + std::vector<future<size_t>> all_but_one_tmp; + all_but_one_tmp.reserve(sz - 1); + for (size_t i = 0 ; i < sz; i++){ + if (i == leave_out) { continue; } + all_but_one_tmp.push_back(std::move(futures[i])); + } + auto all_but_one = make_shared(std::move(all_but_one_tmp)); + return when_all_succeed(all_but_one->begin(), all_but_one->end()).then([all_but_one] (auto&& _) { + return make_ready_future<>(); + }); +} + +SEASTAR_TEST_CASE(test_when_any_iterator_range_i) { + std::vector<future<size_t>> futures; + for (size_t i = 0; i != 100; ++i) { + auto fut = yield(); + 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 seastar::when_any(p->begin(), p->end()).then([p](auto &&ret_obj) { + BOOST_REQUIRE(ret_obj.futures[ret_obj.index].available()); + BOOST_REQUIRE(ret_obj.futures[ret_obj.index].get0() == ret_obj.index); + return when_all_but_one_succeed(ret_obj.futures, ret_obj.index); + }); +} + +SEASTAR_TEST_CASE(test_when_any_iterator_range_ii) { + std::vector<future<size_t>> futures; + for (size_t i = 0; i != 100; ++i) { + if (i == 42) { + auto fut = seastar::make_ready_future<>(); + futures.push_back(fut.then([i] { return i; })); + } else { + auto fut = seastar::sleep(100ms); + futures.push_back(fut.then([i] { return i; })); + } + } + auto p = make_shared(std::move(futures)); + return seastar::when_any(p->begin(), p->end()).then([p](auto &&ret_obj) { + BOOST_REQUIRE(ret_obj.futures[ret_obj.index].available()); + BOOST_REQUIRE(ret_obj.futures[ret_obj.index].get0() == ret_obj.index); + BOOST_REQUIRE(ret_obj.index == 42); + return when_all_but_one_succeed(ret_obj.futures, ret_obj.index); + }); +} + +SEASTAR_TEST_CASE(test_when_any_iterator_range_iii) { + std::vector<future<size_t>> futures; + for (size_t i = 0; i != 100; ++i) { + if (i == 42) { + auto fut = seastar::sleep(5ms); + futures.push_back(fut.then([i] { return i; })); + } else { + auto fut = seastar::sleep(100ms); + futures.push_back(fut.then([i] { return i; })); + } + } + auto p = make_shared(std::move(futures)); + return seastar::when_any(p->begin(), p->end()).then([p](auto &&ret_obj) { + BOOST_REQUIRE(ret_obj.futures[ret_obj.index].available()); + BOOST_REQUIRE(ret_obj.futures[ret_obj.index].get0() == ret_obj.index); + BOOST_REQUIRE(ret_obj.index == 42); + return when_all_but_one_succeed(ret_obj.futures, ret_obj.index); + }); +} + +SEASTAR_TEST_CASE(test_when_any_iterator_range_iv) { + std::vector<future<size_t>> futures; + for (size_t i = 0; i != 100; ++i) { + if (i == 42) { + auto fut = yield().then([] { return seastar::make_exception_future(std::runtime_error("test")); } ); + futures.push_back(fut.then([i] { return i; })); + } else { + auto fut = seastar::sleep(100ms); + futures.push_back(fut.then([i] { return i; })); + } + } + auto p = make_shared(std::move(futures)); + return seastar::when_any(p->begin(), p->end()).then([p](auto &&ret_obj) { + BOOST_REQUIRE(ret_obj.futures[ret_obj.index].available()); + BOOST_REQUIRE_THROW(ret_obj.futures[ret_obj.index].get(), std::runtime_error); + return when_all_but_one_succeed(ret_obj.futures, ret_obj.index); + }); +} + +SEASTAR_TEST_CASE(test_when_any_variadic_i) +{ + auto f_int = yield().then([] { return make_ready_future<int>(42); }); + auto f_string = sleep(100ms).then([] { return make_ready_future<sstring>("hello"); }); + auto f_l33tspeak = sleep(100ms).then([] { + return make_ready_future<std::tuple<char, int, int, char, char, int, char>>( + std::make_tuple('s', 3, 4, 's', 't', 4, 'r')); + }); + return when_any(std::move(f_int), std::move(f_string), std::move(f_l33tspeak)).then([](auto&& wa_result) { + BOOST_REQUIRE(wa_result.index == 0); + auto [one, two, three] = std::move(wa_result.futures); + BOOST_REQUIRE(one.get0() == 42); + return when_all_succeed(std::move(two), std::move(three)).then([](auto _) { return seastar::make_ready_future<>(); }); + }); +} + +SEASTAR_TEST_CASE(test_when_any_variadic_ii) +{ + struct foo { + int bar = 86; + }; + + auto f_int = sleep(100ms).then([] { return make_ready_future<int>(42); }); + auto f_foo = sleep(75ms).then([] { return make_ready_future<foo>(); }); + auto f_string = sleep(1ms).then([] { return make_ready_future<sstring>("hello"); }); + auto f_l33tspeak = sleep(50ms).then([] { + return make_ready_future<std::tuple<char, int, int, char, char, int, char>>( + std::make_tuple('s', 3, 4, 's', 't', 4, 'r')); + }); + return when_any(std::move(f_int), std::move(f_foo), std::move(f_string), std::move(f_l33tspeak)) + .then([](auto&& wa_result) { + BOOST_REQUIRE(wa_result.index == 2); + auto [one, two, three, four] = std::move(wa_result.futures); + BOOST_REQUIRE(three.get0() == "hello"); + return when_any(std::move(one), std::move(two), std::move(four)).then([](auto wa_nextresult) { + auto [one, two, four] = std::move(wa_nextresult.futures); + BOOST_REQUIRE(wa_nextresult.index == 2); + BOOST_REQUIRE(four.get0() == std::make_tuple('s', 3, 4, 's', 't', 4, 'r')); + return when_any(std::move(one), std::move(two)).then([](auto wa_result) { + auto [one, two] = std::move(wa_result.futures); + BOOST_REQUIRE(wa_result.index == 1); + BOOST_REQUIRE(two.get0().bar == foo{}.bar); + return one.then([](int x) { BOOST_REQUIRE(x == 42); }); + }); + }); + }); +} + +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); + }); + }); +} + +SEASTAR_TEST_CASE(test_map_reduce_lifetime) { + struct map { + bool destroyed = false; + ~map() { + destroyed = true; + } + auto operator()(long x) { + return yield().then([this, x] { + BOOST_REQUIRE(!destroyed); + return x; + }); + } + }; + struct reduce { + long& res; + bool destroyed = false; + ~reduce() { + destroyed = true; + } + auto operator()(long x) { + return yield().then([this, x] { + BOOST_REQUIRE(!destroyed); + res += x; + }); + } + }; + return do_with(0L, [] (auto& res) { + long n = 10; + return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n), + map{}, reduce{res}).then([n, &res] { + long expected = (n * (n - 1)) / 2; + BOOST_REQUIRE_EQUAL(res, expected); + }); + }); +} + +SEASTAR_TEST_CASE(test_map_reduce0_lifetime) { + struct map { + bool destroyed = false; + ~map() { + destroyed = true; + } + auto operator()(long x) { + return yield().then([this, x] { + BOOST_REQUIRE(!destroyed); + return x; + }); + } + }; + struct reduce { + bool destroyed = false; + ~reduce() { + destroyed = true; + } + auto operator()(long res, long x) { + BOOST_REQUIRE(!destroyed); + return res + x; + } + }; + long n = 10; + return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n), + map{}, 0L, reduce{}).then([n] (long res) { + long expected = (n * (n - 1)) / 2; + BOOST_REQUIRE_EQUAL(res, 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 yield().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 yield().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 yield().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 yield().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(yield(), yield(), 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<>(); + }, yield()).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<>(); + }, yield()).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); + yield().get(); + + BOOST_REQUIRE(!f.available()); + + manual_clock::advance(1s); + yield().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); + yield().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); + yield().get(); + }); +} + +template<typename... T> +static void check_aborted(future<T...>&& f) { + check_failed_with<abort_requested_exception>(std::move(f)); +} + +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); + yield().get(); + + check_timed_out(std::move(f1)); + BOOST_REQUIRE(!f2.available()); + BOOST_REQUIRE(!f3.available()); + + manual_clock::advance(1s); + yield().get(); + + check_timed_out(std::move(f2)); + BOOST_REQUIRE(!f3.available()); + + pr.set_value(42); + + BOOST_REQUIRE_EQUAL(42, f3.get0()); + }); +} + +SEASTAR_THREAD_TEST_CASE(test_shared_future_with_abort) { + abort_source as; + abort_source as2; + shared_promise<with_clock<manual_clock>, int> pr; + auto f1 = pr.get_shared_future(as); + auto f2 = pr.get_shared_future(as2); + auto f3 = pr.get_shared_future(); + + BOOST_REQUIRE(!f1.available()); + BOOST_REQUIRE(!f2.available()); + BOOST_REQUIRE(!f3.available()); + + as.request_abort(); + + check_aborted(std::move(f1)); + BOOST_REQUIRE(!f2.available()); + BOOST_REQUIRE(!f3.available()); + + as2.request_abort(); + + check_aborted(std::move(f2)); + BOOST_REQUIRE(!f3.available()); + + pr.set_value(42); + + BOOST_REQUIRE_EQUAL(42, f3.get0()); + + auto f4 = pr.get_shared_future(as); + BOOST_REQUIRE(f4.available()); +} + +#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_TEST_CASE(test_destroy_promise_after_state_take_value) { + future<> f = make_ready_future<>(); + auto p = std::make_unique<seastar::promise<>>(); + f = p->get_future(); + p->set_value(); + auto g = f.then([] {}); + p.reset(); + return g; +} + +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 yield().then([] { + seastar_logger.info("backtrace: {}", current_backtrace()); + }); +} + +void func3() { + seastar::async([] { + func4().get(); + }).get(); +} + +future<> func2() { + return seastar::async([] { + func3(); + }); +} + +future<> func1() { + return yield().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 yield().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 yield().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); +} + +SEASTAR_THREAD_TEST_CASE(test_for_each_set) { + std::bitset<32> s; + s.set(4); + s.set(0); + + auto range = bitsets::for_each_set(s); + unsigned res = 0; + do_for_each(range, [&res] (auto i) { + res |= 1 << i; + }).get(); + BOOST_REQUIRE_EQUAL(res, 17); +} + +SEASTAR_THREAD_TEST_CASE(test_yield) { + bool flag = false; + auto one = yield().then([&] { + flag = true; + }); + BOOST_REQUIRE_EQUAL(flag, false); + one.get(); + BOOST_REQUIRE_EQUAL(flag, true); + +#ifndef SEASTAR_DEBUG + // same thing, with now(), but for non-DEBUG only, otherwise .then() doesn't + // use the ready-future fast-path and always schedules a task + flag = false; + auto two = now().then([&] { + flag = true; + }); + // now() does not yield + BOOST_REQUIRE_EQUAL(flag, true); +#endif +} + +// The seastar::make_exception_future() function has two distinct cases - it +// can create an exceptional future from an existing std::exception_ptr, or +// from an any object which will be wrapped in an std::exception_ptr using +// std::make_exception_ptr. We want to test here these two cases, as well +// what happens when the given parameter is almost a std::exception_ptr, +// just with different qualifiers, like && or const (see issue #1010). +SEASTAR_TEST_CASE(test_make_exception_future) { + // When make_exception_future() is given most types - like int and + // std::runtime_error - a copy of the given value get stored in the + // future (internally, it is wrapped using std::make_exception_ptr): + future<> f1 = make_exception_future<>(3); + BOOST_REQUIRE(f1.failed()); + BOOST_REQUIRE_THROW(f1.get(), int); + future<> f2 = make_exception_future<>(std::runtime_error("hello")); + BOOST_REQUIRE(f2.failed()); + BOOST_REQUIRE_THROW(f2.get(), std::runtime_error); + // However, if make_exception_future() is given an std::exception_ptr + // it behaves differently - the exception stored in the future will be + // the one held in the given exception_ptr - not the exception_ptr object + // itself. + std::exception_ptr e3 = std::make_exception_ptr(3); + future<> f3 = make_exception_future<>(e3); + BOOST_REQUIRE(f3.failed()); + BOOST_REQUIRE_THROW(f3.get(), int); // expecting int, not std::exception_ptr + // If make_exception_future() is given an std::exception_ptr by rvalue, + // it should also work correctly: + // An unnamed rvalue: + future<> f4 = make_exception_future<>(std::make_exception_ptr(3)); + BOOST_REQUIRE(f4.failed()); + BOOST_REQUIRE_THROW(f4.get(), int); // expecting int, not std::exception_ptr + // A rvalue reference (a move): + std::exception_ptr e5 = std::make_exception_ptr(3); + future<> f5 = make_exception_future<>(std::move(e5)); // note std::move() + BOOST_REQUIRE(f5.failed()); + BOOST_REQUIRE_THROW(f5.get(), int); // expecting int, not std::exception_ptr + // A rvalue reference to a *const* exception_ptr: + // Reproduces issue #1010 - a const exception_ptr sounds odd, but can + // happen accidentally when capturing an exception_ptr in a non-mutable + // lambda. + // Note that C++ is fine with std::move() being used on a const object, + // it will simply fall back to a copy instead of a move. And a copy does + // work (without std::move(), it works). + const std::exception_ptr e6 = std::make_exception_ptr(3); // note const! + future<> f6 = make_exception_future<>(std::move(e6)); // note std::move() + BOOST_REQUIRE(f6.failed()); + BOOST_REQUIRE_THROW(f6.get(), int); // expecting int, not std::exception_ptr + + return make_ready_future<>(); +} diff --git a/src/seastar/tests/unit/httpd_test.cc b/src/seastar/tests/unit/httpd_test.cc new file mode 100644 index 000000000..9439b2ea2 --- /dev/null +++ b/src/seastar/tests/unit/httpd_test.cc @@ -0,0 +1,1318 @@ +/* + * 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 <seastar/http/response_parser.hh> +#include <sstream> +#include <seastar/core/shared_future.hh> +#include <seastar/http/client.hh> +#include <seastar/http/url.hh> +#include <seastar/util/later.hh> +#include <seastar/util/short_streams.hh> + +using namespace seastar; +using namespace httpd; + +class handl : public httpd::handler_base { +public: + virtual future<std::unique_ptr<http::reply> > handle(const sstring& path, + std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) { + rep->done("html"); + return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep)); + } +}; + +SEASTAR_TEST_CASE(test_reply) +{ + http::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) { + http::request req; + req._url = "/a?q=%23%24%23"; + sstring url = req.parse_query_param(); + BOOST_REQUIRE_EQUAL(url, "/a"); + BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#"); + req._url = "/a?a=%23%24%23&b=%22%26%22"; + req.parse_query_param(); + 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<http::request> req = std::make_unique<http::request>(); + std::unique_ptr<http::reply> rep = std::make_unique<http::reply>(); + + auto f1 = + route.handle("/api", std::move(req), std::move(rep)).then( + [] (std::unique_ptr<http::reply> rep) { + BOOST_REQUIRE_EQUAL((int )rep->_status, (int )http::reply::status_type::ok); + }); + req.reset(new http::request); + rep.reset(new http::reply); + + auto f2 = + route.handle("/", std::move(req), std::move(rep)).then( + [] (std::unique_ptr<http::reply> rep) { + BOOST_REQUIRE_EQUAL((int )rep->_status, (int )http::reply::status_type::ok); + }); + req.reset(new http::request); + rep.reset(new http::reply); + auto f3 = + route.handle("/api/abc", std::move(req), std::move(rep)).then( + [] (std::unique_ptr<http::reply> rep) { + }); + req.reset(new http::request); + rep.reset(new http::reply); + auto f4 = + route.handle("/ap", std::move(req), std::move(rep)).then( + [] (std::unique_ptr<http::reply> rep) { + BOOST_REQUIRE_EQUAL((int )rep->_status, + (int )http::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<http::request>(), std::make_unique<http::reply>()).then([res1, route] (auto f) { + BOOST_REQUIRE_EQUAL(*res1, true); + }); + + auto f2 = route->handle("/my/path/value2/text1", std::make_unique<http::request>(), std::make_unique<http::reply>()).then([res2, route] (auto f) { + BOOST_REQUIRE_EQUAL(*res2, true); + }); + + auto f3 = route->handle("/my/path/value3/text2/text3", std::make_unique<http::request>(), std::make_unique<http::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::http::request> req = std::make_unique<seastar::http::request>(); + ss.str(""); + req->_headers["Host"] = "localhost"; + output_stream_options opts; + opts.trim_to_size = true; + return do_with(output_stream<char>(cr.transform(std::move(req), "json", output_stream<char>(memory_data_sink(ss), 32000, opts))), + 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) { + output_stream_options opts; + opts.trim_to_size = true; + return do_with(output_stream<char>(cr.transform(std::make_unique<seastar::http::request>(), "html", output_stream<char>(memory_data_sink(ss), 32000, opts))), + [] (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(1), 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<http::reply>> handle(const sstring& path, + std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override { + rep->write_body("json", std::move(_write_func)); + count++; + _all_message_sent.set_value(); + return make_ready_future<std::unique_ptr<http::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(1), 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<http::reply>> handle(const sstring& path, + std::unique_ptr<http::request> req, std::unique_ptr<http::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<http::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<http::reply>> handle(const sstring& path, + std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override { + rep->write_body("json", _write_func); + return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep)); + } +}; + +SEASTAR_TEST_CASE(content_length_limit) { + return seastar::async([] { + loopback_connection_factory lcf(1); + 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(1); + 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(1); + 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(); + }); +} + +struct echo_handler : public handler_base { + bool chunked_reply; + echo_handler(bool chunked_reply_) : handler_base(), chunked_reply(chunked_reply_) {} + + future<std::unique_ptr<http::reply>> do_handle(std::unique_ptr<http::request>& req, std::unique_ptr<http::reply>& rep, sstring& content) { + for (auto it : req->chunk_extensions) { + content += it.first; + if (it.second != "") { + content += to_sstring("=") + it.second; + } + } + for (auto it : req->trailing_headers) { + content += it.first; + if (it.second != "") { + content += to_sstring(": ") + it.second; + } + } + if (!chunked_reply) { + rep->write_body("txt", content); + } else { + rep->write_body("txt", [ c = content ] (output_stream<char>&& out) { + return do_with(std::move(out), [ c = std::move(c) ] (output_stream<char>& out) { + return out.write(std::move(c)).then([&out] { + return out.flush().then([&out] { + return out.close(); + }); + }); + }); + }); + } + return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep)); + } +}; + +/* + * A request handler that responds with the same body that was used in the request using the requests content_stream + * */ +struct echo_stream_handler : public echo_handler { + echo_stream_handler(bool chunked_reply = false) : echo_handler(chunked_reply) {} + future<std::unique_ptr<http::reply>> handle(const sstring& path, + std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override { + return do_with(std::move(req), std::move(rep), sstring(), [this] (std::unique_ptr<http::request>& req, std::unique_ptr<http::reply>& rep, sstring& rep_content) { + return do_until([&req] { return req->content_stream->eof(); }, [&req, &rep_content] { + return req->content_stream->read().then([&rep_content] (temporary_buffer<char> tmp) { + rep_content += to_sstring(std::move(tmp)); + }); + }).then([&req, &rep, &rep_content, this] { + return this->do_handle(req, rep, rep_content); + }); + }); + } +}; + +/* + * Same handler as above, but without using streams + * */ +struct echo_string_handler : public echo_handler { + echo_string_handler(bool chunked_reply = false) : echo_handler(chunked_reply) {} + future<std::unique_ptr<http::reply>> handle(const sstring& path, + std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override { + return this->do_handle(req, rep, req->content); + } +}; + +/* + * Checks if the server responds to the request equivalent to the concatenation of all req_parts with a reply containing + * the resp_parts strings, assuming that the content streaming is set to stream and the /test route is handled by handl + * */ +future<> check_http_reply (std::vector<sstring>&& req_parts, std::vector<std::string>&& resp_parts, bool stream, handler_base* handl) { + return seastar::async([req_parts = std::move(req_parts), resp_parts = std::move(resp_parts), stream, handl] { + loopback_connection_factory lcf(1); + http_server server("test"); + server.set_content_streaming(stream); + loopback_socket_impl lsi(lcf); + httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket()); + future<> client = seastar::async([req_parts = std::move(req_parts), resp_parts = std::move(resp_parts), &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& str : req_parts) { + output.write(std::move(str)).get(); + output.flush().get(); + } + auto resp = input.read().get0(); + for (auto& str : resp_parts) { + BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find(std::move(str)), std::string::npos); + } + + input.close().get(); + output.close().get(); + }); + + server._routes.put(GET, "/test", handl); + server.do_accepts(0).get(); + + client.get(); + server.stop().get(); + }); +}; + +static future<> test_basic_content(bool streamed, bool chunked_reply) { + return seastar::async([streamed, chunked_reply] { + loopback_connection_factory lcf(1); + http_server server("test"); + if (streamed) { + server.set_content_streaming(true); + } + loopback_socket_impl lsi(lcf); + httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket()); + future<> client = seastar::async([&lsi, chunked_reply] { + connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0(); + http::experimental::connection conn(std::move(c_socket)); + + { + fmt::print("Simple request test\n"); + auto req = http::request::make("GET", "test", "/test"); + auto resp = conn.make_request(std::move(req)).get0(); + BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok); + if (!chunked_reply) { + BOOST_REQUIRE_EQUAL(resp.content_length, 0); + } else { + // If response is chunked it will contain the single termination + // zero-sized chunk that still needs to be read out + auto in = conn.in(resp); + sstring body = util::read_entire_stream_contiguous(in).get0(); + BOOST_REQUIRE_EQUAL(body, ""); + } + } + + { + fmt::print("Request with body test\n"); + auto req = http::request::make("GET", "test", "/test"); + req.write_body("txt", sstring("12345 78901\t34521345")); + auto resp = conn.make_request(std::move(req)).get0(); + BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok); + if (!chunked_reply) { + BOOST_REQUIRE_EQUAL(resp.content_length, 20); + } + auto in = conn.in(resp); + sstring body = util::read_entire_stream_contiguous(in).get0(); + BOOST_REQUIRE_EQUAL(body, sstring("12345 78901\t34521345")); + } + + { + fmt::print("Request with content-length body\n"); + auto req = http::request::make("GET", "test", "/test"); + req.write_body("txt", 12, [] (output_stream<char>&& out) { + return seastar::async([out = std::move(out)] () mutable { + out.write(sstring("1234567890")).get(); + out.write(sstring("AB")).get(); + out.flush().get(); + out.close().get(); + }); + }); + auto resp = conn.make_request(std::move(req)).get0(); + BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok); + if (!chunked_reply) { + BOOST_REQUIRE_EQUAL(resp.content_length, 12); + } + auto in = conn.in(resp); + sstring body = util::read_entire_stream_contiguous(in).get0(); + BOOST_REQUIRE_EQUAL(body, sstring("1234567890AB")); + } + + { + const size_t size = 128*1024; + fmt::print("Request with {}-kbytes content-length body\n", size >> 10); + temporary_buffer<char> jumbo(size); + temporary_buffer<char> jumbo_copy(size); + for (size_t i = 0; i < size; i++) { + jumbo.get_write()[i] = 'a' + i % ('z' - 'a'); + jumbo_copy.get_write()[i] = jumbo[i]; + } + auto req = http::request::make("GET", "test", "/test"); + req.write_body("txt", size, [jumbo = std::move(jumbo)] (output_stream<char>&& out) mutable { + return seastar::async([out = std::move(out), jumbo = std::move(jumbo)] () mutable { + out.write(jumbo.get(), jumbo.size()).get(); + out.flush().get(); + out.close().get(); + }); + }); + auto resp = conn.make_request(std::move(req)).get0(); + BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok); + if (!chunked_reply) { + BOOST_REQUIRE_EQUAL(resp.content_length, size); + } + auto in = conn.in(resp); + sstring body = util::read_entire_stream_contiguous(in).get0(); + BOOST_REQUIRE_EQUAL(body, to_sstring(std::move(jumbo_copy))); + } + + { + fmt::print("Request with chunked body\n"); + auto req = http::request::make("GET", "test", "/test"); + req.write_body("txt", [] (auto&& out) -> future<> { + return seastar::async([out = std::move(out)] () mutable { + out.write(sstring("req")).get(); + out.write(sstring("1234\r\n7890")).get(); + out.flush().get(); + out.close().get(); + }); + }); + auto resp = conn.make_request(std::move(req)).get0(); + BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok); + if (!chunked_reply) { + BOOST_REQUIRE_EQUAL(resp.content_length, 13); + } + auto in = conn.in(resp); + sstring body = util::read_entire_stream_contiguous(in).get0(); + BOOST_REQUIRE_EQUAL(body, sstring("req1234\r\n7890")); + } + + { + fmt::print("Request with expect-continue\n"); + auto req = http::request::make("GET", "test", "/test"); + req.write_body("txt", sstring("foobar")); + req.set_expects_continue(); + auto resp = conn.make_request(std::move(req)).get0(); + BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok); + if (!chunked_reply) { + BOOST_REQUIRE_EQUAL(resp.content_length, 6); + } + auto in = conn.in(resp); + sstring body = util::read_entire_stream_contiguous(in).get0(); + BOOST_REQUIRE_EQUAL(body, sstring("foobar")); + } + + { + fmt::print("Request with incomplete content-length body\n"); + auto req = http::request::make("GET", "test", "/test"); + req.write_body("txt", 12, [] (output_stream<char>&& out) { + return seastar::async([out = std::move(out)] () mutable { + out.write(sstring("1234567890A")).get(); + out.flush().get(); + out.close().get(); + }); + }); + BOOST_REQUIRE_THROW(conn.make_request(std::move(req)).get0(), std::runtime_error); + } + + { + bool callback_completed = false; + fmt::print("Request with too large content-length body\n"); + auto req = http::request::make("GET", "test", "/test"); + req.write_body("txt", 12, [&callback_completed] (output_stream<char>&& out) { + return seastar::async([out = std::move(out), &callback_completed] () mutable { + out.write(sstring("1234567890ABC")).get(); + out.flush().get(); + out.close().get(); + callback_completed = true; + }); + }); + BOOST_REQUIRE_NE(callback_completed, true); // should throw early + BOOST_REQUIRE_THROW(conn.make_request(std::move(req)).get0(), std::runtime_error); + } + + conn.close().get(); + }); + + handler_base* handler; + if (streamed) { + handler = new echo_stream_handler(chunked_reply); + } else { + handler = new echo_string_handler(chunked_reply); + } + server._routes.put(GET, "/test", handler); + server.do_accepts(0).get(); + + client.get(); + server.stop().get(); + }); +} + +SEASTAR_TEST_CASE(test_string_content) { + return test_basic_content(false, false); +} + +SEASTAR_TEST_CASE(test_string_content_chunked) { + return test_basic_content(false, true); +} + +SEASTAR_TEST_CASE(test_stream_content) { + return test_basic_content(true, false); +} + +SEASTAR_TEST_CASE(test_stream_content_chunked) { + return test_basic_content(true, true); +} + +SEASTAR_TEST_CASE(test_not_implemented_encoding) { + return check_http_reply({ + "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: gzip, chunked\r\n\r\n", + "a\r\n1234567890\r\n", + "a\r\n1234521345\r\n", + "0\r\n\r\n" + }, {"501 Not Implemented", "Encodings other than \"chunked\" are not implemented (received encoding: \"gzip, chunked\")"}, false, new echo_string_handler()); +} + +SEASTAR_TEST_CASE(test_full_chunk_format) { + return check_http_reply({ + "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n", + "a;abc-def;hello=world;aaaa\r\n1234567890\r\n", + "a;a0-!#$%&'*+.^_`|~=\"quoted string obstext\x80\x81\xff quoted_pair: \\a\"\r\n1234521345\r\n", + "0\r\na:b\r\n~|`_^.+*'&%$#!-0a: ~!@#$%^&*()_+\x80\x81\xff\r\n obs fold \r\n\r\n" + }, {"12345678901234521345", "abc-def", "hello=world", "aaaa", "a0-!#$%&'*+.^_`|~=quoted string obstext\x80\x81\xff quoted_pair: a", + "a: b", "~|`_^.+*'&%$#!-0a: ~!@#$%^&*()_+\x80\x81\xff obs fold" + }, false, new echo_string_handler()); +} + +SEASTAR_TEST_CASE(test_chunk_extension_parser_fail) { + return check_http_reply({ + "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n", + "7; \r\nnoparse\r\n", + "0\r\n\r\n" + }, {"400 Bad Request", "Can't parse chunk size and extensions"}, false, new echo_string_handler()); +} + +SEASTAR_TEST_CASE(test_trailer_part_parser_fail) { + return check_http_reply({ + "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n", + "8\r\nparsable\r\n", + "0\r\ngood:header\r\nbad=header\r\n\r\n" + }, {"400 Bad Request", "Can't parse chunked request trailer"}, false, new echo_string_handler()); +} + +SEASTAR_TEST_CASE(test_too_long_chunk) { + return check_http_reply({ + "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n", + "a\r\n1234567890\r\n", + "a\r\n1234521345X\r\n", + "0\r\n\r\n" + }, {"400 Bad Request", "The actual chunk length exceeds the specified length"}, true, new echo_stream_handler()); +} + +SEASTAR_TEST_CASE(test_bad_chunk_length) { + return check_http_reply({ + "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n", + "a\r\n1234567890\r\n", + "aX\r\n1234521345\r\n", + "0\r\n\r\n" + }, {"400 Bad Request", "Can't parse chunk size and extensions"}, true, new echo_stream_handler()); +} + +SEASTAR_TEST_CASE(case_insensitive_header) { + std::unique_ptr<seastar::http::request> req = std::make_unique<seastar::http::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(1); + 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(); +} + +SEASTAR_TEST_CASE(http_parse_response_status) { + http_response_parser parser; + parser.init(); + char r101[] = "HTTP/1.1 101 Switching Protocols\r\n\r\n"; + char r200[] = "HTTP/1.1 200 OK\r\nHost: localhost\r\nhello\r\n"; + + parser.parse(r101, r101 + sizeof(r101), r101 + sizeof(r101)); + auto response = parser.get_parsed_response(); + BOOST_REQUIRE_EQUAL(response->_status_code, 101); + + parser.init(); + parser.parse(r200, r200 + sizeof(r200), r200 + sizeof(r200)); + response = parser.get_parsed_response(); + BOOST_REQUIRE_EQUAL(response->_status_code, 200); + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(test_shared_future) { + shared_promise<json::json_return_type> p; + auto fut = p.get_shared_future(); + + (void)yield().then([p = std::move(p)] () mutable { + p.set_value(json::json_void()); + }); + + return std::move(fut).discard_result(); +} + +SEASTAR_TEST_CASE(test_url_encode_decode) { + sstring encoded, decoded; + bool ok; + + sstring all_valid = "~abcdefghijklmnopqrstuvwhyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789."; + encoded = http::internal::url_encode(all_valid); + ok = http::internal::url_decode(encoded, decoded); + BOOST_REQUIRE_EQUAL(ok, true); + BOOST_REQUIRE_EQUAL(decoded, all_valid); + BOOST_REQUIRE_EQUAL(all_valid, encoded); + + sstring some_invalid = "a?/!@#$%^&*()[]=.\\ \tZ"; + encoded = http::internal::url_encode(some_invalid); + ok = http::internal::url_decode(encoded, decoded); + BOOST_REQUIRE_EQUAL(ok, true); + BOOST_REQUIRE_EQUAL(decoded, some_invalid); + for (size_t i = 0; i < encoded.length(); i++) { + if (encoded[i] != '%' && encoded[i] != '+') { + auto f = std::find(std::begin(all_valid), std::end(all_valid), encoded[i]); + BOOST_REQUIRE_NE(f, std::end(all_valid)); + } + } + + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(test_url_param_encode_decode) { + http::request to_send; + to_send._url = "/foo/bar"; + to_send.query_parameters["a"] = "a+a*a"; + to_send.query_parameters["b"] = "b/b\%b"; + + http::request to_recv; + to_recv._url = to_send.format_url(); + sstring url = to_recv.parse_query_param(); + + BOOST_REQUIRE_EQUAL(url, to_send._url); + BOOST_REQUIRE_EQUAL(to_recv.query_parameters.size(), to_send.query_parameters.size()); + for (const auto& p : to_send.query_parameters) { + auto it = to_recv.query_parameters.find(p.first); + BOOST_REQUIRE(it != to_recv.query_parameters.end()); + BOOST_REQUIRE_EQUAL(it->second, p.second); + } + + return make_ready_future<>(); +} diff --git a/src/seastar/tests/unit/https-server.py b/src/seastar/tests/unit/https-server.py new file mode 100755 index 000000000..9e29b35b8 --- /dev/null +++ b/src/seastar/tests/unit/https-server.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# 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) 2023 Kefu Chai ( tchaikov@gmail.com ) +# + + +import argparse +import socket +import ssl +from http.server import HTTPServer as _HTTPServer +from http.server import SimpleHTTPRequestHandler + + +class HTTPSServer(_HTTPServer): + def __init__(self, addr, port, context): + super().__init__((addr, port), SimpleHTTPRequestHandler) + self.context = context + + def get_request(self): + sock, addr = self.socket.accept() + ssl_conn = self.context.wrap_socket(sock, server_side=True) + return ssl_conn, addr + + def get_listen_port(self): + if self.socket.family == socket.AF_INET: + addr, port = self.socket.getsockname() + return port + elif self.socket.family == socket.AF_INET6: + address, port, flowinfo, scope_id = self.socket.getsockname() + return port + else: + raise Exception(f"unknown family: {self.socket.family}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="httpd for testing TLS") + parser.add_argument('--server', action='store', + help='server address in <host>:<port> format', + default='localhost:11311') + parser.add_argument('--cert', action='store', + help='path to the certificate') + parser.add_argument('--key', action='store', + help='path to the private key') + args = parser.parse_args() + host, port = args.server.split(':') + + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain(certfile=args.cert, keyfile=args.key) + with HTTPSServer(host, int(port), context) as server: + # print out the listening port when ready to serve + print(server.get_listen_port(), flush=True) + server.serve_forever() diff --git a/src/seastar/tests/unit/io_queue_test.cc b/src/seastar/tests/unit/io_queue_test.cc new file mode 100644 index 000000000..58b078fb6 --- /dev/null +++ b/src/seastar/tests/unit/io_queue_test.cc @@ -0,0 +1,503 @@ +/* + * 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) 2021 ScyllaDB + */ + +#include <seastar/core/thread.hh> +#include <seastar/core/sleep.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/testing/thread_test_case.hh> +#include <seastar/testing/test_runner.hh> +#include <seastar/core/reactor.hh> +#include <seastar/core/smp.hh> +#include <seastar/core/when_all.hh> +#include <seastar/core/file.hh> +#include <seastar/core/io_queue.hh> +#include <seastar/core/io_intent.hh> +#include <seastar/core/internal/io_request.hh> +#include <seastar/core/internal/io_sink.hh> +#include <seastar/util/internal/iovec_utils.hh> + +using namespace seastar; + +struct fake_file { + std::unordered_map<uint64_t, int> data; + + static internal::io_request make_write_req(size_t idx, int* buf) { + return internal::io_request::make_write(0, idx, buf, 1, false); + } + + static internal::io_request make_writev_req(size_t idx, int* buf, size_t nr, size_t buf_len, std::vector<::iovec>& vecs) { + vecs.reserve(nr); + for (unsigned i = 0; i < nr; i++) { + vecs.push_back({ &buf[i], buf_len }); + } + return internal::io_request::make_writev(0, idx, vecs, false); + } + + void execute_write_req(const internal::io_request& rq, io_completion* desc) { + const auto& op = rq.as<internal::io_request::operation::write>(); + data[op.pos] = *(reinterpret_cast<int*>(op.addr)); + desc->complete_with(op.size); + } + + void execute_writev_req(const internal::io_request& rq, io_completion* desc) { + size_t len = 0; + const auto& op = rq.as<internal::io_request::operation::writev>(); + for (unsigned i = 0; i < op.iov_len; i++) { + data[op.pos + i] = *(reinterpret_cast<int*>(op.iovec[i].iov_base)); + len += op.iovec[i].iov_len; + } + desc->complete_with(len); + } +}; + +struct io_queue_for_tests { + io_group_ptr group; + internal::io_sink sink; + io_queue queue; + timer<> kicker; + + io_queue_for_tests() + : group(std::make_shared<io_group>(io_queue::config{0})) + , sink() + , queue(group, sink) + , kicker([this] { kick(); }) + { + kicker.arm_periodic(std::chrono::microseconds(500)); + } + + void kick() { + for (auto&& fg : group->_fgs) { + fg->replenish_capacity(std::chrono::steady_clock::now()); + } + } +}; + +SEASTAR_THREAD_TEST_CASE(test_basic_flow) { + io_queue_for_tests tio; + fake_file file; + + auto val = std::make_unique<int>(42); + auto f = tio.queue.queue_request(default_priority_class(), internal::io_direction_and_length(internal::io_direction_and_length::write_idx, 0), file.make_write_req(0, val.get()), nullptr, {}) + .then([&file] (size_t len) { + BOOST_REQUIRE(file.data[0] == 42); + }); + + seastar::sleep(std::chrono::milliseconds(500)).get(); + tio.queue.poll_io_queue(); + tio.sink.drain([&file] (const internal::io_request& rq, io_completion* desc) -> bool { + file.execute_write_req(rq, desc); + return true; + }); + + f.get(); +} + +enum class part_flaw { none, partial, error }; + +static void do_test_large_request_flow(part_flaw flaw) { + io_queue_for_tests tio; + fake_file file; + int values[3] = { 13, 42, 73 }; + + auto limits = tio.queue.get_request_limits(); + + std::vector<::iovec> vecs; + auto f = tio.queue.queue_request(default_priority_class(), internal::io_direction_and_length(internal::io_direction_and_length::write_idx, limits.max_write * 3), + file.make_writev_req(0, values, 3, limits.max_write, vecs), nullptr, std::move(vecs)) + .then([&file, &values, &limits, flaw] (size_t len) { + size_t expected = limits.max_write; + + BOOST_REQUIRE_EQUAL(file.data[0 * limits.max_write], values[0]); + + if (flaw == part_flaw::none) { + BOOST_REQUIRE_EQUAL(file.data[1 * limits.max_write], values[1]); + BOOST_REQUIRE_EQUAL(file.data[2 * limits.max_write], values[2]); + expected += 2 * limits.max_write; + } + + if (flaw == part_flaw::partial) { + BOOST_REQUIRE_EQUAL(file.data[1 * limits.max_write], values[1]); + expected += limits.max_write / 2; + } + + BOOST_REQUIRE_EQUAL(len, expected); + }); + + for (int i = 0; i < 3; i++) { + seastar::sleep(std::chrono::milliseconds(500)).get(); + tio.queue.poll_io_queue(); + tio.sink.drain([&file, i, flaw] (const internal::io_request& rq, io_completion* desc) -> bool { + if (i == 1) { + if (flaw == part_flaw::partial) { + const auto& op = rq.as<internal::io_request::operation::writev>(); + op.iovec[0].iov_len /= 2; + } + if (flaw == part_flaw::error) { + desc->complete_with(-EIO); + return true; + } + } + file.execute_writev_req(rq, desc); + return true; + }); + } + + f.get(); +} + +SEASTAR_THREAD_TEST_CASE(test_large_request_flow) { + do_test_large_request_flow(part_flaw::none); +} + +SEASTAR_THREAD_TEST_CASE(test_large_request_flow_partial) { + do_test_large_request_flow(part_flaw::partial); +} + +SEASTAR_THREAD_TEST_CASE(test_large_request_flow_error) { + do_test_large_request_flow(part_flaw::error); +} + +SEASTAR_THREAD_TEST_CASE(test_intent_safe_ref) { + auto get_cancelled = [] (internal::intent_reference& iref) -> bool { + try { + iref.retrieve(); + return false; + } catch(seastar::cancelled_error& err) { + return true; + } + }; + + io_intent intent, intent_x; + + internal::intent_reference ref_orig(&intent); + BOOST_REQUIRE(ref_orig.retrieve() == &intent); + + // Test move armed + internal::intent_reference ref_armed(std::move(ref_orig)); + BOOST_REQUIRE(ref_orig.retrieve() == nullptr); + BOOST_REQUIRE(ref_armed.retrieve() == &intent); + + internal::intent_reference ref_armed_2(&intent_x); + ref_armed_2 = std::move(ref_armed); + BOOST_REQUIRE(ref_armed.retrieve() == nullptr); + BOOST_REQUIRE(ref_armed_2.retrieve() == &intent); + + intent.cancel(); + BOOST_REQUIRE(get_cancelled(ref_armed_2)); + + // Test move cancelled + internal::intent_reference ref_cancelled(std::move(ref_armed_2)); + BOOST_REQUIRE(ref_armed_2.retrieve() == nullptr); + BOOST_REQUIRE(get_cancelled(ref_cancelled)); + + internal::intent_reference ref_cancelled_2(&intent_x); + ref_cancelled_2 = std::move(ref_cancelled); + BOOST_REQUIRE(ref_cancelled.retrieve() == nullptr); + BOOST_REQUIRE(get_cancelled(ref_cancelled_2)); + + // Test move empty + internal::intent_reference ref_empty(std::move(ref_orig)); + BOOST_REQUIRE(ref_empty.retrieve() == nullptr); + + internal::intent_reference ref_empty_2(&intent_x); + ref_empty_2 = std::move(ref_empty); + BOOST_REQUIRE(ref_empty_2.retrieve() == nullptr); +} + +static constexpr int nr_requests = 24; + +SEASTAR_THREAD_TEST_CASE(test_io_cancellation) { + fake_file file; + + io_queue_for_tests tio; + io_priority_class pc0 = io_priority_class::register_one("a", 100); + io_priority_class pc1 = io_priority_class::register_one("b", 100); + + size_t idx = 0; + int val = 100; + + io_intent live, dead; + + std::vector<future<>> finished; + std::vector<future<>> cancelled; + + auto queue_legacy_request = [&] (io_queue_for_tests& q, io_priority_class& pc) { + auto buf = std::make_unique<int>(val); + auto f = q.queue.queue_request(pc, internal::io_direction_and_length(internal::io_direction_and_length::write_idx, 0), file.make_write_req(idx, buf.get()), nullptr, {}) + .then([&file, idx, val, buf = std::move(buf)] (size_t len) { + BOOST_REQUIRE(file.data[idx] == val); + return make_ready_future<>(); + }); + finished.push_back(std::move(f)); + idx++; + val++; + }; + + auto queue_live_request = [&] (io_queue_for_tests& q, io_priority_class& pc) { + auto buf = std::make_unique<int>(val); + auto f = q.queue.queue_request(pc, internal::io_direction_and_length(internal::io_direction_and_length::write_idx, 0), file.make_write_req(idx, buf.get()), &live, {}) + .then([&file, idx, val, buf = std::move(buf)] (size_t len) { + BOOST_REQUIRE(file.data[idx] == val); + return make_ready_future<>(); + }); + finished.push_back(std::move(f)); + idx++; + val++; + }; + + auto queue_dead_request = [&] (io_queue_for_tests& q, io_priority_class& pc) { + auto buf = std::make_unique<int>(val); + auto f = q.queue.queue_request(pc, internal::io_direction_and_length(internal::io_direction_and_length::write_idx, 0), file.make_write_req(idx, buf.get()), &dead, {}) + .then_wrapped([buf = std::move(buf)] (auto&& f) { + try { + f.get(); + BOOST_REQUIRE(false); + } catch(...) {} + return make_ready_future<>(); + }) + .then([&file, idx] () { + BOOST_REQUIRE(file.data[idx] == 0); + }); + cancelled.push_back(std::move(f)); + idx++; + val++; + }; + + auto seed = std::random_device{}(); + std::default_random_engine reng(seed); + std::uniform_int_distribution<> dice(0, 5); + + for (int i = 0; i < nr_requests; i++) { + int pc = dice(reng) % 2; + if (dice(reng) < 3) { + fmt::print("queue live req to pc {}\n", pc); + queue_live_request(tio, pc == 0 ? pc0 : pc1); + } else if (dice(reng) < 5) { + fmt::print("queue dead req to pc {}\n", pc); + queue_dead_request(tio, pc == 0 ? pc0 : pc1); + } else { + fmt::print("queue legacy req to pc {}\n", pc); + queue_legacy_request(tio, pc == 0 ? pc0 : pc1); + } + } + + dead.cancel(); + + // cancelled requests must resolve right at once + + when_all_succeed(cancelled.begin(), cancelled.end()).get(); + + seastar::sleep(std::chrono::milliseconds(500)).get(); + tio.queue.poll_io_queue(); + tio.sink.drain([&file] (const internal::io_request& rq, io_completion* desc) -> bool { + file.execute_write_req(rq, desc); + return true; + }); + + when_all_succeed(finished.begin(), finished.end()).get(); +} + +SEASTAR_TEST_CASE(test_request_buffer_split) { + auto ensure = [] (const std::vector<internal::io_request::part>& parts, const internal::io_request& req, int idx, uint64_t pos, size_t size, uintptr_t mem) { + BOOST_REQUIRE(parts[idx].req.opcode() == req.opcode()); + const auto& op = req.as<internal::io_request::operation::read>(); + const auto& sub_op = parts[idx].req.as<internal::io_request::operation::read>(); + BOOST_REQUIRE_EQUAL(sub_op.fd, op.fd); + BOOST_REQUIRE_EQUAL(sub_op.pos, pos); + BOOST_REQUIRE_EQUAL(sub_op.size, size); + BOOST_REQUIRE_EQUAL(sub_op.addr, reinterpret_cast<void*>(mem)); + BOOST_REQUIRE_EQUAL(sub_op.nowait_works, op.nowait_works); + BOOST_REQUIRE_EQUAL(parts[idx].iovecs.size(), 0); + BOOST_REQUIRE_EQUAL(parts[idx].size, sub_op.size); + }; + + // No split + { + internal::io_request req = internal::io_request::make_read(5, 13, reinterpret_cast<void*>(0x420), 17, true); + auto parts = req.split(21); + BOOST_REQUIRE_EQUAL(parts.size(), 1); + ensure(parts, req, 0, 13, 17, 0x420); + } + + // Without tail + { + internal::io_request req = internal::io_request::make_read(7, 24, reinterpret_cast<void*>(0x4321), 24, true); + auto parts = req.split(12); + BOOST_REQUIRE_EQUAL(parts.size(), 2); + ensure(parts, req, 0, 24, 12, 0x4321); + ensure(parts, req, 1, 24 + 12, 12, 0x4321 + 12); + } + + // With tail + { + internal::io_request req = internal::io_request::make_read(9, 42, reinterpret_cast<void*>(0x1234), 33, true); + auto parts = req.split(13); + BOOST_REQUIRE_EQUAL(parts.size(), 3); + ensure(parts, req, 0, 42, 13, 0x1234); + ensure(parts, req, 1, 42 + 13, 13, 0x1234 + 13); + ensure(parts, req, 2, 42 + 26, 7, 0x1234 + 26); + } + + return make_ready_future<>(); +} + +static void show_request(const internal::io_request& req, void* buf_off, std::string pfx = "") { + if (!seastar_logger.is_enabled(log_level::trace)) { + return; + } + + const auto& op = req.as<internal::io_request::operation::readv>(); + seastar_logger.trace("{}{} iovecs on req:", pfx, op.iov_len); + for (unsigned i = 0; i < op.iov_len; i++) { + seastar_logger.trace("{} base={} len={}", pfx, reinterpret_cast<uintptr_t>(op.iovec[i].iov_base) - reinterpret_cast<uintptr_t>(buf_off), op.iovec[i].iov_len); + } +} + +static void show_request_parts(const std::vector<internal::io_request::part>& parts, void* buf_off) { + if (!seastar_logger.is_enabled(log_level::trace)) { + return; + } + + seastar_logger.trace("{} parts", parts.size()); + for (const auto& p : parts) { + seastar_logger.trace(" size={} iovecs={}", p.size, p.iovecs.size()); + seastar_logger.trace(" {} iovecs on part:", p.iovecs.size()); + for (const auto& iov : p.iovecs) { + seastar_logger.trace(" base={} len={}", reinterpret_cast<uintptr_t>(iov.iov_base) - reinterpret_cast<uintptr_t>(buf_off), iov.iov_len); + } + show_request(p.req, buf_off, " "); + } +} + +SEASTAR_TEST_CASE(test_request_iovec_split) { + char large_buffer[1025]; + + auto clear_buffer = [&large_buffer] { + memset(large_buffer, 0, sizeof(large_buffer)); + }; + + auto bump_buffer = [] (const std::vector<::iovec>& vecs) { + for (auto&& v : vecs) { + for (unsigned i = 0; i < v.iov_len; i++) { + (reinterpret_cast<char*>(v.iov_base))[i]++; + } + } + }; + + auto check_buffer = [&large_buffer] (size_t len, char value) { + assert(len < sizeof(large_buffer)); + bool fill_match = true; + bool train_match = true; + for (unsigned i = 0; i < sizeof(large_buffer); i++) { + if (i < len) { + if (large_buffer[i] != value) { + fill_match = false; + } + } else { + if (large_buffer[i] != '\0') { + train_match = false; + } + } + } + BOOST_REQUIRE_EQUAL(fill_match, true); + BOOST_REQUIRE_EQUAL(train_match, true); + }; + + auto ensure = [] (const std::vector<internal::io_request::part>& parts, const internal::io_request& req, int idx, uint64_t pos) { + BOOST_REQUIRE(parts[idx].req.opcode() == req.opcode()); + const auto& op = req.as<internal::io_request::operation::writev>(); + const auto& sub_op = parts[idx].req.as<internal::io_request::operation::writev>(); + BOOST_REQUIRE_EQUAL(sub_op.fd, op.fd); + BOOST_REQUIRE_EQUAL(sub_op.pos, pos); + BOOST_REQUIRE_EQUAL(sub_op.iov_len, parts[idx].iovecs.size()); + BOOST_REQUIRE_EQUAL(sub_op.nowait_works, op.nowait_works); + BOOST_REQUIRE_EQUAL(parts[idx].size, internal::iovec_len(parts[idx].iovecs)); + + for (unsigned iov = 0; iov < parts[idx].iovecs.size(); iov++) { + BOOST_REQUIRE_EQUAL(sub_op.iovec[iov].iov_base, parts[idx].iovecs[iov].iov_base); + BOOST_REQUIRE_EQUAL(sub_op.iovec[iov].iov_len, parts[idx].iovecs[iov].iov_len); + } + }; + + std::default_random_engine& reng = testing::local_random_engine; + auto dice = std::uniform_int_distribution<uint16_t>(1, 31); + auto stop = std::chrono::steady_clock::now() + std::chrono::seconds(4); + uint64_t iter = 0; + unsigned no_splits = 0; + unsigned no_tails = 0; + + do { + seastar_logger.debug("===== iter {} =====", iter++); + std::vector<::iovec> vecs; + unsigned nr_vecs = dice(reng) % 13 + 1; + seastar_logger.debug("Generate {} iovecs", nr_vecs); + size_t total = 0; + for (unsigned i = 0; i < nr_vecs; i++) { + ::iovec iov; + iov.iov_base = reinterpret_cast<void*>(large_buffer + total); + iov.iov_len = dice(reng); + assert(iov.iov_len != 0); + total += iov.iov_len; + vecs.push_back(std::move(iov)); + } + + assert(total > 0); + clear_buffer(); + bump_buffer(vecs); + check_buffer(total, 1); + + size_t file_off = dice(reng); + internal::io_request req = internal::io_request::make_readv(5, file_off, vecs, true); + + show_request(req, large_buffer); + + size_t max_len = dice(reng) * 3; + unsigned nr_parts = (total + max_len - 1) / max_len; + seastar_logger.debug("Split {} into {}-bytes ({} parts)", total, max_len, nr_parts); + auto parts = req.split(max_len); + show_request_parts(parts, large_buffer); + BOOST_REQUIRE_EQUAL(parts.size(), nr_parts); + + size_t parts_total = 0; + for (unsigned p = 0; p < nr_parts; p++) { + ensure(parts, req, p, file_off + parts_total); + if (p < nr_parts - 1) { + BOOST_REQUIRE_EQUAL(parts[p].size, max_len); + } + parts_total += parts[p].size; + bump_buffer(parts[p].iovecs); + } + BOOST_REQUIRE_EQUAL(parts_total, total); + check_buffer(total, 2); + + if (parts.size() == 1) { + no_splits++; + } + if (parts.back().size == max_len) { + no_tails++; + } + } while (std::chrono::steady_clock::now() < stop || iter < 32 || no_splits < 16 || no_tails < 16); + + seastar_logger.info("{} iters ({} no-splits, {} no-tails)", iter, no_splits, no_tails); + + return make_ready_future<>(); +} 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..3997e8302 --- /dev/null +++ b/src/seastar/tests/unit/json_formatter_test.cc @@ -0,0 +1,61 @@ +/* + * This file is open source software, licensed to you under the terms + * of the Apache License, Version 2.0 (the "License"). See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. You may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Copyright (C) 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")); // to_json(const char*) + BOOST_CHECK_EQUAL("\"apa\"", formatter::to_json(sstring("apa"))); // to_json(const sstring&) + BOOST_CHECK_EQUAL("\"apa\"", formatter::to_json("apa", 3)); // to_json(const char*, size_t) + + using namespace std::string_literals; + sstring str = "\0 COWA\bU\nGA [{\r}]\x1a"s, + expected = "\"\\u0000 COWA\\bU\\nGA [{\\r}]\\u001A\""s; + BOOST_CHECK_EQUAL(expected, formatter::to_json(str)); // to_json(const sstring&) + BOOST_CHECK_EQUAL(expected, formatter::to_json(str.c_str(), str.size())); // to_json(const char*, size_t) + + 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..f5749c862 --- /dev/null +++ b/src/seastar/tests/unit/locking_test.cc @@ -0,0 +1,422 @@ +/* + * 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 <exception> +#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> +#include <stdexcept> + +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_rwlock_abort) { + rwlock l; + + l.write_lock().get(); + + { + abort_source as; + auto f = l.write_lock(as); + BOOST_REQUIRE(!f.available()); + + (void)sleep(1ms).then([&as] { + as.request_abort(); + }); + + BOOST_REQUIRE_THROW(f.get0(), semaphore_aborted); + } + + { + abort_source as; + auto f = l.read_lock(as); + BOOST_REQUIRE(!f.available()); + + (void)sleep(1ms).then([&as] { + as.request_abort(); + }); + + BOOST_REQUIRE_THROW(f.get0(), semaphore_aborted); + } +} + +SEASTAR_THREAD_TEST_CASE(test_rwlock_hold_abort) { + rwlock l; + + auto wh = l.hold_write_lock().get0(); + + { + abort_source as; + auto f = l.hold_write_lock(as); + BOOST_REQUIRE(!f.available()); + + (void)sleep(1ms).then([&as] { + as.request_abort(); + }); + + BOOST_REQUIRE_THROW(f.get0(), semaphore_aborted); + } + + { + abort_source as; + auto f = l.hold_read_lock(as); + BOOST_REQUIRE(!f.available()); + + (void)sleep(1ms).then([&as] { + as.request_abort(); + }); + + BOOST_REQUIRE_THROW(f.get0(), semaphore_aborted); + } +} + +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_throwing_func) { + shared_mutex sm; + struct X { + int x; + X(int x_) noexcept : x(x_) {}; + X(X&& o) : x(o.x) { + throw std::runtime_error("X moved"); + } + }; + + // verify that the shared_mutex is unlocked when func move fails + future<> fut = with_shared(sm, [x = X(0)] {}); + BOOST_REQUIRE_THROW(fut.get(), std::runtime_error); + + fut = with_lock(sm, [x = X(0)] {}); + 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 +} + +struct expected_exception : public std::exception { + int value; + expected_exception(int v) noexcept : value(v) {} +}; + +struct moved_exception : public std::exception { + int count; + moved_exception(int c) noexcept : count(c) {} +}; + +struct throw_on_move { + int value; + int delay; + int count = 0; + + throw_on_move(int v, int d = 0) noexcept : value(v), delay(d) {} + throw_on_move(const throw_on_move& o) = default; + throw_on_move(throw_on_move&& o) + : value(o.value) + , delay(o.delay) + , count(o.count + 1) + { + if (count >= delay) { + throw moved_exception(count); + } + } +}; + +SEASTAR_THREAD_TEST_CASE(test_with_shared_typed_return_nothrow_move_func) { + shared_mutex sm; + + auto expected = 42; + auto res = with_shared(sm, [expected] { + return expected; + }).get0(); + BOOST_REQUIRE_EQUAL(res, expected); + + try { + with_shared(sm, [expected] { + if (expected == 42) { + throw expected_exception(expected); + } + return expected; + }).get(); + BOOST_FAIL("No exception was thrown"); + } catch (const expected_exception& e) { + BOOST_REQUIRE_EQUAL(e.value, expected); + } catch (const std::exception& e) { + BOOST_FAIL(format("Unexpected exception type: {}", e.what())); + } +} + +SEASTAR_THREAD_TEST_CASE(test_with_shared_typed_return_throwing_move_func) { + shared_mutex sm; + + int expected_value = 42; + bool done = false; + for (int move_delay = 0; !done; move_delay++) { + try { + auto res = with_shared(sm, [exp = throw_on_move(expected_value, move_delay)] { + auto expected = std::move(exp); + return expected.value; + }).get(); + BOOST_REQUIRE_EQUAL(res, expected_value); + done = true; + } catch (const moved_exception& e) { + } catch (const std::exception& e) { + BOOST_FAIL(format("Unexpected exception type: {}", e.what())); + } + } +} + +SEASTAR_THREAD_TEST_CASE(test_with_lock_typed_return_nothrow_move_func) { + shared_mutex sm; + + auto expected = 42; + auto res = with_lock(sm, [expected] { + return expected; + }).get0(); + BOOST_REQUIRE_EQUAL(res, expected); + + try { + with_lock(sm, [expected] { + if (expected == 42) { + throw expected_exception(expected); + } + return expected; + }).get(); + BOOST_FAIL("No exception was thrown"); + } catch (const expected_exception& e) { + BOOST_REQUIRE_EQUAL(e.value, expected); + } catch (const std::exception& e) { + BOOST_FAIL(format("Unexpected exception type: {}", e.what())); + } +} + +SEASTAR_THREAD_TEST_CASE(test_with_lock_typed_return_throwing_move_func) { + shared_mutex sm; + + int expected_value = 42; + bool done = false; + for (int move_delay = 0; !done; move_delay++) { + try { + auto res = with_lock(sm, [exp = throw_on_move(expected_value, move_delay)] { + auto expected = std::move(exp); + return expected.value; + }).get(); + BOOST_REQUIRE_EQUAL(res, expected_value); + done = true; + } catch (const moved_exception& e) { + } catch (const std::exception& e) { + BOOST_FAIL(format("Unexpected exception type: {}", e.what())); + } + } +} 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..f9fc0cd16 --- /dev/null +++ b/src/seastar/tests/unit/log_buf_test.cc @@ -0,0 +1,122 @@ +/* + * 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(); + + 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())); + + const char* p = b.data(); + for (auto i = 0; i < 129; ++i) { + BOOST_REQUIRE_EQUAL(p[i], 'a'); + } + + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(log_buf_insert_iterator_format_to) { + constexpr size_t size = 128; + auto external_buf = std::make_unique<char[]>(size); + auto external_buf_ptr = external_buf.get(); + char str[size + 1]; + + internal::log_buf b(external_buf_ptr, size); + + BOOST_REQUIRE_EQUAL(reinterpret_cast<uintptr_t>(b.data()), reinterpret_cast<uintptr_t>(external_buf_ptr)); + + auto it = b.back_insert_begin(); + + memset(str, 'a', size); + str[size] = '\0'; + +#if FMT_VERSION >= 80000 + it = fmt::format_to(it, fmt::runtime(str), size); +#else + it = fmt::format_to(it, str, size); +#endif + BOOST_REQUIRE_EQUAL(reinterpret_cast<uintptr_t>(b.data()), reinterpret_cast<uintptr_t>(external_buf_ptr)); + + *it++ = '\n'; + BOOST_REQUIRE_NE(reinterpret_cast<uintptr_t>(b.data()), reinterpret_cast<uintptr_t>(external_buf_ptr)); + + memset(str, 'b', size); +#if FMT_VERSION >= 80000 + it = fmt::format_to(it, fmt::runtime(str), size); +#else + it = fmt::format_to(it, str, size); +#endif + *it++ = '\n'; + + const char* p = b.data(); + size_t pos = 0; + for (size_t i = 0; i < size; i++) { + BOOST_REQUIRE_EQUAL(p[pos++], 'a'); + } + BOOST_REQUIRE_EQUAL(p[pos++], '\n'); + for (size_t i = 0; i < size; i++) { + BOOST_REQUIRE_EQUAL(p[pos++], 'b'); + } + BOOST_REQUIRE_EQUAL(p[pos++], '\n'); + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(log_buf_clear) { + internal::log_buf buf; + + auto it = buf.back_insert_begin(); + + fmt::format_to(it, "abcd"); + + BOOST_CHECK_EQUAL(buf.view(), "abcd"); + auto cap_before = buf.capacity(); + buf.clear(); + BOOST_CHECK_EQUAL(cap_before, buf.capacity()); + BOOST_CHECK_EQUAL(0, buf.size()); + + fmt::format_to(it, "uuvvwwxxyyzz"); + + BOOST_CHECK_EQUAL(buf.view(), "uuvvwwxxyyzz"); + cap_before = buf.capacity(); + buf.clear(); + BOOST_CHECK_EQUAL(cap_before, buf.capacity()); + BOOST_CHECK_EQUAL(0, buf.size()); + + 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..04dc4b964 --- /dev/null +++ b/src/seastar/tests/unit/loopback_socket.hh @@ -0,0 +1,313 @@ +/* + * 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 { + enum class error { none, one_shot, abort }; + virtual ~loopback_error_injector() {}; + virtual error server_rcv_error() { return error::none; } + virtual error server_snd_error() { return error::none; } + virtual error client_rcv_error() { return error::none; } + virtual error client_snd_error() { return error::none; } + virtual error connect_error() { return error::none; } +}; + +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())); + } + if (_error_injector) { + auto error = _type == type::CLIENT_TX ? _error_injector->client_snd_error() : _error_injector->server_snd_error(); + if (error == loopback_error_injector::error::one_shot) { + return make_exception_future<>(std::runtime_error("test injected glitch on send")); + } + if (error == loopback_error_injector::error::abort) { + 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())); + } + if (_error_injector) { + auto error = _type == type::CLIENT_TX ? _error_injector->client_rcv_error() : _error_injector->server_rcv_error(); + if (error == loopback_error_injector::error::one_shot) { + return make_exception_future<temporary_buffer<char>>(std::runtime_error("test injected glitch on receive")); + } + if (error == loopback_error_injector::error::abort) { + shutdown(); + return make_exception_future<temporary_buffer<char>>(std::runtime_error("test injected error on receive")); + } + } + return _q.pop_eventually(); + } + void shutdown() noexcept { + _aborted = true; + _q.abort(std::make_exception_ptr(std::system_error(EPIPE, std::system_category()))); + } +}; + +class loopback_data_sink_impl : public data_sink_impl { + lw_shared_ptr<foreign_ptr<lw_shared_ptr<loopback_buffer>>> _buffer; +public: + explicit loopback_data_sink_impl(lw_shared_ptr<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 { + lw_shared_ptr<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(make_lw_shared(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(), [tx = _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"); + } + socket_address local_address() const noexcept override { + // dummy + return {}; + } + future<> wait_input_shutdown() override { + abort(); // No tests use this + return make_ready_future<>(); + } +}; + +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; + unsigned _shards_count; + std::vector<lw_shared_ptr<queue<connected_socket>>> _pending; +public: + explicit loopback_connection_factory(unsigned shards_count = smp::count) + : _shards_count(shards_count) + { + _pending.resize(shards_count); + } + server_socket get_server_socket() { + assert(this_shard_id() < _shards_count); + 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) { + assert(this_shard_id() < _shards_count); + 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++ % _shards_count; + } + void destroy_shard(unsigned shard) { + assert(shard < _shards_count); + _pending[shard] = nullptr; + } + future<> destroy_all_shards() { + return parallel_for_each(boost::irange(0u, _shards_count), [this](shard_id shard) { + return smp::submit_to(shard, [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; + std::optional<promise<connected_socket>> _connect_abort; +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 { + if (_error_injector) { + auto error = _error_injector->connect_error(); + if (error != loopback_error_injector::error::none) { + _connect_abort.emplace(); + return _connect_abort->get_future(); + } + } + + 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 { + if (_connect_abort) { + _connect_abort->set_exception(std::make_exception_ptr(std::system_error(ECONNABORTED, std::system_category()))); + _connect_abort = std::nullopt; + } else { + _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..4dde007d3 --- /dev/null +++ b/src/seastar/tests/unit/metrics_test.cc @@ -0,0 +1,319 @@ +/* + * 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/relabel_config.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 = io_priority_class::register_one("hello",100); + smp::invoke_on_all([&pc] () { + // this is a trick to get all of the queues actually register their + // stats. + return pc.update_shares(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(); + pc.rename(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 pc.rename(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)); +} + +int count_by_label(const std::string& label) { + seastar::foreign_ptr<seastar::metrics::impl::values_reference> values = seastar::metrics::impl::get_values(); + int count = 0; + for (auto&& md : (*values->metadata)) { + for (auto&& mi : md.metrics) { + if (label == "" || mi.id.labels().find(label) != mi.id.labels().end()) { + count++; + } + } + } + return count; +} + +int count_by_fun(std::function<bool(const seastar::metrics::impl::metric_info&)> f) { + seastar::foreign_ptr<seastar::metrics::impl::values_reference> values = seastar::metrics::impl::get_values(); + int count = 0; + for (auto&& md : (*values->metadata)) { + for (auto&& mi : md.metrics) { + if (f(mi)) { + count++; + } + } + } + return count; +} + +SEASTAR_THREAD_TEST_CASE(test_relabel_add_labels) { + using namespace seastar::metrics; + namespace sm = seastar::metrics; + sm::metric_groups app_metrics; + app_metrics.add_group("test", { + sm::make_gauge("gauge_1", sm::description("gague 1"), [] { return 0; }), + sm::make_counter("counter_1", sm::description("counter 1"), [] { return 1; }) + }); + + std::vector<sm::relabel_config> rl(1); + rl[0].source_labels = {"__name__"}; + rl[0].target_label = "level"; + rl[0].replacement = "1"; + rl[0].expr = "test_counter_.*"; + + sm::metric_relabeling_result success = sm::set_relabel_configs(rl).get(); + BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0); + BOOST_CHECK_EQUAL(count_by_label("level"), 1); + app_metrics.add_group("test", { + sm::make_counter("counter_2", sm::description("counter 2"), [] { return 2; }) + }); + BOOST_CHECK_EQUAL(count_by_label("level"), 2); + sm::set_relabel_configs({}).get(); +} + +SEASTAR_THREAD_TEST_CASE(test_relabel_drop_label_prevent_runtime_conflicts) { + using namespace seastar::metrics; + namespace sm = seastar::metrics; + sm::metric_groups app_metrics; + app_metrics.add_group("test2", { + sm::make_gauge("gauge_1", sm::description("gague 1"), { sm::label_instance("g", "1")}, [] { return 0; }), + sm::make_counter("counter_1", sm::description("counter 1"), [] { return 0; }), + sm::make_counter("counter_1", sm::description("counter 1"), { sm::label_instance("lev", "2")}, [] { return 0; }) + }); + BOOST_CHECK_EQUAL(count_by_label("lev"), 1); + + std::vector<sm::relabel_config> rl(1); + rl[0].source_labels = {"lev"}; + rl[0].expr = "2"; + rl[0].target_label = "lev"; + rl[0].action = sm::relabel_config::relabel_action::drop_label; + // Dropping the lev label would cause a conflict, but not crash the system + sm::metric_relabeling_result success = sm::set_relabel_configs(rl).get(); + BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 1); + BOOST_CHECK_EQUAL(count_by_label("lev"), 0); + BOOST_CHECK_EQUAL(count_by_label("err"), 1); + + //reseting all the labels to their original state + success = sm::set_relabel_configs({}).get(); + BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0); + BOOST_CHECK_EQUAL(count_by_label("lev"), 1); + BOOST_CHECK_EQUAL(count_by_label("err"), 0); + sm::set_relabel_configs({}).get(); +} + +SEASTAR_THREAD_TEST_CASE(test_relabel_enable_disable_skip_when_empty) { + using namespace seastar::metrics; + namespace sm = seastar::metrics; + sm::metric_groups app_metrics; + app_metrics.add_group("test3", { + sm::make_gauge("gauge_1", sm::description("gague 1"), { sm::label_instance("lev3", "3")}, [] { return 0; }), + sm::make_counter("counter_1", sm::description("counter 1"), { sm::label_instance("lev3", "3")}, [] { return 0; }), + sm::make_counter("counter_2", sm::description("counter 2"), { sm::label_instance("lev3", "3")}, [] { return 0; }) + }); + std::vector<sm::relabel_config> rl(2); + rl[0].source_labels = {"__name__"}; + rl[0].action = sm::relabel_config::relabel_action::drop; + + rl[1].source_labels = {"lev3"}; + rl[1].expr = "3"; + rl[1].action = sm::relabel_config::relabel_action::keep; + // We just disable all metrics besides those mark as lev3 + sm::metric_relabeling_result success = sm::set_relabel_configs(rl).get(); + BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0); + BOOST_CHECK_EQUAL(count_by_label(""), 3); + BOOST_CHECK_EQUAL(count_by_fun([](const seastar::metrics::impl::metric_info& mi) { + return mi.should_skip_when_empty == sm::skip_when_empty::yes; + }), 0); + + std::vector<sm::relabel_config> rl2(3); + rl2[0].source_labels = {"__name__"}; + rl2[0].action = sm::relabel_config::relabel_action::drop; + + rl2[1].source_labels = {"lev3"}; + rl2[1].expr = "3"; + rl2[1].action = sm::relabel_config::relabel_action::keep; + + rl2[2].source_labels = {"__name__"}; + rl2[2].expr = "test3.*"; + rl2[2].action = sm::relabel_config::relabel_action::skip_when_empty; + + success = sm::set_relabel_configs(rl2).get(); + BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0); + BOOST_CHECK_EQUAL(count_by_label(""), 3); + BOOST_CHECK_EQUAL(count_by_fun([](const seastar::metrics::impl::metric_info& mi) { + return mi.should_skip_when_empty == sm::skip_when_empty::yes; + }), 3); + // clear the configuration + success = sm::set_relabel_configs({}).get(); + app_metrics.add_group("test3", { + sm::make_counter("counter_3", sm::description("counter 2"), { sm::label_instance("lev3", "3")}, [] { return 0; })(sm::skip_when_empty::yes) + }); + std::vector<sm::relabel_config> rl3(3); + rl3[0].source_labels = {"__name__"}; + rl3[0].action = sm::relabel_config::relabel_action::drop; + + rl3[1].source_labels = {"lev3"}; + rl3[1].expr = "3"; + rl3[1].action = sm::relabel_config::relabel_action::keep; + + rl3[2].source_labels = {"__name__"}; + rl3[2].expr = "test3.*"; + rl3[2].action = sm::relabel_config::relabel_action::report_when_empty; + + success = sm::set_relabel_configs(rl3).get(); + BOOST_CHECK_EQUAL(success.metrics_relabeled_due_to_collision, 0); + BOOST_CHECK_EQUAL(count_by_fun([](const seastar::metrics::impl::metric_info& mi) { + return mi.should_skip_when_empty == sm::skip_when_empty::yes; + }), 0); + sm::set_relabel_configs({}).get(); +} 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/mkmtls.gmk b/src/seastar/tests/unit/mkmtls.gmk new file mode 100644 index 000000000..d104eaeb1 --- /dev/null +++ b/src/seastar/tests/unit/mkmtls.gmk @@ -0,0 +1,29 @@ +server = $(shell hostname) +domain = $(shell dnsdomainname) +name = $(server) + +country = SE +state = Stockholm +locality= $(state) +org = $(domain) +unit = $(domain) +mail = mx +common = $(server).$(domain) +subj = "/C=$(country)/ST=$(state)/L=$(locality)/O=$(domain)/OU=$(domain)/CN=$(common)" +client1 = "/C=$(country)/ST=$(state)/L=$(locality)/O=$(domain)/OU=$(domain)/CN=client1.org" +client2 = "/C=$(country)/ST=$(state)/L=$(locality)/O=$(domain)/OU=$(domain)/CN=client2.org" +mtls_certs : + openssl ecparam -name prime256v1 -genkey -noout -out mtls_ca.key + openssl req -new -x509 -sha256 -key mtls_ca.key -out mtls_ca.crt -subj $(subj) + openssl ecparam -name prime256v1 -genkey -noout -out mtls_server.key + openssl req -new -sha256 -key mtls_server.key -out mtls_server.csr -subj $(subj) + openssl x509 -req -in mtls_server.csr -CA mtls_ca.crt -CAkey mtls_ca.key -CAcreateserial -out mtls_server.crt -days 1000 -sha256 + + openssl ecparam -name prime256v1 -genkey -noout -out mtls_client1.key + openssl req -new -sha256 -key mtls_client1.key -out mtls_client1.csr -subj $(client1) + openssl x509 -req -in mtls_client1.csr -CA mtls_ca.crt -CAkey mtls_ca.key -CAcreateserial -out mtls_client1.crt -days 1000 -sha256 + + openssl ecparam -name prime256v1 -genkey -noout -out mtls_client2.key + openssl req -new -sha256 -key mtls_client2.key -out mtls_client2.csr -subj $(client2) + openssl x509 -req -in mtls_client2.csr -CA mtls_ca.crt -CAkey mtls_ca.key -CAcreateserial -out mtls_client2.crt -days 1000 -sha256 + 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..5c262d6e5 --- /dev/null +++ b/src/seastar/tests/unit/network_interface_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) 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"); + +static_assert(std::is_nothrow_default_constructible_v<net::ethernet_address>); +static_assert(std::is_nothrow_copy_constructible_v<net::ethernet_address>); +static_assert(std::is_nothrow_move_constructible_v<net::ethernet_address>); + +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(); +} + +SEASTAR_TEST_CASE(is_standard_addresses_sanity) { + BOOST_REQUIRE_EQUAL(net::inet_address("127.0.0.1").is_loopback(), true); + BOOST_REQUIRE_EQUAL(net::inet_address("127.0.0.11").is_loopback(), true); + BOOST_REQUIRE_EQUAL(net::inet_address(::in_addr{INADDR_ANY}).is_addr_any(), true); + auto addr = net::inet_address("1.2.3.4"); + BOOST_REQUIRE_EQUAL(addr.is_loopback(), false); + BOOST_REQUIRE_EQUAL(addr.is_addr_any(), false); + + BOOST_REQUIRE_EQUAL(net::inet_address("::1").is_loopback(), true); + BOOST_REQUIRE_EQUAL(net::inet_address(::in6addr_any).is_addr_any(), true); + auto addr6 = net::inet_address("acf1:f5e5:5a99:337f:ebe2:c57e:0e27:69c6"); + BOOST_REQUIRE_EQUAL(addr6.is_loopback(), false); + BOOST_REQUIRE_EQUAL(addr6.is_addr_any(), false); + + 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..a6dccd89c --- /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 { + output_stream_options opts; + size_t _size; + + stream_maker size(size_t size) && { + _size = size; + return std::move(*this); + } + + stream_maker trim(bool do_trim) && { + opts.trim_to_size = 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, opts); + } +}; + +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/pipe_test.cc b/src/seastar/tests/unit/pipe_test.cc new file mode 100644 index 000000000..c44279814 --- /dev/null +++ b/src/seastar/tests/unit/pipe_test.cc @@ -0,0 +1,51 @@ +/* + * This file is open source software, licensed to you under the terms + * of the Apache License, Version 2.0 (the "License"). See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. You may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Copyright 2021-present ScyllaDB + */ + +#include <seastar/testing/test_case.hh> +#include <seastar/testing/thread_test_case.hh> + +#include <seastar/core/pipe.hh> + +using namespace seastar; + +static_assert(!std::is_default_constructible_v<seastar::pipe_reader<int>>); +static_assert(!std::is_default_constructible_v<seastar::pipe_writer<int>>); + +static_assert(std::is_nothrow_move_constructible_v<seastar::pipe_reader<int>>); +static_assert(std::is_nothrow_move_assignable_v<seastar::pipe_reader<int>>); + +static_assert(std::is_nothrow_move_constructible_v<seastar::pipe_writer<int>>); +static_assert(std::is_nothrow_move_assignable_v<seastar::pipe_writer<int>>); + +SEASTAR_THREAD_TEST_CASE(simple_pipe_test) { + seastar::pipe<int> p(1); + + auto f0 = p.reader.read(); + BOOST_CHECK(!f0.available()); + p.writer.write(17).get(); + BOOST_REQUIRE_EQUAL(*f0.get0(), 17); + + p.writer.write(42).get(); + auto f2 = p.reader.read(); + BOOST_CHECK(f2.available()); + BOOST_REQUIRE_EQUAL(*f2.get0(), 42); +} 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..4ade98385 --- /dev/null +++ b/src/seastar/tests/unit/queue_test.cc @@ -0,0 +1,163 @@ +/* + * 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/testing/thread_test_case.hh> +#include <seastar/core/queue.hh> +#include <seastar/core/thread.hh> +#include <seastar/core/sleep.hh> +#include <seastar/core/loop.hh> +#include <seastar/core/future-util.hh> +#include <seastar/util/log.hh> +#include <seastar/util/alloc_failure_injector.hh> + +using namespace seastar; +using namespace std::chrono_literals; + +static seastar::logger testlog("testlog"); + +SEASTAR_THREAD_TEST_CASE(test_queue_pop_eventually) { + queue<int> q(100); + int pushed = 0; + bool pusher_done = false; + int popped = 0; + bool popper_done = false; + int stop = 0; + auto test_duration = 1ms; + auto watchdog_duration = 10s; + timer stop_timer; + stop_timer.set_callback([&] { + testlog.debug("stop_timer: pushed={} pusher_done={} popped={} popper_done={} full={} empty={} stop={}", + pushed, pusher_done, popped, popper_done, + q.full(), q.empty(), stop); + if (!stop++) { + // First callback is for stopping the test. + // Second one is a watchdog. Consider the test hung + // if this callback isn't canceled within 10 seconds. + // That should be long enough to work in absurdly slow test environments. + stop_timer.arm(watchdog_duration); + } else { + testlog.error("test_queue_pop_eventually is hung: pushed={} pusher_done={} popped={} popper_done={} full={} empty={} stop={}", + pushed, pusher_done, popped, popper_done, + q.full(), q.empty(), stop); + abort(); + } + }); + stop_timer.arm(test_duration); + auto start = std::chrono::system_clock::now(); + auto pusher = repeat([&] { + auto&& data = pushed; + testlog.trace("pusher: full={} empty={} stop={}", q.full(), q.empty(), stop); + return q.push_eventually(std::move(data)).then([&] { + pushed++; + if (stop && !q.empty()) { + testlog.debug("pusher done"); + pusher_done = true; + return stop_iteration::yes; + } + return stop_iteration::no; + }); + }); + auto popper = repeat([&] { + testlog.trace("popper: full={} empty={} stop={}", q.full(), q.empty(), stop); + if (q.empty()) { + if (pusher_done) { + testlog.debug("popper done"); + popper_done = true; + return make_ready_future<stop_iteration>(true); + } else if (stop) { + testlog.debug("popper: full={} empty={} pusher_done={} stop={}", q.full(), q.empty(), pusher_done, stop); + } + } + return q.pop_eventually().then([&] (int&&) { + popped++; + return stop_iteration::no; + }); + }); + pusher.get(); + popper.get(); + auto elapsed = std::chrono::system_clock::now() - start; + auto elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count(); + stop_timer.cancel(); + BOOST_REQUIRE(q.empty()); + BOOST_REQUIRE(pushed); + BOOST_REQUIRE(pusher_done); + BOOST_REQUIRE(popped == pushed); + BOOST_REQUIRE(popper_done); + testlog.info("Pushed and popped {} elemements in {}us, {:.3f} elements/us", pushed, elapsed_us, double(pushed) / elapsed_us); +} + +#ifdef SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION +SEASTAR_THREAD_TEST_CASE(test_queue_push_eventually_exception) { + int i = 0; + queue<int> q(42); + int intercepted = 0; + + memory::with_allocation_failures([&] { + BOOST_REQUIRE_NO_THROW(q.push_eventually(i++).handle_exception_type([&] (std::bad_alloc&) { + intercepted++; + }).get()); + }); + BOOST_REQUIRE(intercepted); +} +#endif + +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..a122a6b3d --- /dev/null +++ b/src/seastar/tests/unit/rpc_test.cc @@ -0,0 +1,1457 @@ +/* + * 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> +#include <seastar/util/closeable.hh> +#include <seastar/util/noncopyable_function.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 ()>; + +class rpc_loopback_error_injector : public loopback_error_injector { +public: + struct config { + struct { + int limit = 0; + error kind = error::none; + private: + friend class rpc_loopback_error_injector; + int _x = 0; + error inject() { + return _x++ >= limit ? kind : error::none; + } + } server_rcv = {}, server_snd = {}, client_rcv = {}, client_snd = {}; + error connect_kind = error::none; + }; +private: + config _cfg; +public: + rpc_loopback_error_injector(config cfg) : _cfg(std::move(cfg)) {} + + error server_rcv_error() override { + return _cfg.server_rcv.inject(); + } + + error server_snd_error() override { + return _cfg.server_snd.inject(); + } + + error client_rcv_error() override { + return _cfg.client_rcv.inject(); + } + + error client_snd_error() override { + return _cfg.client_snd.inject(); + } + + error connect_error() override { + return _cfg.connect_kind; + } +}; + +class rpc_socket_impl : public ::net::socket_impl { + rpc_loopback_error_injector _error_injector; + loopback_socket_impl _socket; +public: + rpc_socket_impl(loopback_connection_factory& factory, std::optional<rpc_loopback_error_injector::config> inject_error) + : + _error_injector(inject_error.value_or(rpc_loopback_error_injector::config{})), + _socket(factory, inject_error ? &_error_injector : nullptr) { + } + virtual future<connected_socket> connect(socket_address sa, socket_address local, transport proto = transport::TCP) override { + return _socket.connect(sa, local, proto); + } + virtual void set_reuseaddr(bool reuseaddr) override {} + virtual bool get_reuseaddr() const override { return false; }; + virtual void shutdown() override { + _socket.shutdown(); + } +}; + +struct rpc_test_config { + rpc::resource_limits resource_limits = {}; + rpc::server_options server_options = {}; + std::optional<rpc_loopback_error_injector::config> inject_error; +}; + +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 = deferred_stop(cl); + 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.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; + rpc_loopback_error_injector::config ecfg; + ecfg.connect_kind = loopback_error_injector::error::abort; + cfg.inject_error = ecfg; + 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); + auto fut = f(c1); + c1.stop().get0(); + try { + fut.get0(); + BOOST_REQUIRE(false); + } catch (...) {} + 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); + promise<> cont; + rpc::cancellable cancel; + c1.suspend_for_testing(cont); + auto f = call(c1, cancel); + // cancel send side + cancel.cancel(); + cont.set_value(); + 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); + }); +} + +SEASTAR_TEST_CASE(test_rpc_remote_verb_error) { + rpc_test_config cfg; + 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, []() { throw std::runtime_error("error"); }).get(); + auto f = env.proto().make_client<void ()>(1); + BOOST_REQUIRE_THROW(f(c1).get0(), rpc::remote_verb_error); + c1.stop().get0(); + }); +} + +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; + bool exception_while_creating_sink = false; +}; + +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); + struct failed_to_create_sync{}; + auto x = [&] { + try { + return c.make_stream_sink<serializer, int>(env.make_socket()).get0(); + } catch (...) { + c.stop().get(); + throw failed_to_create_sync{}; + } + }; + try { + 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; + } catch(failed_to_create_sync&) { + r.exception_while_creating_sink = true; + 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; + rpc_loopback_error_injector::config ecfg; + ecfg.server_rcv.limit = 50; + ecfg.server_rcv.kind = loopback_error_injector::error::abort; + cfg.inject_error = ecfg; + 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_stream_negotiation_error) { + rpc::server_options so; + so.streaming_domain = rpc::streaming_domain_type(1); + rpc_test_config cfg; + cfg.server_options = so; + rpc_loopback_error_injector::config ecfg; + ecfg.server_rcv.limit = 0; + ecfg.server_rcv.kind = loopback_error_injector::error::abort; + cfg.inject_error = ecfg; + 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.exception_while_creating_sink); + }); + }); +} + +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([&] () noexcept { destroy_scheduling_group(sg1).get(); }); + auto sg2 = create_scheduling_group("sg2", 100).get0(); + auto sg2_kill = defer([&] () noexcept { 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([&] () noexcept { destroy_scheduling_group(sg1).get(); }); + auto sg2 = create_scheduling_group("sg2", 100).get0(); + auto sg2_kill = defer([&] () noexcept { 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(); +} + +SEASTAR_THREAD_TEST_CASE(test_rpc_scheduling_connection_based_async) { + scheduling_group sg1 = default_scheduling_group(); + scheduling_group sg2 = default_scheduling_group(); + auto sg1_kill = defer([&] () noexcept { + if (sg1 != default_scheduling_group()) { + destroy_scheduling_group(sg1).get(); + } + }); + auto sg2_kill = defer([&] () noexcept { + if (sg2 != default_scheduling_group()) { + destroy_scheduling_group(sg2).get(); + } + }); + rpc::resource_limits limits; + limits.isolate_connection = [&sg1, &sg2] (sstring cookie) { + future<seastar::scheduling_group> get_scheduling_group = make_ready_future<>().then([&sg1, &sg2, cookie] { + if (cookie == "sg1") { + if (sg1 == default_scheduling_group()) { + return create_scheduling_group("sg1", 100).then([&sg1] (seastar::scheduling_group sg) { + sg1 = sg; + return sg; + }); + } else { + return make_ready_future<seastar::scheduling_group>(sg1); + } + } else if (cookie == "sg2") { + if (sg2 == default_scheduling_group()) { + return create_scheduling_group("sg2", 100).then([&sg2] (seastar::scheduling_group sg) { + sg2 = sg; + return sg; + }); + } else { + return make_ready_future<seastar::scheduling_group>(sg2); + } + } + return make_ready_future<seastar::scheduling_group>(current_scheduling_group()); + }); + + return get_scheduling_group.then([] (scheduling_group sg) { + 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_async) { + scheduling_group sg1 = default_scheduling_group(); + scheduling_group sg2 = default_scheduling_group(); + scheduling_group sg3 = create_scheduling_group("sg3", 100).get0(); + auto sg1_kill = defer([&] () noexcept { + if (sg1 != default_scheduling_group()) { + destroy_scheduling_group(sg1).get(); + } + }); + auto sg2_kill = defer([&] () noexcept { + if (sg2 != default_scheduling_group()) { + destroy_scheduling_group(sg2).get(); + } + }); + auto sg3_kill = defer([&] () noexcept { destroy_scheduling_group(sg3).get(); }); + rpc::resource_limits limits; + limits.isolate_connection = [&sg1, &sg2] (sstring cookie) { + future<seastar::scheduling_group> get_scheduling_group = make_ready_future<>().then([&sg1, &sg2, cookie] { + if (cookie == "sg1") { + if (sg1 == default_scheduling_group()) { + return create_scheduling_group("sg1", 100).then([&sg1] (seastar::scheduling_group sg) { + sg1 = sg; + return sg; + }); + } else { + return make_ready_future<seastar::scheduling_group>(sg1); + } + } else if (cookie == "sg2") { + if (sg2 == default_scheduling_group()) { + return create_scheduling_group("sg2", 100).then([&sg2] (seastar::scheduling_group sg) { + sg2 = sg; + return sg; + }); + } else { + return make_ready_future<seastar::scheduling_group>(sg2); + } + } + return make_ready_future<seastar::scheduling_group>(current_scheduling_group()); + }); + + return get_scheduling_group.then([] (scheduling_group sg) { + 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, &sg3] (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 sg3 if the client is old + env.register_handler(1, sg3, [] () { + 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(sg3)); + 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<noncopyable_function<std::tuple<sstring, size_t, snd_buf> ()>> inputs; + + auto add_input = [&] (auto func_returning_tuple) { + inputs.emplace_back(std::move(func_returning_tuple)); + }; + + auto& eng = testing::local_random_engine; + auto dist = std::uniform_int_distribution<int>(0, std::numeric_limits<char>::max()); + + auto snd = snd_buf(1); + *snd.front().get_write() = 'a'; + add_input([snd = std::move(snd)] () mutable { return std::tuple(sstring("one byte, no headroom"), size_t(0), std::move(snd)); }); + + snd = snd_buf(1); + *snd.front().get_write() = 'a'; + add_input([snd = std::move(snd)] () mutable { return std::tuple(sstring("one byte, 128k of headroom"), size_t(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(); + } + return std::tuple(msg, size_t(0), std::move(snd)); + }; + + + add_input(std::bind(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(); + } + return std::tuple(msg, size_t(0), std::move(snd)); + }; + + add_input(std::bind(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(); + } + return std::tuple(msg, size_t(0), std::move(snd)); + }; + +#ifndef SEASTAR_DEBUG + auto buffer_sizes = { 1, 4 }; +#else + auto buffer_sizes = { 1 }; +#endif + + for (auto s : buffer_sizes) { + for (auto ss : { 32, 64, 128, 48, 56, 246, 511 }) { + add_input(std::bind(gen_fill, s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} kB - {}", s, ss, ss), ss * 1024 - ss)); + add_input(std::bind(gen_fill, s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} kB", s, ss), ss * 1024)); + add_input(std::bind(gen_rand, s * 1024 * 1024, format("{} MB buffer of random split into {} kB", s, ss), ss * 1024)); + + add_input(std::bind(gen_fill, s * 1024 * 1024 + 1, format("{} MB + 1B buffer of \'a\' split into {} kB", s, ss), ss * 1024)); + add_input(std::bind(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 }) { + add_input(std::bind(gen_fill, s * 1024 * 1024, format("{} MB buffer of \'a\' split into {} B", s, ss), ss)); + add_input(std::bind(gen_rand, s * 1024 * 1024, format("{} MB buffer of random split into {} B", s, ss), ss)); + add_input(std::bind(gen_text, s * 1024 * 1024, format("{} MB buffer of text split into {} B", s, ss), ss)); + add_input(std::bind(gen_fill, s * 1024 * 1024 - ss, format("{} MB - {}B buffer of \'a\' split into {} B", s, ss, ss), ss)); + add_input(std::bind(gen_rand, s * 1024 * 1024 - ss, format("{} MB - {}B buffer of random split into {} B", s, ss, ss), ss)); + add_input(std::bind(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 }) { + add_input(std::bind(gen_fill, s, format("{} bytes buffer of \'a\'", s))); + add_input(std::bind(gen_rand, s, format("{} bytes buffer of random", s))); + add_input(std::bind(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_func : inputs) { + auto in = in_func(); + 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; + rpc_loopback_error_injector::config ecfg; + ecfg.connect_kind = loopback_error_injector::error::abort; + cfg.inject_error = ecfg; + 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"); + }); +} + +SEASTAR_TEST_CASE(test_connection_id_format) { + rpc::connection_id cid = rpc::connection_id::make_id(0x123, 1); + std::string res = format("{}", cid); + BOOST_REQUIRE_EQUAL(res, "1230001"); + return make_ready_future<>(); +} + +static_assert(std::is_same_v<decltype(rpc::tuple(1U, 1L)), rpc::tuple<unsigned, long>>, "rpc::tuple deduction guid not working"); + +SEASTAR_TEST_CASE(test_client_info) { + rpc::client_info info; + const rpc::client_info& const_info = *const_cast<rpc::client_info*>(&info); + + info.attach_auxiliary("key", 0); + BOOST_REQUIRE_EQUAL(const_info.retrieve_auxiliary<int>("key"), 0); + info.retrieve_auxiliary<int>("key") = 1; + BOOST_REQUIRE_EQUAL(const_info.retrieve_auxiliary<int>("key"), 1); + + BOOST_REQUIRE_EQUAL(info.retrieve_auxiliary_opt<int>("key"), &info.retrieve_auxiliary<int>("key")); + BOOST_REQUIRE_EQUAL(const_info.retrieve_auxiliary_opt<int>("key"), &const_info.retrieve_auxiliary<int>("key")); + + BOOST_REQUIRE_EQUAL(info.retrieve_auxiliary_opt<int>("missing"), nullptr); + BOOST_REQUIRE_EQUAL(const_info.retrieve_auxiliary_opt<int>("missing"), nullptr); + + return make_ready_future<>(); +} 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..341d74ec4 --- /dev/null +++ b/src/seastar/tests/unit/scheduling_group_test.cc @@ -0,0 +1,284 @@ +/* + * 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/core/reactor.hh> +#include <seastar/util/later.hh> +#include <seastar/util/defer.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] () noexcept { + 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] () noexcept { + 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] () noexcept { + 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(); + auto cleanup = defer([&] () noexcept { destroy_scheduling_group(sg).get(); }); + 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(yield_preserves_sg) { + scheduling_group sg = create_scheduling_group("sg", 100).get0(); + auto cleanup = defer([&] () noexcept { destroy_scheduling_group(sg).get(); }); + with_scheduling_group(sg, [&] { + return yield().then([&] { + BOOST_REQUIRE_EQUAL( + internal::scheduling_group_index(current_scheduling_group()), + internal::scheduling_group_index(sg)); + }); + }).get(); +} + +SEASTAR_THREAD_TEST_CASE(sg_count) { + class scheduling_group_destroyer { + scheduling_group _sg; + public: + scheduling_group_destroyer(scheduling_group sg) : _sg(sg) {} + ~scheduling_group_destroyer() { + destroy_scheduling_group(_sg).get(); + } + }; + + std::vector<scheduling_group_destroyer> scheduling_groups_deferred_cleanup; + // The line below is necessary in order to skip support of copy and move construction of scheduling_group_destroyer. + scheduling_groups_deferred_cleanup.reserve(max_scheduling_groups()); + // try to create 3 groups too many. + for (auto i = internal::scheduling_group_count(); i < max_scheduling_groups() + 3; i++) { + try { + BOOST_REQUIRE_LE(internal::scheduling_group_count(), max_scheduling_groups()); + scheduling_groups_deferred_cleanup.emplace_back(create_scheduling_group(format("sg_{}", i), 10).get()); + } catch (std::runtime_error& e) { + // make sure it is the right exception. + BOOST_REQUIRE_EQUAL(e.what(), fmt::format("Scheduling group limit exceeded while creating sg_{}", i)); + // make sure that the scheduling group count makes sense + BOOST_REQUIRE_EQUAL(internal::scheduling_group_count(), max_scheduling_groups()); + // make sure that we expect this exception at this point + BOOST_REQUIRE_GE(i, max_scheduling_groups()); + } + } + BOOST_REQUIRE_EQUAL(internal::scheduling_group_count(), max_scheduling_groups()); +} diff --git a/src/seastar/tests/unit/semaphore_test.cc b/src/seastar/tests/unit/semaphore_test.cc new file mode 100644 index 000000000..5213c8763 --- /dev/null +++ b/src/seastar/tests/unit/semaphore_test.cc @@ -0,0 +1,446 @@ +/* + * 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_THREAD_TEST_CASE(test_default_broken_semaphore) { + struct test_semaphore_exception_factory { + static semaphore_timed_out timeout() noexcept { return semaphore_timed_out(); } + }; + auto sem = basic_semaphore<test_semaphore_exception_factory>(0); + auto fut = sem.wait(); + BOOST_REQUIRE(!fut.available()); + sem.broken(); + BOOST_REQUIRE_THROW(fut.get(), broken_semaphore); + BOOST_REQUIRE_THROW(sem.wait().get(), broken_semaphore); +} + +SEASTAR_THREAD_TEST_CASE(test_non_default_broken_semaphore) { + struct test_semaphore_exception_factory { + static semaphore_timed_out timeout() noexcept { return semaphore_timed_out(); } + }; + auto sem = basic_semaphore<test_semaphore_exception_factory>(0); + auto fut = sem.wait(); + BOOST_REQUIRE(!fut.available()); + sem.broken(std::runtime_error("test")); + BOOST_REQUIRE_THROW(fut.get(), std::runtime_error); + BOOST_REQUIRE_THROW(sem.wait().get(), std::runtime_error); +} + +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(3); + auto units = get_units(sm, 3, 1min).get0(); + { + BOOST_REQUIRE_EQUAL(units.count(), 3); + BOOST_REQUIRE_EQUAL(sm.available_units(), 0); + auto split = units.split(1); + BOOST_REQUIRE_EQUAL(sm.available_units(), 0); + BOOST_REQUIRE_EQUAL(units.count(), 2); + BOOST_REQUIRE_EQUAL(split.count(), 1); + } + BOOST_REQUIRE_EQUAL(sm.available_units(), 1); + units.~semaphore_units(); + units = get_units(sm, 3, 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_semaphore_try_get_units) { + constexpr size_t initial_units = 1; + auto sm = semaphore(initial_units); + + auto opt_units = try_get_units(sm, 1); + BOOST_REQUIRE(opt_units); + + auto opt_units2 = try_get_units(sm, 1); + BOOST_REQUIRE(!opt_units2); + + opt_units.reset(); + BOOST_REQUIRE_EQUAL(sm.available_units(), initial_units); + + opt_units = try_get_units(sm, 1); + BOOST_REQUIRE(opt_units); + + opt_units->return_all(); + BOOST_REQUIRE_EQUAL(opt_units->count(), 0); + BOOST_REQUIRE_EQUAL(sm.available_units(), initial_units); +} + +SEASTAR_THREAD_TEST_CASE(test_semaphore_units_abort) { + auto sm = semaphore(3); + auto units = get_units(sm, 3, 1min).get0(); + BOOST_REQUIRE_EQUAL(units.count(), 3); + + abort_source as; + + auto f = get_units(sm, 1, as); + BOOST_REQUIRE(!f.available()); + + (void)sleep(1ms).then([&as] { + as.request_abort(); + }); + + BOOST_REQUIRE_THROW(f.get(), semaphore_aborted); +} + +SEASTAR_THREAD_TEST_CASE(test_semaphore_units_bool_operator) { + auto sem = semaphore(2); + semaphore_units u0; + BOOST_REQUIRE(!bool(u0)); + + u0 = get_units(sem, 2).get0(); + BOOST_REQUIRE(bool(u0)); + + u0.return_units(1); + BOOST_REQUIRE(bool(u0)); + + auto n = u0.release(); + BOOST_REQUIRE(!bool(u0)); + sem.signal(n); + + u0 = get_units(sem, 2).get0(); + BOOST_REQUIRE(bool(u0)); + auto u1 = std::move(u0); + BOOST_REQUIRE(bool(u1)); + BOOST_REQUIRE(!bool(u0)); + + u1.return_all(); + BOOST_REQUIRE(!bool(u1)); + + u0 = get_units(sem, 2).get0(); + BOOST_REQUIRE(bool(u0)); + u1 = u0.split(1); + BOOST_REQUIRE(bool(u1)); + BOOST_REQUIRE(bool(u0)); + + u0.adopt(std::move(u1)); + BOOST_REQUIRE(!bool(u1)); + BOOST_REQUIRE(bool(u0)); +} + +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"); + } +} + +SEASTAR_THREAD_TEST_CASE(test_semaphore_abort_after_wait) { + auto sem = semaphore(0); + abort_source as; + int x = 0; + auto fut1 = sem.wait(as).then([&x] { + x++; + }); + as.request_abort(); + sem.signal(); + BOOST_CHECK_THROW(fut1.get(), semaphore_aborted); + BOOST_REQUIRE_EQUAL(x, 0); +} + +SEASTAR_THREAD_TEST_CASE(test_semaphore_abort_before_wait) { + auto sem = semaphore(0); + abort_source as; + int x = 0; + as.request_abort(); + auto fut1 = sem.wait(as).then([&x] { + x++; + }); + sem.signal(); + BOOST_CHECK_THROW(fut1.get(), semaphore_aborted); + BOOST_REQUIRE_EQUAL(x, 0); +} + +SEASTAR_THREAD_TEST_CASE(test_semaphore_move_with_outstanding_units) { + auto sem0 = semaphore(1); + auto sem = std::make_unique<semaphore>(std::move(sem0)); + auto units = std::make_unique<semaphore_units<>>(get_units(*sem, 1).get()); + BOOST_REQUIRE_EQUAL(sem->current(), 0); + auto sem1 = std::move(sem); + BOOST_REQUIRE_EQUAL(sem1->current(), 0); + units.reset(); + BOOST_REQUIRE_EQUAL(sem1->current(), 1); +} diff --git a/src/seastar/tests/unit/sharded_test.cc b/src/seastar/tests/unit/sharded_test.cc new file mode 100644 index 000000000..2b39faee6 --- /dev/null +++ b/src/seastar/tests/unit/sharded_test.cc @@ -0,0 +1,203 @@ +/* + * 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 peering_counter : public peering_sharded_service<peering_counter> { +public: + future<int> count() const { + return container().map_reduce(adder<int>(), [] (auto& pc) { return 1; }); + } + + future<int> count_from(int base) const { + return container().map_reduce0([] (auto& pc) { return 1; }, base, std::plus<int>()); + } + + future<int> count_from_const(int base) const { + return container().map_reduce0(&peering_counter::get_1_c, base, std::plus<int>()); + } + + future<int> count_from_mutate(int base) { + return container().map_reduce0(&peering_counter::get_1_m, base, std::plus<int>()); + } + + future<int> count_const() const { + return container().map_reduce(adder<int>(), &peering_counter::get_1_c); + } + + future<int> count_mutate() { + return container().map_reduce(adder<int>(), &peering_counter::get_1_m); + } + +private: + future<int> get_1_c() const { + return make_ready_future<int>(1); + } + + future<int> get_1_m() { + return make_ready_future<int>(1); + } +}; + +SEASTAR_THREAD_TEST_CASE(test_const_map_reduces) { + sharded<peering_counter> c; + c.start().get(); + + BOOST_REQUIRE_EQUAL(c.local().count().get0(), smp::count); + BOOST_REQUIRE_EQUAL(c.local().count_from(1).get0(), smp::count + 1); + + c.stop().get(); +} + +SEASTAR_THREAD_TEST_CASE(test_member_map_reduces) { + sharded<peering_counter> c; + c.start().get(); + + BOOST_REQUIRE_EQUAL(std::as_const(c.local()).count_const().get0(), smp::count); + BOOST_REQUIRE_EQUAL(c.local().count_mutate().get0(), smp::count); + BOOST_REQUIRE_EQUAL(std::as_const(c.local()).count_from_const(1).get0(), smp::count + 1); + BOOST_REQUIRE_EQUAL(c.local().count_from_mutate(1).get0(), smp::count + 1); + c.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(); +} + +class argument { + int _x; +public: + argument() : _x(this_shard_id()) {} + int get() const { return _x; } +}; + +class service { +public: + void fn_local(argument& arg) { + BOOST_REQUIRE_EQUAL(arg.get(), this_shard_id()); + } + + void fn_sharded(sharded<argument>& arg) { + BOOST_REQUIRE_EQUAL(arg.local().get(), this_shard_id()); + } + + void fn_sharded_param(int arg) { + BOOST_REQUIRE_EQUAL(arg, this_shard_id()); + } +}; + +SEASTAR_THREAD_TEST_CASE(invoke_on_all_sharded_arg) { + seastar::sharded<service> srv; + srv.start().get(); + seastar::sharded<argument> arg; + arg.start().get(); + + srv.invoke_on_all(&service::fn_local, std::ref(arg)).get(); + srv.invoke_on_all(&service::fn_sharded, std::ref(arg)).get(); + srv.invoke_on_all(&service::fn_sharded_param, sharded_parameter([&arg] { return arg.local().get(); })).get(); + + srv.stop().get(); + arg.stop().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..475378eda --- /dev/null +++ b/src/seastar/tests/unit/shared_ptr_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 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>(); +} + +BOOST_AUTO_TEST_CASE(test_nullptr_compare) { + seastar::shared_ptr<int> ptr; + BOOST_REQUIRE(ptr == nullptr); + BOOST_REQUIRE(nullptr == ptr); + ptr = seastar::make_shared<int>(0); + BOOST_REQUIRE(ptr != nullptr); + BOOST_REQUIRE(nullptr != ptr); + + seastar::lw_shared_ptr<int> lptr; + BOOST_REQUIRE(lptr == nullptr); + BOOST_REQUIRE(nullptr == lptr); + lptr = seastar::make_lw_shared<int>(0); + BOOST_REQUIRE(lptr != nullptr); + BOOST_REQUIRE(nullptr != lptr); +} diff --git a/src/seastar/tests/unit/shared_token_bucket_test.cc b/src/seastar/tests/unit/shared_token_bucket_test.cc new file mode 100644 index 000000000..9a2d98edd --- /dev/null +++ b/src/seastar/tests/unit/shared_token_bucket_test.cc @@ -0,0 +1,73 @@ +/* + * 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) 2022 ScyllaDB + */ + +#include <seastar/core/thread.hh> +#include <seastar/core/manual_clock.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/util/shared_token_bucket.hh> + +using namespace seastar; +using namespace std::chrono_literals; + +SEASTAR_TEST_CASE(test_basic_non_capped_loop) { + internal::shared_token_bucket<uint64_t, std::ratio<1>, internal::capped_release::no, manual_clock> tb(1, 1, 0, false); + + // Grab one token and make sure it's only available in 1s + auto th = tb.grab(1); + BOOST_REQUIRE(tb.deficiency(th) > 0); + manual_clock::advance(1s); + tb.replenish(manual_clock::now()); + BOOST_REQUIRE(tb.deficiency(th) == 0); + + // Grab one more token and check the same + th = tb.grab(1); + BOOST_REQUIRE(tb.deficiency(th) > 0); + manual_clock::advance(1s); + tb.replenish(manual_clock::now()); + BOOST_REQUIRE(tb.deficiency(th) == 0); + + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(test_basic_capped_loop) { + internal::shared_token_bucket<uint64_t, std::ratio<1>, internal::capped_release::yes, manual_clock> tb(1, 1, 0, false); + + // Grab on token and make sure it's only available in 1s + auto th = tb.grab(1); + BOOST_REQUIRE(tb.deficiency(th) > 0); + manual_clock::advance(1s); + tb.replenish(manual_clock::now()); + BOOST_REQUIRE(tb.deficiency(th) == 0); + + // The 2nd time this trick only works after the 1st token is explicitly released + th = tb.grab(1); + BOOST_REQUIRE(tb.deficiency(th) > 0); + manual_clock::advance(1s); + tb.replenish(manual_clock::now()); + BOOST_REQUIRE(tb.deficiency(th) > 0); + manual_clock::advance(1s); + tb.release(1); + tb.replenish(manual_clock::now()); + BOOST_REQUIRE(tb.deficiency(th) == 0); + + return make_ready_future<>(); +} 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..f705a4afb --- /dev/null +++ b/src/seastar/tests/unit/socket_test.cc @@ -0,0 +1,226 @@ +/* + * 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/util/later.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/core/abort_source.hh> +#include <seastar/core/sleep.hh> +#include <seastar/core/thread.hh> +#include <seastar/core/when_all.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}; + +SEASTAR_TEST_CASE(socket_allocation_test) { + return echo_server_loop().finally([](){ engine().exit((malloc_allocator.allocs == malloc_allocator.frees) ? 0 : 1); }); +} + +SEASTAR_TEST_CASE(socket_skip_test) { + return seastar::async([&] { + listen_options lo; + lo.reuse_address = true; + server_socket ss = seastar::listen(ipv4_addr("127.0.0.1", 1234), lo); + + abort_source as; + auto client = async([&as] { + connected_socket socket = connect(ipv4_addr("127.0.0.1", 1234)).get(); + socket.output().write("abc").get(); + socket.shutdown_output(); + try { + sleep_abortable(std::chrono::seconds(10), as).get(); + } catch (const sleep_aborted&) { + // expected + return; + } + assert(!"Skipping data from socket is likely stuck"); + }); + + accept_result accepted = ss.accept().get(); + input_stream<char> input = accepted.connection.input(); + input.skip(16).get(); + as.request_abort(); + client.get(); + }); +} + +SEASTAR_TEST_CASE(test_file_desc_fdinfo) { + auto fd = file_desc::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + auto info = fd.fdinfo(); + BOOST_REQUIRE_EQUAL(info.substr(0, 8), "socket:["); + return make_ready_future<>(); +} + +SEASTAR_TEST_CASE(socket_on_close_test) { + return seastar::async([&] { + listen_options lo; + lo.reuse_address = true; + server_socket ss = seastar::listen(ipv4_addr("127.0.0.1", 12345), lo); + + bool server_closed = false; + bool client_notified = false; + + auto client = seastar::async([&] { + connected_socket cln = connect(ipv4_addr("127.0.0.1", 12345)).get0(); + + auto close_wait_fiber = cln.wait_input_shutdown().then([&] { + BOOST_REQUIRE_EQUAL(server_closed, true); + client_notified = true; + fmt::print("Client: server closed\n"); + }); + + auto out = cln.output(); + auto in = cln.input(); + + while (!client_notified) { + fmt::print("Client: -> message\n"); + out.write("hello").get(); + out.flush().get(); + seastar::sleep(std::chrono::milliseconds(250)).get(); + fmt::print("Client: <- message\n"); + auto buf = in.read().get0(); + if (!buf) { + fmt::print("Client: server eof\n"); + break; + } + seastar::sleep(std::chrono::milliseconds(250)).get(); + } + + out.close().get(); + in.close().get(); + close_wait_fiber.get(); + }); + + auto server = seastar::async([&] { + accept_result acc = ss.accept().get0(); + auto out = acc.connection.output(); + auto in = acc.connection.input(); + + for (int i = 0; i < 3; i++) { + auto buf = in.read().get(); + BOOST_REQUIRE_EQUAL(client_notified, false); + out.write(std::move(buf)).get(); + out.flush().get(); + fmt::print("Server: served\n"); + } + + server_closed = true; + fmt::print("Server: closing\n"); + out.close().get(); + in.close().get(); + }); + + when_all(std::move(client), std::move(server)).discard_result().get(); + }); +} + +SEASTAR_TEST_CASE(socket_on_close_local_shutdown_test) { + return seastar::async([&] { + listen_options lo; + lo.reuse_address = true; + server_socket ss = seastar::listen(ipv4_addr("127.0.0.1", 12345), lo); + + bool server_closed = false; + bool client_notified = false; + + auto client = seastar::async([&] { + connected_socket cln = connect(ipv4_addr("127.0.0.1", 12345)).get0(); + + auto close_wait_fiber = cln.wait_input_shutdown().then([&] { + BOOST_REQUIRE_EQUAL(server_closed, false); + client_notified = true; + fmt::print("Client: socket closed\n"); + }); + + auto out = cln.output(); + cln.shutdown_input(); + + auto fin = std::chrono::steady_clock::now() + std::chrono::seconds(1); + do { + seastar::yield().get(); + } while (!client_notified && std::chrono::steady_clock::now() < fin); + BOOST_REQUIRE_EQUAL(client_notified, true); + + out.write("hello").get(); + out.flush().get(); + out.close().get(); + + close_wait_fiber.get(); + }); + + auto server = seastar::async([&] { + accept_result acc = ss.accept().get0(); + auto in = acc.connection.input(); + auto buf = in.read().get(); + server_closed = true; + fmt::print("Server: closing\n"); + in.close().get(); + }); + + when_all(std::move(client), std::move(server)).discard_result().get(); + }); +} diff --git a/src/seastar/tests/unit/source_location_test.cc b/src/seastar/tests/unit/source_location_test.cc new file mode 100644 index 000000000..d0805fef6 --- /dev/null +++ b/src/seastar/tests/unit/source_location_test.cc @@ -0,0 +1,43 @@ +/* + * 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) 2021 ScyllaDB + */ + +#define BOOST_TEST_MODULE core + +#include <boost/test/included/unit_test.hpp> + +#include <seastar/util/std-compat.hh> + +using namespace seastar; + +static void test_source_location_callee(const char* ref_file, const char* ref_func, int ref_line, compat::source_location loc = compat::source_location::current()) { + BOOST_REQUIRE_EQUAL(loc.file_name(), ref_file); + BOOST_REQUIRE_EQUAL(loc.line(), ref_line); + BOOST_REQUIRE(std::string(loc.function_name()).find(ref_func) != std::string::npos); +} + +static void test_source_location_caller() { + test_source_location_callee(__FILE__, __func__, __LINE__); +} + +BOOST_AUTO_TEST_CASE(test_source_location) { + test_source_location_caller(); +} diff --git a/src/seastar/tests/unit/spawn_test.cc b/src/seastar/tests/unit/spawn_test.cc new file mode 100644 index 000000000..60d0a591f --- /dev/null +++ b/src/seastar/tests/unit/spawn_test.cc @@ -0,0 +1,138 @@ +/* + * 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) 2022 Kefu Chai ( tchaikov@gmail.com ) + */ +#include <seastar/core/reactor.hh> +#include <seastar/core/seastar.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/util/log.hh> +#include <seastar/util/process.hh> + +using namespace seastar; +using namespace seastar::experimental; + +static seastar::logger testlog("testlog"); + +SEASTAR_TEST_CASE(test_spawn_success) { + return spawn_process("/bin/true").then([] (auto process) { + return process.wait(); + }).then([] (auto wstatus) { + auto* exit_status = std::get_if<process::wait_exited>(&wstatus); + BOOST_REQUIRE(exit_status != nullptr); + BOOST_CHECK_EQUAL(exit_status->exit_code, EXIT_SUCCESS); + }); +} + +SEASTAR_TEST_CASE(test_spawn_failure) { + return spawn_process("/bin/false").then([] (auto process) { + return process.wait(); + }).then([] (auto wstatus) { + auto* exit_status = std::get_if<process::wait_exited>(&wstatus); + BOOST_REQUIRE(exit_status != nullptr); + BOOST_CHECK_EQUAL(exit_status->exit_code, EXIT_FAILURE); + }); +} + +SEASTAR_TEST_CASE(test_spawn_program_does_not_exist) { + return spawn_process("non/existent/path").then_wrapped([] (future<process> fut) { + BOOST_REQUIRE(fut.failed()); + BOOST_CHECK_EXCEPTION(std::rethrow_exception(fut.get_exception()), + std::system_error, + [](const auto& e) { + return e.code().value() == ENOENT; + }); + }); +} + +SEASTAR_TEST_CASE(test_spawn_echo) { + const char* echo_cmd = "/bin/echo"; + return spawn_process(echo_cmd, {.argv = {echo_cmd, "-n", "hello", "world"}}).then([] (auto process) { + auto stdout = process.stdout(); + return do_with(std::move(process), std::move(stdout), bool(true), [](auto& p, auto& stdout, auto& matched) { + 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; + struct consumer { + consumer(std::string_view expected, bool& matched) + : _expected(expected), _matched(matched) {} + future<consumption_result_type> operator()(tmp_buf buf) { + _matched = std::equal(buf.begin(), buf.end(), _expected.begin()); + if (!_matched) { + return make_ready_future<consumption_result_type>(stop_consuming_type({})); + } + _expected.remove_prefix(buf.size()); + return make_ready_future<consumption_result_type>(continue_consuming{}); + } + std::string_view _expected; + bool& _matched; + }; + return stdout.consume(consumer("hello world", matched)).then([&matched] { + BOOST_CHECK(matched); + }).finally([&p] { + return p.wait().discard_result(); + }); + }); + }); +} + +SEASTAR_TEST_CASE(test_spawn_input) { + static const sstring text = "hello world\n"; + return spawn_process("/bin/cat").then([] (auto process) { + auto stdin = process.stdin(); + auto stdout = process.stdout(); + return do_with(std::move(process), std::move(stdin), std::move(stdout), [](auto& p, auto& stdin, auto& stdout) { + return stdin.write(text).then([&stdin] { + return stdin.flush(); + }).handle_exception_type([] (std::system_error& e) { + testlog.error("failed to write to stdin: {}", e); + return make_exception_future<>(std::move(e)); + }).then([&stdout] { + return stdout.read_exactly(text.size()); + }).handle_exception_type([] (std::system_error& e) { + testlog.error("failed to read from stdout: {}", e); + return make_exception_future<temporary_buffer<char>>(std::move(e)); + }).then([] (temporary_buffer<char> echo) { + BOOST_CHECK_EQUAL(sstring(echo.get(), echo.size()), text); + }).finally([&p] { + return p.wait().discard_result(); + }); + }); + }); +} + +SEASTAR_TEST_CASE(test_spawn_kill) { + const char* sleep_cmd = "/bin/sleep"; + // sleep for 10s, but terminate it right away. + return spawn_process(sleep_cmd, {.argv = {sleep_cmd, "10"}}).then([] (auto process) { + auto start = std::chrono::high_resolution_clock::now(); + return do_with(std::move(process), [](auto& p) { + p.terminate(); + return p.wait(); + }).then([start](experimental::process::wait_status wait_status) { + auto* wait_signaled = std::get_if<experimental::process::wait_signaled>(&wait_status); + BOOST_REQUIRE(wait_signaled != nullptr); + BOOST_CHECK_EQUAL(wait_signaled->terminating_signal, SIGTERM); + // sleep should be terminated in 10ms + auto end = std::chrono::high_resolution_clock::now(); + auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); + BOOST_CHECK_LE(ms, 10); + }); + }); +} diff --git a/src/seastar/tests/unit/sstring_test.cc b/src/seastar/tests/unit/sstring_test.cc new file mode 100644 index 000000000..2a81fc792 --- /dev/null +++ b/src/seastar/tests/unit/sstring_test.cc @@ -0,0 +1,240 @@ +/* + * 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_find_sstring_compatible) { + auto check_find = [](const char* s1, const char* s2, size_t pos) { + const auto xpos_ss = sstring(s1).find(s2, pos); + const auto xpos_std = std::string(s1).find(s2, pos); + + // verify that std::string really has the same behavior as we just tested for sstring + if (xpos_ss == sstring::npos) { // sstring::npos may not equal std::string::npos ? + BOOST_REQUIRE_EQUAL(xpos_std, std::string::npos); + } else { + BOOST_REQUIRE_EQUAL(xpos_ss, xpos_std); + } + }; + + check_find("", "", 0); + check_find("", "", 1); + check_find("abcde", "", 0); + check_find("abcde", "", 1); + check_find("abcde", "", 5); + check_find("abcde", "", 6); +} + +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_REQUIRE_EQUAL(sstring("abcde").find("abcde"), 0u); + BOOST_REQUIRE_EQUAL(sstring("abcde").find("", 5), 5u); + BOOST_REQUIRE_EQUAL(sstring("ababcbdbef").find("bef"), 7u); + BOOST_REQUIRE_EQUAL(sstring("").find("", 0), 0u); +} + +BOOST_AUTO_TEST_CASE(test_str_not_find_sstring) { + BOOST_REQUIRE_EQUAL(sstring("abcde").find("x"), sstring::npos); + BOOST_REQUIRE_EQUAL(sstring("abcdefg").find("cde", 6), sstring::npos); + BOOST_REQUIRE_EQUAL(sstring("abcdefg").find("cde", 4), sstring::npos); + BOOST_REQUIRE_EQUAL(sstring("ababcbdbe").find("bcd"), sstring::npos); + BOOST_REQUIRE_EQUAL(sstring("").find("", 1), sstring::npos); + BOOST_REQUIRE_EQUAL(sstring("abc").find("abcde"), 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()))); + } +} + +BOOST_AUTO_TEST_CASE(test_resize_and_overwrite) { + static constexpr size_t new_size = 42; + static constexpr char pattern = 's'; + { + // the size of new content is identical to the specified count + sstring s; + s.resize_and_overwrite(new_size, [](char* buf, size_t n) { + memset(buf, pattern, n); + return n; + }); + BOOST_CHECK_EQUAL(s, sstring(new_size, pattern)); + } + { + // the size of new content is smaller than the specified count + static constexpr size_t smaller_size = new_size / 2; + sstring s; + s.resize_and_overwrite(new_size, [](char* buf, size_t n) { + memset(buf, pattern, smaller_size); + return smaller_size; + }); + BOOST_CHECK_EQUAL(s, sstring(smaller_size, pattern)); + } +} 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..eec642fbd --- /dev/null +++ b/src/seastar/tests/unit/stall_detector_test.cc @@ -0,0 +1,174 @@ +/* + * This file is open source software, licensed to you under the terms + * of the Apache License, Version 2.0 (the "License"). See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. You may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright (C) 2018 ScyllaDB Ltd. + */ + +#include <boost/test/tools/old/interface.hpp> +#include <cstddef> +#include <seastar/core/internal/stall_detector.hh> +#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 <atomic> +#include <chrono> +#include <sys/mman.h> + +using namespace seastar; +using namespace std::chrono_literals; + +static seastar::logger testlog("testlog"); + +class temporary_stall_detector_settings { + std::chrono::milliseconds _old_threshold; + std::function<void ()> _old_report; +public: + /** + * Temporarily (until destructor) overload the stall detector threshold and reporting function. + * + * Also resets the reported stalls counter to zero, so the next backtraces will not be supressed. + */ + 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)); + } +}; + +using void_fn = std::function<void()>; + +void spin(std::chrono::duration<double> how_much, void_fn body = []{}) { + auto end = internal::cpu_stall_detector::clock_type::now() + how_much; + while (internal::cpu_stall_detector::clock_type::now() < end) { + body(); // spin! + } +} + +static void spin_user_hires(std::chrono::duration<double> how_much) { + auto end = std::chrono::high_resolution_clock::now() + how_much; + while (std::chrono::high_resolution_clock::now() < end) { + + } +} + +void spin_some_cooperatively(std::chrono::duration<double> how_much, void_fn body = []{}) { + auto end = std::chrono::steady_clock::now() + how_much; + while (std::chrono::steady_clock::now() < end) { + spin(200us, body); + 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)yield().then([i, &p] { + spin(500us); + if (i == tasks - 1) { + p.set_value(); + } + }); + return make_ready_future<>(); + }).get(); + f.get(); + BOOST_REQUIRE_EQUAL(reports, 0); +} + +// Triggers stalls by spinning with a specify "body" function +// which takes most of the spin time. +static void test_spin_with_body(const char* what, void_fn body) { + // The !count_stacks mode outputs stall notification to stderr as usual + // and do not assert anything, but are intended for diagnosing + // stall problems by inspecting the output. We expect the userspace + // spin test to show no kernel callstack, and the kernel test to + // show kernel backtraces in the mmap or munmap path, but this is + // not exact since neither test spends 100% of its time in the + // selected mode (of course, kernel stacks only appear if the + // perf-based stall detected could be enabled). + // + // Then the count_stacks mode tests that the right number of stacks + // were output. + for (auto count_stacks : {false, true}) { + testlog.info("Starting spin test: {}", what); + std::atomic<unsigned> reports{}; + std::function<void()> reporter = count_stacks ? std::function<void()>{[&]{ ++reports; }} : nullptr; + temporary_stall_detector_settings tsds(10ms, std::move(reporter)); + constexpr unsigned nr = 5; + for (unsigned i = 0; i < nr; ++i) { + spin_some_cooperatively(100ms, body); + spin(20ms, body); + } + testlog.info("Ending spin test: {}", what); + BOOST_CHECK_EQUAL(reports, count_stacks ? 5 : 0); + } +} + +SEASTAR_THREAD_TEST_CASE(spin_in_userspace) { + // a body which spends almost all of its time in userspace + test_spin_with_body("userspace", [] { spin_user_hires(1ms); }); +} + +static void mmap_populate(size_t len) { + void *p = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, 0, 0); + BOOST_REQUIRE(p != MAP_FAILED); + BOOST_REQUIRE(munmap(p, len) == 0); +} + +SEASTAR_THREAD_TEST_CASE(spin_in_kernel) { + // a body which spends almost all of its time in the kernel + // doing 128K mmaps + test_spin_with_body("kernel", [] { mmap_populate(128 * 1024); }); +} diff --git a/src/seastar/tests/unit/stream_reader_test.cc b/src/seastar/tests/unit/stream_reader_test.cc new file mode 100644 index 000000000..61401e08e --- /dev/null +++ b/src/seastar/tests/unit/stream_reader_test.cc @@ -0,0 +1,111 @@ +/* + * 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/future.hh> +#include <seastar/core/temporary_buffer.hh> +#include <seastar/core/thread.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/testing/thread_test_case.hh> +#include <seastar/http/request.hh> +#include <seastar/util/short_streams.hh> +#include <string> + +using namespace seastar; +using namespace util; + +/* + * Simple data source producing up to total_size bytes + * in buffer_size-byte chunks. + * */ +class test_source_impl : public data_source_impl { + short _current_letter = 0; // a-z corresponds to 0-25 + size_t _buffer_size; + size_t _remaining_size; +public: + test_source_impl(size_t buffer_size, size_t total_size) + : _buffer_size(buffer_size), _remaining_size(total_size) { + } + virtual future<temporary_buffer<char>> get() override { + size_t len = std::min(_buffer_size, _remaining_size); + temporary_buffer<char> tmp(len); + for (size_t i = 0; i < len; i++) { + tmp.get_write()[i] = 'a' + _current_letter; + ++_current_letter %= 26; + } + _remaining_size -= len; + return make_ready_future<temporary_buffer<char>>(std::move(tmp)); + } + virtual future<temporary_buffer<char>> skip(uint64_t n) override { + _remaining_size -= std::min(_remaining_size, n); + _current_letter += n %= 26; + return make_ready_future<temporary_buffer<char>>(); + } +}; + +SEASTAR_TEST_CASE(test_read_all) { + return async([] { + auto check_read_all = [] (input_stream<char>& strm, const char* test) { + auto all = read_entire_stream(strm).get0(); + sstring s; + for (auto&& buf: all) { + s += seastar::to_sstring(std::move(buf)); + }; + BOOST_REQUIRE_EQUAL(s, test); + }; + input_stream<char> inp(data_source(std::make_unique<test_source_impl>(5, 15))); + check_read_all(inp, "abcdefghijklmno"); + BOOST_REQUIRE(inp.eof()); + input_stream<char> inp2(data_source(std::make_unique<test_source_impl>(5, 16))); + check_read_all(inp2, "abcdefghijklmnop"); + BOOST_REQUIRE(inp2.eof()); + input_stream<char> empty_inp(data_source(std::make_unique<test_source_impl>(5, 0))); + check_read_all(empty_inp, ""); + BOOST_REQUIRE(empty_inp.eof()); + + input_stream<char> inp_cont(data_source(std::make_unique<test_source_impl>(5, 15))); + BOOST_REQUIRE_EQUAL(to_sstring(read_entire_stream_contiguous(inp_cont).get0()), "abcdefghijklmno"); + BOOST_REQUIRE(inp_cont.eof()); + input_stream<char> inp_cont2(data_source(std::make_unique<test_source_impl>(5, 16))); + BOOST_REQUIRE_EQUAL(to_sstring(read_entire_stream_contiguous(inp_cont2).get0()), "abcdefghijklmnop"); + BOOST_REQUIRE(inp_cont2.eof()); + input_stream<char> empty_inp_cont(data_source(std::make_unique<test_source_impl>(5, 0))); + BOOST_REQUIRE_EQUAL(to_sstring(read_entire_stream_contiguous(empty_inp_cont).get0()), ""); + BOOST_REQUIRE(empty_inp_cont.eof()); + }); +} + +SEASTAR_TEST_CASE(test_skip_all) { + return async([] { + input_stream<char> inp(data_source(std::make_unique<test_source_impl>(5, 15))); + skip_entire_stream(inp).get(); + BOOST_REQUIRE(inp.eof()); + BOOST_REQUIRE(to_sstring(inp.read().get0()).empty()); + input_stream<char> inp2(data_source(std::make_unique<test_source_impl>(5, 16))); + skip_entire_stream(inp2).get(); + BOOST_REQUIRE(inp2.eof()); + BOOST_REQUIRE(to_sstring(inp2.read().get0()).empty()); + input_stream<char> empty_inp(data_source(std::make_unique<test_source_impl>(5, 0))); + skip_entire_stream(empty_inp).get(); + BOOST_REQUIRE(empty_inp.eof()); + BOOST_REQUIRE(to_sstring(empty_inp.read().get0()).empty()); + }); +} 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..9d40d3f2f --- /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() const override { return __FILE__; } + const char* get_name() const override { return "test_thread_custom_stack_size_failure"; } + int get_expected_failures() const override { return 0; } \ + seastar::future<> run_test_case() const 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() const { + 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/tl-generator.hh b/src/seastar/tests/unit/tl-generator.hh new file mode 100644 index 000000000..8a510cd3c --- /dev/null +++ b/src/seastar/tests/unit/tl-generator.hh @@ -0,0 +1,165 @@ +/// +// generator - Single-header, ranges-compatible generator type built +// on C++20 coroutines +// Written in 2021 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at https://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// <http://creativecommons.org/publicdomain/zero/1.0/>. +/// + +#ifndef TL_GENERATOR_HPP +#define TL_GENERATOR_HPP + +#define TL_GENERATOR_VERSION_MAJOR 0 +#define TL_GENERATOR_VERSION_MINOR 3 +#define TL_GENERATOR_VERSION_PATCH 0 + +#include <coroutine> +#include <exception> +#include <utility> +#include <type_traits> +#include <ranges> + +namespace tl { + template <class T> + class generator { + struct promise { + using value_type = std::remove_reference_t<T>; + using reference_type = value_type&; + using pointer_type = value_type*; + + promise() = default; + + generator get_return_object() { + return generator(std::coroutine_handle<promise>::from_promise(*this)); + } + + std::suspend_always initial_suspend() const { return {}; } + std::suspend_always final_suspend() const noexcept { return {}; } + + void return_void() const noexcept { return; } + + void unhandled_exception() noexcept { + exception_ = std::current_exception(); + } + + void rethrow_if_exception() { + if (exception_) { + std::rethrow_exception(exception_); + } + } + + std::suspend_always yield_value(reference_type v) noexcept { + value_ = std::addressof(v); + return {}; + } + + std::exception_ptr exception_; + pointer_type value_; + }; + + public: + using promise_type = promise; + class sentinel {}; + + class iterator { + using handle_type = std::coroutine_handle<promise_type>; + + public: + using value_type = typename promise_type::value_type; + using reference_type = typename promise_type::reference_type; + using pointer_type = typename promise_type::pointer_type; + using difference_type = std::ptrdiff_t; + + iterator() = default; + ~iterator() { + if (handle_) handle_.destroy(); + } + + //Non-copyable because coroutine handles point to a unique resource + iterator(iterator const&) = delete; + iterator(iterator&& rhs) noexcept : handle_(std::exchange(rhs.handle_, nullptr)) {} + iterator& operator=(iterator const&) = delete; + iterator& operator=(iterator&& rhs) noexcept { + handle_ = std::exchange(rhs.handle_, nullptr); + return *this; + } + + friend bool operator==(iterator const& it, sentinel) noexcept { + return (!it.handle_ || it.handle_.done()); + } + + iterator& operator++() { + handle_.resume(); + if (handle_.done()) { + handle_.promise().rethrow_if_exception(); + } + return *this; + } + + void operator++(int) { + (void)this->operator++(); + } + + reference_type operator*() const + noexcept(noexcept(std::is_nothrow_copy_constructible_v<reference_type>)){ + return *handle_.promise().value_; + } + + private: + friend class generator; + iterator(handle_type handle) : handle_(handle) {} + + handle_type handle_; + }; + + using handle_type = std::coroutine_handle<promise_type>; + + generator() noexcept = default; + ~generator() { + if (handle_) handle_.destroy(); + } + + generator(generator const&) = delete; + generator(generator&& rhs) noexcept : handle_(std::exchange(rhs.handle_, nullptr)) {} + generator& operator=(generator const&) = delete; + generator& operator=(generator&& rhs) noexcept { + swap(rhs); + return *this; + } + + iterator begin() { + handle_.resume(); + if (handle_.done()) { + handle_.promise().rethrow_if_exception(); + } + return {std::exchange(handle_, nullptr)}; + } + + sentinel end() const noexcept { + return {}; + } + + void swap(generator& other) noexcept { + std::swap(handle_, other.handle_); + } + + private: + friend class iterator; + explicit generator(handle_type handle) noexcept : handle_(handle) {} + + handle_type handle_ = nullptr; + }; +} + +template<class T> +inline constexpr bool std::ranges::enable_view<tl::generator<T>> = true; + +#endif 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..62048dfdc --- /dev/null +++ b/src/seastar/tests/unit/tls_test.cc @@ -0,0 +1,1364 @@ +/* + * 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/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/core/with_timeout.hh> +#include <seastar/util/std-compat.hh> +#include <seastar/util/process.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, const sstring& name = {}) { + return repeat_until_value([=]() mutable { + return tls::connect(certs, addr, name).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(), sstring{}, [](auto& in, sstring& buffer) { + return do_until(std::bind(&input_stream<char>::eof, std::cref(in)), [&buffer, &in] { + auto f = in.read(); + return f.then([&](temporary_buffer<char> buf) { + buffer.append(buf.get(), buf.size()); + }); + }).then([&buffer]() -> future<std::optional<bool>> { + if (buffer.empty()) { + // # 1127 google servers have a (pretty short) timeout between connect and expected first + // write. If we are delayed inbetween connect and write above (cert verification, scheduling + // solar spots or just time sharing on AWS) we could get a short read here. Just retry. + // If we get an actual error, it is either on protocol level (exception) or HTTP error. + return make_ready_future<std::optional<bool>>(std::nullopt); + } + BOOST_CHECK(buffer.size() > 8); + BOOST_CHECK_EQUAL(buffer.substr(0, 5), sstring("HTTP/")); + return make_ready_future<std::optional<bool>>(true); + }); + }); + }); + }).finally([&os] { + return os.close(); + }); + }); + }); + }); + + }).discard_result(); +} + +#if SEASTAR_TESTING_WITH_NETWORKING + +static const auto google_name = "www.google.com"; + +// broken out from below. to allow pre-lookup +static future<socket_address> google_address() { + static socket_address google; + + if (google.is_unspecified()) { + return net::dns::resolve_name(google_name, net::inet_address::family::INET).then([](net::inet_address addr) { + google = socket_address(addr, 443); + return google_address(); + }); + } + return make_ready_future<socket_address>(google); +} + +static future<> connect_to_ssl_google(::shared_ptr<tls::certificate_credentials> certs) { + return google_address().then([certs](socket_address addr) { + return connect_to_ssl_addr(std::move(certs), addr, google_name); + }); +} + +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) { + // avoid getting parallel connects stuck on dns lookup (if running single case). + // pre-lookup www.google.com + return google_address().then([](socket_address) { + 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_system_trust_and_priority_strings) { + static std::vector<sstring> prios( { + "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. + "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_system_trust_and_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<>(); + }); +} +#endif // SEASTAR_TESTING_WITH_NETWORKING + +class https_server { + const sstring _cert; + const std::string _addr = "127.0.0.1"; + experimental::process _process; + uint16_t _port; + + static experimental::process spawn(const std::string& addr, const sstring& key, const sstring& cert) { + auto httpd = boost::dll::program_location().parent_path() / "https-server.py"; + const std::vector<sstring> argv{ + "httpd", + "--server", fmt::format("{}:{}", addr, 0), + "--key", key, + "--cert", cert, + }; + return experimental::spawn_process(httpd.string(), {.argv = argv}).get0(); + } + + // https-server.py picks an available port and listens on it. when it is + // ready to serve, it prints out the listening port. without hardwiring to + // a fixed port, we are able to run multiple tests in parallel. + static uint16_t read_port(experimental::process& process) { + 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; + struct consumer { + future<consumption_result_type> operator()(tmp_buf buf) { + if (auto newline = std::find(buf.begin(), buf.end(), '\n'); newline != buf.end()) { + size_t consumed = newline - buf.begin(); + line += std::string_view(buf.get(), consumed); + buf.trim_front(consumed); + return make_ready_future<consumption_result_type>(stop_consuming_type(std::move(buf))); + } else { + line += std::string_view(buf.get(), buf.size()); + return make_ready_future<consumption_result_type>(stop_consuming_type({})); + } + } + std::string line; + }; + auto reader = ::make_shared<consumer>(); + process.stdout().consume(*reader).get(); + return std::stoul(reader->line); + } + +public: + https_server(const std::string& ca = "mtls_ca") + : _cert(certfile(fmt::format("{}.crt", ca))) + , _process(spawn(_addr, certfile(fmt::format("{}.key", ca)), _cert)) + , _port(read_port(_process)) + {} + ~https_server() { + _process.terminate(); + _process.wait().discard_result().get(); + } + const sstring& cert() const { + return _cert; + } + socket_address addr() const { + return ipv4_addr(_addr, _port); + } + sstring name() const { + // should be identical to the one passed as the "-addext" option when + // generating the cert. + return "127.0.0.1"; + } +}; + +#if !SEASTAR_TESTING_WITH_NETWORKING + +SEASTAR_THREAD_TEST_CASE(test_simple_x509_client) { + auto certs = ::make_shared<tls::certificate_credentials>(); + https_server server; + certs->set_x509_trust_file(server.cert(), tls::x509_crt_format::PEM).get(); + connect_to_ssl_addr(certs, server.addr(), server.name()).get(); +} + +#endif // !SEASTAR_TESTING_WITH_NETWORKING + +SEASTAR_THREAD_TEST_CASE(test_x509_client_with_builder) { + tls::credentials_builder b; + https_server server; + b.set_x509_trust_file(server.cert(), tls::x509_crt_format::PEM).get(); + connect_to_ssl_addr(b.build_certificate_credentials(), server.addr()).get(); +} + +SEASTAR_THREAD_TEST_CASE(test_x509_client_with_builder_multiple) { + tls::credentials_builder b; + https_server server; + b.set_x509_trust_file(server.cert(), tls::x509_crt_format::PEM).get(); + auto creds = b.build_certificate_credentials(); + auto addr = server.addr(); + parallel_for_each(boost::irange(0, 20), [creds, addr](auto i) { + return connect_to_ssl_addr(creds, addr); + }).get(); +} + +SEASTAR_THREAD_TEST_CASE(test_x509_client_with_priority_strings) { + static std::vector<sstring> prios( { + "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. + "SECURE256:+SECURE128", + "NORMAL:%COMPAT", + "NORMAL:-MD5", + "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-256-GCM:+SIGN-ALL:+COMP-NULL:+GROUP-EC-ALL", + "NORMAL:+ARCFOUR-128", + "SECURE128:-VERS-TLS1.0:+COMP-DEFLATE", + "SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2" + }); + tls::credentials_builder b; + https_server server; + b.set_x509_trust_file(server.cert(), tls::x509_crt_format::PEM).get(); + auto addr = server.addr(); + do_for_each(prios, [&b, addr](const sstring& prio) { + b.set_priority_string(prio); + return connect_to_ssl_addr(b.build_certificate_credentials(), addr); + }).get(); +} + +SEASTAR_THREAD_TEST_CASE(test_x509_client_with_priority_strings_fail) { + static std::vector<sstring> prios( { "NONE", + "NONE:+CURVE-SECP256R1" + }); + tls::credentials_builder b; + https_server server; + b.set_x509_trust_file(server.cert(), tls::x509_crt_format::PEM).get(); + auto addr = server.addr(); + do_for_each(prios, [&b, addr](const sstring& prio) { + b.set_priority_string(prio); + try { + return connect_to_ssl_addr(b.build_certificate_credentials(), addr).then([] { + BOOST_FAIL("Expected exception"); + }).handle_exception([](auto ep) { + // ok. + }); + } catch (...) { + // also ok + } + return make_ready_future<>(); + }).get(); +} + +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); + + std::ofstream(cert.c_str()) << "lala land" << std::endl; + std::ofstream(key.c_str()) << "lala land" << std::endl; + + // should get one or two exceptions + q.pop_eventually().get(); + + fs::remove(cert); + fs::remove(key); + + fs::copy_file(certfile("test.crt"), cert); + fs::copy_file(certfile("test.key"), key); + + // now it should reload + p.get_future().get(); +} + +using namespace std::chrono_literals; + +// the same as previous test, but we set a big tolerance for +// reload errors, and verify that either our scheduling/fs is +// super slow, or we got through the changes without failures. +SEASTAR_THREAD_TEST_CASE(test_reload_tolerance) { + 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(); + + int nfails = 0; + + // use 5s tolerance - this should ensure we don't generate any errors. + auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) { + if (ep) { + ++nfails; + return; + } + changed.insert(files.begin(), files.end()); + if (changed.count(cert) && changed.count(key)) { + p.set_value(); + } + }, std::chrono::milliseconds(5000)).get0(); + + // very intentionally use blocking calls. We want all our modifications to happen + // before any other continuation is allowed to process. + + auto start = std::chrono::system_clock::now(); + + fs::remove(cert); + fs::remove(key); + + std::ofstream(cert.c_str()) << "lala land" << std::endl; + std::ofstream(key.c_str()) << "lala land" << std::endl; + + fs::remove(cert); + fs::remove(key); + + fs::copy_file(certfile("test.crt"), cert); + fs::copy_file(certfile("test.key"), key); + + // now it should reload + p.get_future().get(); + + auto end = std::chrono::system_clock::now(); + + BOOST_ASSERT(nfails == 0 || (end - start) > 4s); +} + +SEASTAR_THREAD_TEST_CASE(test_reload_by_move) { + tmpdir tmp; + tmpdir tmp2; + + namespace fs = std::filesystem; + + fs::copy_file(certfile("test.crt"), tmp.path() / "test.crt"); + fs::copy_file(certfile("test.key"), tmp.path() / "test.key"); + fs::copy_file(certfile("test.crt"), tmp2.path() / "test.crt"); + fs::copy_file(certfile("test.key"), tmp2.path() / "test.key"); + + auto cert = (tmp.path() / "test.crt").native(); + auto key = (tmp.path() / "test.key").native(); + auto cert2 = (tmp2.path() / "test.crt").native(); + auto key2 = (tmp2.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(); + + int nfails = 0; + + // use 5s tolerance - this should ensure we don't generate any errors. + auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) { + if (ep) { + ++nfails; + return; + } + changed.insert(files.begin(), files.end()); + if (changed.count(cert) && changed.count(key)) { + p.set_value(); + } + }, std::chrono::milliseconds(5000)).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); + + // deletes should _not_ cause errors/reloads + try { + with_timeout(std::chrono::steady_clock::now() + 3s, p.get_future()).get(); + BOOST_FAIL("should not reach"); + } catch (timed_out_error&) { + // ok + } + + BOOST_REQUIRE_EQUAL(changed.size(), 0); + + p = promise(); + + fs::rename(cert2, cert); + fs::rename(key2, key); + + // now it should reload + p.get_future().get(); + + BOOST_REQUIRE_EQUAL(changed.size(), 2); + changed.clear(); + + // again, without delete + + fs::copy_file(certfile("test.crt"), tmp2.path() / "test.crt"); + fs::copy_file(certfile("test.key"), tmp2.path() / "test.key"); + + p = promise(); + + fs::rename(cert2, cert); + fs::rename(key2, key); + + // it should reload here as well. + p.get_future().get(); + + // could get two notifications. but not more. + for (int i = 0;; ++i) { + p = promise(); + try { + with_timeout(std::chrono::steady_clock::now() + 3s, p.get_future()).get(); + BOOST_ASSERT(i == 0); + } catch (timed_out_error&) { + // ok + break; + } + } +} + +SEASTAR_THREAD_TEST_CASE(test_closed_write) { + 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(); + + ::listen_options opts; + opts.reuse_address = true; + opts.set_fixed_cpu(this_shard_id()); + + auto addr = ::make_ipv4_address( {0x7f000001, 4712}); + auto server = tls::listen(serv, addr, opts); + + auto check_same_message_two_writes = [](output_stream<char>& out) { + std::exception_ptr ep1, ep2; + + try { + out.write("apa").get(); + out.flush().get(); + BOOST_FAIL("should not reach"); + } catch (...) { + // ok + ep1 = std::current_exception(); + } + + try { + out.write("apa").get(); + out.flush().get(); + BOOST_FAIL("should not reach"); + } catch (...) { + // ok + ep2 = std::current_exception(); + } + + try { + std::rethrow_exception(ep1); + } catch (std::exception& e1) { + try { + std::rethrow_exception(ep2); + } catch (std::exception& e2) { + BOOST_REQUIRE_EQUAL(std::string(e1.what()), std::string(e2.what())); + return; + } + } + + BOOST_FAIL("should not reach"); + }; + + + { + auto sa = server.accept(); + auto c = tls::connect(creds, addr).get0(); + auto s = sa.get0(); + auto in = s.connection.input(); + + output_stream<char> out(c.output().detach(), 4096); + // close on client end before writing + out.close().get(); + + check_same_message_two_writes(out); + } + + { + auto sa = server.accept(); + auto c = tls::connect(creds, 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(); + in.read().get(); + f.get(); + + // close on server end before writing + in.close().get(); + s.connection.shutdown_input(); + s.connection.shutdown_output(); + + // we won't get broken pipe until + // after a while (tm) + for (;;) { + try { + out.write("apa").get(); + out.flush().get(); + } catch (...) { + break; + } + } + + // now check we get the same message. + check_same_message_two_writes(out); + } + +} + +/* + * Certificates: + * + * make -f tests/unit/mkmtls.gmk domain=scylladb.org server=test + * + * -> mtls_ca.crt + * mtls_ca.key + * mtls_server.crt + * mtls_server.csr + * mtls_server.key + * mtls_client1.crt + * mtls_client1.csr + * mtls_client1.key + * mtls_client2.crt + * mtls_client2.csr + * mtls_client2.key + * + */ +SEASTAR_THREAD_TEST_CASE(test_dn_name_handling) { + // Connect to server using two different client certificates. + // Make sure that for every client the server can fetch DN string + // and the string is correct. + // The setup consist of several certificates: + // - CA + // - mtls_server.crt - server certificate + // - mtls_client1.crt - first client certificate + // - mtls_client2.crt - second client certificate + // + // The test runs server that uses mtls_server.crt. + // The server accepts two incomming connections, first one uses mtls_client1.crt + // and the second one uses mtls_client2.crt. Every client sends a short string + // that server receives and tries to find it in the DN string. + + auto addr = ::make_ipv4_address( {0x7f000001, 4712}); + + auto client1_creds = [] { + tls::credentials_builder builder; + builder.set_x509_trust_file(certfile("mtls_ca.crt"), tls::x509_crt_format::PEM).get(); + builder.set_x509_key_file(certfile("mtls_client1.crt"), certfile("mtls_client1.key"), tls::x509_crt_format::PEM).get(); + return builder.build_certificate_credentials(); + }(); + + auto client2_creds = [] { + tls::credentials_builder builder; + builder.set_x509_trust_file(certfile("mtls_ca.crt"), tls::x509_crt_format::PEM).get(); + builder.set_x509_key_file(certfile("mtls_client2.crt"), certfile("mtls_client2.key"), tls::x509_crt_format::PEM).get(); + return builder.build_certificate_credentials(); + }(); + + auto server_creds = [] { + tls::credentials_builder builder; + builder.set_x509_trust_file(certfile("mtls_ca.crt"), tls::x509_crt_format::PEM).get(); + builder.set_x509_key_file(certfile("mtls_server.crt"), certfile("mtls_server.key"), tls::x509_crt_format::PEM).get(); + builder.set_client_auth(tls::client_auth::REQUIRE); + return builder.build_server_credentials(); + }(); + + auto fetch_dn = [server_creds, addr] (sstring id, shared_ptr<tls::certificate_credentials> client_cred) { + listen_options lo{}; + lo.reuse_address = true; + auto server_sock = tls::listen(server_creds, addr, lo); + + auto sa = server_sock.accept(); + auto c = tls::connect(client_cred, addr).get(); + auto s = sa.get(); + + auto in = s.connection.input(); + output_stream<char> out(c.output().detach(), 1024); + out.write(id).get(); + + auto fdn = tls::get_dn_information(s.connection); + + auto fout = out.flush(); + auto fin = in.read(); + + fout.get(); + + auto dn = fdn.get(); + auto client_id = fin.get(); + + in.close().get(); + out.close().get(); + + s.connection.shutdown_input(); + s.connection.shutdown_output(); + + c.shutdown_input(); + c.shutdown_output(); + + auto it = dn->subject.find(sstring(client_id.get(), client_id.size())); + BOOST_REQUIRE(it != sstring::npos); + }; + + fetch_dn("client1.org", client1_creds); + fetch_dn("client2.org", client2_creds); +} 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..8f7e8c243 --- /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 <seastar/core/internal/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..0452e9293 --- /dev/null +++ b/src/seastar/tests/unit/unix_domain_test.cc @@ -0,0 +1,232 @@ +/* + * 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(); + void 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; + } + } + client_round(); + } + }); + + 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. +// Runs in a seastar::thread. +void 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(); + + auto inp = cc.input(); + auto out = cc.output(); + + out.write(test_message).get(); + out.flush().get(); + auto bb = inp.read().get0(); + BOOST_REQUIRE_EQUAL(std::string_view(bb.begin(), bb.size()), "+"s+test_message); + inp.close().get(); + out.close().get(); +} + +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..df5ab6478 --- /dev/null +++ b/src/seastar/tests/unit/unwind_test.cc @@ -0,0 +1,69 @@ +/* + * This file is open source software, licensed to you under the terms + * of the Apache License, Version 2.0 (the "License"). See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. You may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2016 ScyllaDB + */ + +#define BOOST_TEST_MODULE core + +#include <boost/test/included/unit_test.hpp> +#include <pthread.h> +#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..64b4f54c0 --- /dev/null +++ b/src/seastar/tests/unit/weak_ptr_test.cc @@ -0,0 +1,171 @@ +/* + * 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(); + + weak_ptr<myclass> wp3_moved; + wp3_moved = std::move(wp3); + weak_ptr<myclass> 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)); + BOOST_REQUIRE(wp1_moved.get() == owning_ptr.get()); + BOOST_REQUIRE(wp2_moved.get() == owning_ptr.get()); + BOOST_REQUIRE(wp3_moved.get() == owning_ptr.get()); + + owning_ptr = {}; + + BOOST_REQUIRE(!bool(wp1_moved)); + BOOST_REQUIRE(!bool(wp2_moved)); + BOOST_REQUIRE(!bool(wp3_moved)); +} + +BOOST_AUTO_TEST_CASE(test_weak_ptr_can_be_copied) { + 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(); + + weak_ptr<myclass> wp1_copied(wp1); + auto wp2_copied = wp2; + weak_ptr<myclass> wp3_copied; + wp3_copied = wp3; + BOOST_REQUIRE(bool(wp1)); + BOOST_REQUIRE(bool(wp2)); + BOOST_REQUIRE(bool(wp3)); + BOOST_REQUIRE(bool(wp1_copied)); + BOOST_REQUIRE(bool(wp2_copied)); + BOOST_REQUIRE(bool(wp3_copied)); + + BOOST_REQUIRE(wp1.get() == wp1_copied.get()); + BOOST_REQUIRE(wp2.get() == wp2_copied.get()); + BOOST_REQUIRE(wp3.get() == wp3_copied.get()); + + owning_ptr = {}; + + BOOST_REQUIRE(!bool(wp1)); + BOOST_REQUIRE(!bool(wp2)); + BOOST_REQUIRE(!bool(wp3)); + BOOST_REQUIRE(!bool(wp1_copied)); + BOOST_REQUIRE(!bool(wp2_copied)); + BOOST_REQUIRE(!bool(wp3_copied)); +} + +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)); +} diff --git a/src/seastar/tests/unit/websocket_test.cc b/src/seastar/tests/unit/websocket_test.cc new file mode 100644 index 000000000..ae97c526b --- /dev/null +++ b/src/seastar/tests/unit/websocket_test.cc @@ -0,0 +1,156 @@ +/* + * Copyright 2021 ScyllaDB + */ + +#include <seastar/websocket/server.hh> +#include <seastar/testing/test_case.hh> +#include <seastar/testing/thread_test_case.hh> +#include <seastar/http/response_parser.hh> +#include <seastar/util/defer.hh> +#include "loopback_socket.hh" + +using namespace seastar; +using namespace seastar::experimental; + +SEASTAR_TEST_CASE(test_websocket_handshake) { + return seastar::async([] { + const std::string request = + "GET / HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Protocol: echo\r\n" + "\r\n"; + loopback_connection_factory factory; + loopback_socket_impl lsi(factory); + + auto acceptor = factory.get_server_socket().accept(); + auto connector = lsi.connect(socket_address(), socket_address()); + connected_socket sock = connector.get0(); + auto input = sock.input(); + auto output = sock.output(); + + websocket::server dummy; + dummy.register_handler("echo", [] (input_stream<char>& in, + output_stream<char>& out) { + return repeat([&in, &out]() { + return in.read().then([&out](temporary_buffer<char> f) { + std::cerr << "f.size(): " << f.size() << "\n"; + if (f.empty()) { + return make_ready_future<stop_iteration>(stop_iteration::yes); + } else { + return out.write(std::move(f)).then([&out]() { + return out.flush().then([] { + return make_ready_future<stop_iteration>(stop_iteration::no); + }); + }); + } + }); + }); + }); + websocket::connection conn(dummy, acceptor.get0().connection); + future<> serve = conn.process(); + auto close = defer([&conn, &input, &output, &serve] () noexcept { + conn.shutdown(); + conn.close().get(); + input.close().get(); + output.close().get(); + serve.get(); + }); + + // Send the handshake + output.write(request).get(); + output.flush().get(); + // Check that the server correctly computed the response + // according to WebSocket handshake specification + http_response_parser parser; + parser.init(); + input.consume(parser).get(); + std::unique_ptr<http_response> resp = parser.get_parsed_response(); + BOOST_ASSERT(resp); + sstring websocket_accept = resp->_headers["Sec-WebSocket-Accept"]; + // Trim possible whitespace prefix + auto it = std::find_if(websocket_accept.begin(), websocket_accept.end(), ::isalnum); + if (it != websocket_accept.end()) { + websocket_accept.erase(websocket_accept.begin(), it); + } + BOOST_REQUIRE_EQUAL(websocket_accept, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="); + for (auto& header : resp->_headers) { + std::cout << header.first << ':' << header.second << std::endl; + } + }); +} + + + +SEASTAR_TEST_CASE(test_websocket_handler_registration) { + return seastar::async([] { + loopback_connection_factory factory; + loopback_socket_impl lsi(factory); + + auto acceptor = factory.get_server_socket().accept(); + auto connector = lsi.connect(socket_address(), socket_address()); + connected_socket sock = connector.get0(); + auto input = sock.input(); + auto output = sock.output(); + + // Setup server + websocket::server ws; + ws.register_handler("echo", [] (input_stream<char>& in, + output_stream<char>& out) { + return repeat([&in, &out]() { + return in.read().then([&out](temporary_buffer<char> f) { + std::cerr << "f.size(): " << f.size() << "\n"; + if (f.empty()) { + return make_ready_future<stop_iteration>(stop_iteration::yes); + } else { + return out.write(std::move(f)).then([&out]() { + return out.flush().then([] { + return make_ready_future<stop_iteration>(stop_iteration::no); + }); + }); + } + }); + }); + }); + websocket::connection conn(ws, acceptor.get0().connection); + future<> serve = conn.process(); + + auto close = defer([&conn, &input, &output, &serve] () noexcept { + conn.shutdown(); + conn.close().get(); + input.close().get(); + output.close().get(); + serve.get(); + }); + + // handshake + const std::string request = + "GET / HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Protocol: echo\r\n" + "\r\n"; + output.write(request).get(); + output.flush().get(); + input.read_exactly(186).get(); + + // Sending and receiving a websocket frame + const auto ws_frame = std::string( + "\202\204" // 1000 0002 1000 0100 + "TEST" // Masking Key + "\0\0\0\0", 10); // Masked Message - TEST + const auto rs_frame = std::string( + "\202\004" // 1000 0002 0000 0100 + "TEST", 6); // Message - TEST + output.write(ws_frame).get(); + output.flush().get(); + + auto response = input.read_exactly(6).get(); + auto response_str = std::string(response.begin(), response.end()); + BOOST_REQUIRE_EQUAL(rs_frame, response_str); + }); +} |