diff --git a/README.md b/README.md index 7ba101c..99663d4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ receiver.config: { "psi_config": { "protocol_config": { - "protocol": "PROTOCOL_KKRT", + "protocol": "PROTOCOL_RR22", "role": "ROLE_RECEIVER", "broadcast_result": true }, @@ -70,7 +70,7 @@ sender.config: { "psi_config": { "protocol_config": { - "protocol": "PROTOCOL_KKRT", + "protocol": "PROTOCOL_RR22", "role": "ROLE_SENDER", "broadcast_result": true }, @@ -132,7 +132,7 @@ You could also pass a minified JSON config directly. A minified JSON is a compac e.g. ``` -docker run -it --rm --network host --mount type=bind,source=/tmp/sender,target=/root/sender --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=NET_ADMIN --privileged=true secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/psi-anolis8:latest --json '{"psi_config":{"protocol_config":{"protocol":"PROTOCOL_KKRT","role":"ROLE_RECEIVER","broadcast_result":true},"input_config":{"type":"IO_TYPE_FILE_CSV","path":"/root/receiver/receiver_input.csv"},"output_config":{"type":"IO_TYPE_FILE_CSV","path":"/root/receiver/receiver_output.csv"},"keys":["id0","id1"],"debug_options":{"trace_path":"/root/receiver/receiver.trace"}},"self_link_party":"receiver","link_config":{"parties":[{"id":"receiver","host":"127.0.0.1:5300"},{"id":"sender","host":"127.0.0.1:5400"}]}}' +docker run -it --rm --network host --mount type=bind,source=/tmp/sender,target=/root/sender --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=NET_ADMIN --privileged=true secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/psi-anolis8:latest --json '{"psi_config":{"protocol_config":{"protocol":"PROTOCOL_RR22","role":"ROLE_RECEIVER","broadcast_result":true},"input_config":{"type":"IO_TYPE_FILE_CSV","path":"/root/receiver/receiver_input.csv"},"output_config":{"type":"IO_TYPE_FILE_CSV","path":"/root/receiver/receiver_output.csv"},"keys":["id0","id1"],"debug_options":{"trace_path":"/root/receiver/receiver.trace"}},"self_link_party":"receiver","link_config":{"parties":[{"id":"receiver","host":"127.0.0.1:5300"},{"id":"sender","host":"127.0.0.1:5400"}]}}' ``` ## Building SecretFlow PSI Library @@ -207,6 +207,4 @@ chmod +x traceconv -## PSI V2 Benchamrk -Please refer to [PSI V2 Benchmark](docs/user_guide/psi_v2_benchmark.md) \ No newline at end of file diff --git a/bazel/jsoncpp.BUILD b/bazel/jsoncpp.BUILD index 87d99c7..b18dc15 100644 --- a/bazel/jsoncpp.BUILD +++ b/bazel/jsoncpp.BUILD @@ -30,6 +30,9 @@ psi_cmake_external( "BUILD_OBJECT_LIBS": "OFF", "CMAKE_INSTALL_LIBDIR": "lib", }, + env = { + "CCACHE_DISABLE": "1", + }, lib_source = "@com_github_open_source_parsers_jsoncpp//:all", out_static_libs = ["libjsoncpp.a"], ) diff --git a/bazel/patches/apsi-fourq.patch b/bazel/patches/apsi-fourq.patch index 0e0d2a0..6fd52de 100644 --- a/bazel/patches/apsi-fourq.patch +++ b/bazel/patches/apsi-fourq.patch @@ -1,24 +1,11 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index 55e2d77..80d0afc 100644 +index 78d54a6..166047c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -146,7 +146,11 @@ if(NOT Flatbuffers_FOUND) - message(FATAL_ERROR "Flatbuffers: not found") - else() - message(STATUS "Flatbuffers: found") -- get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_RELEASE) -+ if (CMAKE_BUILD_TYPE STREQUAL "Debug") -+ get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_DEBUG) -+ else() -+ get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_RELEASE) -+ endif() - message(STATUS "flatc path: ${FLATBUFFERS_FLATC_PATH}") - include(CompileSchemaCXX) - endif() -@@ -273,10 +277,9 @@ if(APSI_USE_ZMQ) +@@ -273,10 +273,9 @@ if(APSI_USE_ZMQ) target_link_libraries(apsi PUBLIC libzmq-static cppzmq-static) endif() - + -# Configurations for FourQlib: system, arch, SIMD, and assembler -target_compile_options(apsi PUBLIC -DHAVE_CONFIG) -target_compile_options(apsi PUBLIC -DUSE_SECURE_SEED) @@ -26,7 +13,7 @@ index 55e2d77..80d0afc 100644 +# Add FourQlib +target_include_directories(apsi PUBLIC ${EXT_BUILD_DEPS}/FourQlib/include) +target_link_libraries(apsi PUBLIC FourQ) - + # Set system if(MSVC) diff --git a/common/apsi/CMakeLists.txt b/common/apsi/CMakeLists.txt @@ -36,7 +23,7 @@ index a65bbfe..60e246e 100644 @@ -28,7 +28,6 @@ install( ${APSI_INCLUDES_INSTALL_DIR}/apsi ) - + -add_subdirectory(fourq) add_subdirectory(network) add_subdirectory(oprf) @@ -47,7 +34,7 @@ index bcaa013..93e2b4a 100644 +++ b/common/apsi/network/zmq/zmq_channel.cpp @@ -8,7 +8,7 @@ #include - + // APSI -#include "apsi/fourq/random.h" +#include "random.h" @@ -60,7 +47,7 @@ index d12313f..5fd9be1 100644 +++ b/common/apsi/oprf/ecpoint.cpp @@ -10,10 +10,10 @@ #include "apsi/util/utils.h" - + // FourQ -#include "apsi/fourq/FourQ.h" -#include "apsi/fourq/FourQ_api.h" @@ -70,7 +57,7 @@ index d12313f..5fd9be1 100644 +#include "FourQ_api.h" +#include "FourQ_internal.h" +#include "random.h" - + // SEAL #include "seal/randomgen.h" diff --git a/common/apsi/util/label_encryptor.cpp b/common/apsi/util/label_encryptor.cpp @@ -79,7 +66,7 @@ index 3e00b5e..57a5b40 100644 +++ b/common/apsi/util/label_encryptor.cpp @@ -9,7 +9,7 @@ #include - + // APSI -#include "apsi/fourq/random.h" +#include "random.h" diff --git a/bazel/patches/apsi.patch b/bazel/patches/apsi.patch index f3b0566..20cb990 100644 --- a/bazel/patches/apsi.patch +++ b/bazel/patches/apsi.patch @@ -1,3 +1,20 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 55e2d77..7d3007b 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -146,7 +146,11 @@ if(NOT Flatbuffers_FOUND) + message(FATAL_ERROR "Flatbuffers: not found") + else() + message(STATUS "Flatbuffers: found") +- get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_RELEASE) ++ if (CMAKE_BUILD_TYPE STREQUAL "Release") ++ get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_RELEASE) ++ else() ++ get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_DEBUG) ++ endif() + message(STATUS "flatc path: ${FLATBUFFERS_FLATC_PATH}") + include(CompileSchemaCXX) + endif() diff --git a/receiver/apsi/itt.h b/receiver/apsi/itt.h index e683045..067d244 100644 --- a/receiver/apsi/itt.h diff --git a/bazel/patches/seal.patch b/bazel/patches/seal.patch index 52c04dd..18f9a87 100644 --- a/bazel/patches/seal.patch +++ b/bazel/patches/seal.patch @@ -34,7 +34,7 @@ index 31e07441..6f8e6b2a 100644 uint64_t coeff_modulus_size64 = static_cast(coeff_modulus_.size()); uint8_t scheme = static_cast(scheme_); + uint8_t use_special_prime_size8 = static_cast(use_special_prime_); - + stream.write(reinterpret_cast(&scheme), sizeof(uint8_t)); + stream.write(reinterpret_cast(&use_special_prime_size8), sizeof(uint8_t)); stream.write(reinterpret_cast(&poly_modulus_degree64), sizeof(uint64_t)); @@ -43,7 +43,7 @@ index 31e07441..6f8e6b2a 100644 @@ -63,6 +65,9 @@ namespace seal // This constructor will throw if scheme is invalid EncryptionParameters parms(scheme); - + + uint8_t use_special_prime_size8; + stream.read(reinterpret_cast(&use_special_prime_size8), sizeof(uint8_t)); + @@ -55,7 +55,7 @@ index 31e07441..6f8e6b2a 100644 parms.set_poly_modulus_degree(safe_cast(poly_modulus_degree64)); parms.set_coeff_modulus(coeff_modulus); + parms.set_use_special_prime(use_special_prime_size8); - + // Only BFV and BGV uses plain_modulus; set_plain_modulus checks that for // other schemes it is zero @@ -128,6 +134,7 @@ namespace seal @@ -64,12 +64,12 @@ index 31e07441..6f8e6b2a 100644 size_t(1), // poly_modulus_degree + size_t(1), // use_special_prime coeff_modulus_size, plain_modulus_.uint64_count()); - + auto param_data(allocate_uint(total_uint64_count, pool_)); @@ -139,6 +146,7 @@ namespace seal // Write the poly_modulus_degree. Note that it will always be positive. *param_data_ptr++ = static_cast(poly_modulus_degree_); - + + *param_data_ptr++ = static_cast(use_special_prime_); for (const auto &mod : coeff_modulus_) { @@ -81,7 +81,7 @@ index 9e1fbe48..8530eeeb 100644 @@ -266,6 +266,11 @@ namespace seal random_generator_ = std::move(random_generator); } - + + inline void set_use_special_prime(bool flag) + { + use_special_prime_ = flag; @@ -93,7 +93,7 @@ index 9e1fbe48..8530eeeb 100644 @@ -274,6 +279,11 @@ namespace seal return scheme_; } - + + bool use_special_prime() const noexcept + { + return use_special_prime_; @@ -111,9 +111,9 @@ index 9e1fbe48..8530eeeb 100644 sizeof(std::uint64_t), // coeff_modulus_size coeff_modulus_total_size, @@ -501,6 +512,8 @@ namespace seal - + Modulus plain_modulus_{}; - + + bool use_special_prime_ = true; + parms_id_type parms_id_ = parms_id_zero; @@ -128,7 +128,7 @@ index dabd3bab..61a96ae9 100644 // Use key_context_data where permutation tables exist since previous runs. auto galois_tool = context_.key_context_data()->galois_tool(); + bool is_ntt_form = encrypted.is_ntt_form(); - + // Size check if (!product_fits_in(coeff_count, coeff_modulus_size)) @@ -2412,7 +2413,7 @@ namespace seal @@ -139,7 +139,7 @@ index dabd3bab..61a96ae9 100644 + if (not is_ntt_form) { // !!! DO NOT CHANGE EXECUTION ORDER!!! - + @@ -2426,7 +2427,7 @@ namespace seal // Next transform encrypted.data(1) galois_tool->apply_galois(encrypted_iter[1], coeff_modulus_size, galois_elt, coeff_modulus, temp); @@ -148,7 +148,7 @@ index dabd3bab..61a96ae9 100644 + else { // !!! DO NOT CHANGE EXECUTION ORDER!!! - + @@ -2440,10 +2441,6 @@ namespace seal // Next transform encrypted.data(1) galois_tool->apply_galois_ntt(encrypted_iter[1], coeff_modulus_size, galois_elt, temp); @@ -157,7 +157,7 @@ index dabd3bab..61a96ae9 100644 - { - throw logic_error("scheme not implemented"); - } - + // Wipe encrypted.data(1) set_zero_poly(coeff_count, coeff_modulus_size, encrypted.data(1)); @@ -2530,6 +2527,7 @@ namespace seal @@ -165,7 +165,7 @@ index dabd3bab..61a96ae9 100644 auto &key_parms = key_context_data.parms(); auto scheme = parms.scheme(); + bool is_ntt_form = encrypted.is_ntt_form(); - + // Verify parameters. if (!is_metadata_valid_for(encrypted, context_) || !is_buffer_valid(encrypted)) @@ -2559,14 +2557,6 @@ namespace seal @@ -185,7 +185,7 @@ index dabd3bab..61a96ae9 100644 throw invalid_argument("BGV encrypted must be in NTT form"); @@ -2605,7 +2595,7 @@ namespace seal set_uint(target_iter, decomp_modulus_size * coeff_count, t_target); - + // In CKKS or BGV, t_target is in NTT form; switch back to normal form - if (scheme == scheme_type::ckks || scheme == scheme_type::bgv) + if (is_ntt_form) @@ -194,7 +194,7 @@ index dabd3bab..61a96ae9 100644 } @@ -2632,7 +2622,7 @@ namespace seal ConstCoeffIter t_operand; - + // RNS-NTT form exists in input - if ((scheme == scheme_type::ckks || scheme == scheme_type::bgv) && (I == J)) + if (is_ntt_form && (I == J)) @@ -203,7 +203,7 @@ index dabd3bab..61a96ae9 100644 } @@ -2789,7 +2779,7 @@ namespace seal SEAL_ITERATE(t_ntt, coeff_count, [fix](auto &K) { K += fix; }); - + uint64_t qi_lazy = qi << 1; // some multiples of qi - if (scheme == scheme_type::ckks) + if (is_ntt_form) @@ -226,15 +226,15 @@ index 9e3dd576..bb598ddf 100644 @@ -1355,10 +1355,12 @@ namespace seal apply_galois_inplace(encrypted, galois_tool->get_elt_from_step(0), galois_keys, std::move(pool)); } - + + public: void switch_key_inplace( Ciphertext &encrypted, util::ConstRNSIter target_iter, const KSwitchKeys &kswitch_keys, std::size_t key_index, MemoryPoolHandle pool = MemoryManager::GetPool()) const; - + + private: void multiply_plain_normal(Ciphertext &encrypted, const Plaintext &plain, MemoryPoolHandle pool) const; - + void multiply_plain_ntt(Ciphertext &encrypted_ntt, const Plaintext &plain_ntt) const; diff --git a/native/src/seal/serializable.h b/native/src/seal/serializable.h index a940190c..e490b302 100644 @@ -243,7 +243,7 @@ index a940190c..e490b302 100644 @@ -135,6 +135,9 @@ namespace seal return obj_.save(out, size, compr_mode); } - + + const T& obj() const { return obj_; } + + T& obj() { return obj_; } diff --git a/bazel/psi.bzl b/bazel/psi.bzl index 82a3919..61f0a79 100644 --- a/bazel/psi.bzl +++ b/bazel/psi.bzl @@ -24,7 +24,7 @@ WARNING_FLAGS = [ "-Wextra", "-Werror", ] -DEBUG_FLAGS = ["-O0", "-g"] +DEBUG_FLAGS = ["-O0", "-g", "-DSPDLOG_ACTIVE_LEVEL=1"] RELEASE_FLAGS = ["-O2"] FAST_FLAGS = ["-O1"] diff --git a/docker/build.sh b/docker/build.sh index abddf40..eb88fc6 100644 --- a/docker/build.sh +++ b/docker/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -ex show_help() { echo "Usage: bash build.sh [OPTION]... -v {the_version}" @@ -69,13 +69,17 @@ LATEST_TAG=${DOCKER_REG}/psi-anolis8:latest echo -e "Build psi binary ${GREEN}PSI ${PSI_VERSION}${NO_COLOR}..." +SCRIPT_DIR="$(realpath $(dirname $0))" + if [[ SKIP -eq 0 ]]; then - docker run -it --rm --mount type=bind,source="$(pwd)/../../psi",target=/home/admin/dev/src -w /home/admin/dev --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=NET_ADMIN --privileged=true secretflow/release-ci:1.4 /home/admin/dev/src/docker/entry.sh + docker run -it --rm --mount type=bind,source="${SCRIPT_DIR}/../",target=/home/admin/dev/src -w /home/admin/dev --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=NET_ADMIN --privileged=true secretflow/release-ci:latest /home/admin/dev/src/docker/entry.sh echo -e "Finish building psi binary ${GREEN}${IMAGE_LITE_TAG}${NO_COLOR}" fi +cd $SCRIPT_DIR + echo -e "Building docker image ${GREEN}${IMAGE_TAG}${NO_COLOR}..." -docker build . -f Dockerfile -t ${IMAGE_TAG} --build-arg version=${VERSION} --build-arg config_templates="$(cat config_templates.yml)" --build-arg deploy_templates="$(cat deploy_templates.yml)" +docker buildx build --platform linux/amd64 -f Dockerfile -t ${IMAGE_TAG} --build-arg version=${VERSION} --build-arg config_templates="$(cat config_templates.yml)" --build-arg deploy_templates="$(cat deploy_templates.yml)" . echo -e "Finish building docker image ${GREEN}${IMAGE_LITE_TAG}${NO_COLOR}" if [[ UPLOAD -eq 1 ]]; then @@ -92,3 +96,5 @@ if [[ LATEST -eq 1 ]]; then fi echo ${VERSION} > version.txt + +cd - diff --git a/docker/entry.sh b/docker/entry.sh old mode 100644 new mode 100755 diff --git a/docs/user_guide/index.rst b/docs/user_guide/index.rst index 6491509..dad825f 100644 --- a/docs/user_guide/index.rst +++ b/docs/user_guide/index.rst @@ -9,5 +9,4 @@ PSI v2 is recommended to use. We are still working on PIR code refactoring. psi psi_v2 pir - faq - psi_v2_benchmark \ No newline at end of file + faq \ No newline at end of file diff --git a/docs/user_guide/pir.rst b/docs/user_guide/pir.rst index 68cf2ad..695bc71 100644 --- a/docs/user_guide/pir.rst +++ b/docs/user_guide/pir.rst @@ -6,13 +6,13 @@ Quick start with SPU Private Information Retrival (PIR). Supported Protocols ------------------- -+---------------+---------------+---------------+ -| PIR protocols | Type | Server Number | -+===============+===============+===============+ -| SealPIR | Index PIR | Single Server | -+---------------+---------------+---------------+ -| APSI | Keyword PIR | Single Server | -+---------------+---------------+---------------+ ++----------------+-------------+---------------+ +| PIR protocols | Type | Server Number | ++================+=============+===============+ +| SealPIR(later) | Index PIR | Single Server | ++----------------+-------------+---------------+ +| APSI | Keyword PIR | Single Server | ++----------------+-------------+---------------+ At this moment, SealPIR is temporaily removed and will come back later. @@ -75,6 +75,7 @@ CSV File .. code-block:: + key,value Yb,Ii Kw,uO LA,Oc @@ -87,9 +88,10 @@ CSV File Please make sure: -- No headers are allowed. -- The first column must be items(keys) -- The second column must be labels(values), this column is optional. +- Since version **0.4.0b0**, headers line is required. +- The first row must be headers, only **key** and **value**(optional) are allowed. +- The **key** column must be items(keys) +- The **value** column must be labels(values), this column is optional. APSI Params File @@ -111,13 +113,46 @@ PIR Config """""""""" 1. Sender: Setup Stage. In this stage, sender generates sender db file with csv file. This stage is offline. +Since version **0.4.0b0**, the source csv file for db generating should be specified as **source_file**, and **db_file** +is used to specify db file. .. code-block:: :caption: apsi_sender_setup.json { "apsi_sender_config": { - "db_file": "/tmp/db.csv", + "source_file": "/tmp/db.csv", + "params_file": "/tmp/1M-256-288.json", + "sdb_out_file": "/tmp/sdb" + } + } + +2. Sender: Online stage. In this stage, sender generates responses to receivers' queries. This stage is online. + +.. code-block:: + :caption: apsi_sender_online.json + + { + "apsi_sender_config": { + "db_file": "/tmp/sdb" + }, + "link_config": { + "parties": [ + { + "id": "sender", + "host": "127.0.0.1:5300" + }, + { + "id": "receiver", + "host": "127.0.0.1:5400" + + +.. code-block:: + :caption: apsi_sender_setup.json + + { + "apsi_sender_config": { + "source_file": "/tmp/db.csv", "params_file": "/tmp/1M-256-288.json", "sdb_out_file": "/tmp/sdb", "save_db_only": true @@ -190,7 +225,7 @@ Bucketized Mode Searching in a large sender db is costly. So can we search in a smaller db? A naive idea is: -1. In the setup stage, sender split data into buckets. Each bucket will generate a sender db.\ +1. In the setup stage, sender split data into buckets. Each bucket will generate a sender db. 2. In the online stage, receiver split query into subqueries. Each subquery only contains items residing in the same bucket. When receivers sends a subquery to the sender, bucket idx is also provided. @@ -198,4 +233,3 @@ When receivers sends a subquery to the sender, bucket idx is also provided. 3. For each subquery, sender only search in the corresponding sender db for specific bucket. Bucketized Mode is experimental and for evaluation purposes only. - diff --git a/docs/user_guide/psi_v2_benchmark.md b/docs/user_guide/psi_v2_benchmark.md deleted file mode 100644 index 9d3dc2b..0000000 --- a/docs/user_guide/psi_v2_benchmark.md +++ /dev/null @@ -1,415 +0,0 @@ -# PSI V2 Benchmark - -This document will introduce the PSI V2 Benchmark. It uses the PSI V2 version of the interface. -## Building from source - -``` -git clone https://github.com/secretflow/psi.git -cd psi -bazel build //... -c opt -``` - -If the building is successful, you will find an executable file named `main` in the 'bazel-bin/psi' directory. We will use `./main` file, combined with config file to run different PSI protocols. Such as: - -``` -./main --config configs/sender.config -./main --config configs/receiver.config - -``` -## Generate Data - - -In order to measure the performance of different PSI protocols under different data scales, we need to generate dummy data through [generate_psi.py](https://github.com/secretflow/secretflow/blob/main/docs/developer/benchmark/resources/generate_psi.py) - -```python -python3 generate_psi.py 10000000 10000000 - -python3 generate_psi.py 100000000 100000000 -``` - -## Prepare config file - -We use the config file to specify different PSI protocols and input data. - -### sender.config -``` -{ - "psi_config": { - "protocol_config": { - "protocol": "PROTOCOL_KKRT", - "role": "ROLE_SENDER", - "broadcast_result": false - }, - "input_config": { - "type": "IO_TYPE_FILE_CSV", - "path": "/home/zuoxian/v2psi/datas/psi_1_1kw.csv" - }, - "output_config": { - "type": "IO_TYPE_FILE_CSV", - "path": "/home/zuoxian/v2psi/sender/sender_output.csv" - }, - "keys": [ - "id", - ], - "debug_options": { - "trace_path": "/home/zuoxian/v2psi/sender/sender.trace" - }, - "skip_duplicates_check": false, - "disable_alignment": false, - "advanced_join_type": "ADVANCED_JOIN_TYPE_UNSPECIFIED", - "left_side": "ROLE_RECEIVER", - "check_hash_digest": false, - "recovery_config": { - "enabled": false - } - }, - "link_config": { - "parties": [ - { - "id": "receiver", - "host": "127.0.0.1:5300" - }, - { - "id": "sender", - "host": "127.0.0.1:5400" - } - ] - }, - "self_link_party": "sender" -} -``` - - -### receiver.config -``` -{ - "psi_config": { - "protocol_config": { - "protocol": "PROTOCOL_KKRT", - "role": "ROLE_RECEIVER", - "broadcast_result": false - }, - "input_config": { - "type": "IO_TYPE_FILE_CSV", - "path": "/home/zuoxian/v2psi/datas/psi_2_1kw.csv" - }, - "output_config": { - "type": "IO_TYPE_FILE_CSV", - "path": "/home/zuoxian/v2psi/receiver/receiver_output.csv" - }, - "keys": [ - "id", - ], - "debug_options": { - "trace_path": "/home/zuoxian/v2psi/receiver/receiver.trace" - }, - "skip_duplicates_check": false, - "disable_alignment": false, - "advanced_join_type": "ADVANCED_JOIN_TYPE_UNSPECIFIED", - "left_side": "ROLE_RECEIVER", - "check_hash_digest": false, - "recovery_config": { - "enabled": false - } - }, - "link_config": { - "parties": [ - { - "id": "receiver", - "host": "127.0.0.1:5300" - }, - { - "id": "sender", - "host": "127.0.0.1:5400" - } - ] - }, - "self_link_party": "receiver" -} -``` - - -## Run PSI with docker - -In order to measure the PSI V2 benchmark under different machine configurations and network configurations, we use two dockers to act as sender and receiver respectively. - - -**alice** - -``` -docker run -d -it --name "alice" \ - --mount type=bind,source="$(pwd)",target=/home/admin/dev/ \ - -w /home/admin/dev \ - --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ - --cap-add=NET_ADMIN \ - --cpuset-cpus 0-31 \ - --privileged=true \ - --memory="64g" \ - --entrypoint="bash" \ - secretflow/ubuntu-base-ci:latest - -docker start alice -docker exec -it alice bash -``` - - -**bob** - -``` -docker run -d -it --name "bob" \ - --mount type=bind,source="$(pwd)",target=/home/admin/dev/ \ - -w /home/admin/dev \ - --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ - --cap-add=NET_ADMIN \ - --cpuset-cpus 32-63 \ - --privileged=true \ - --memory="64g" \ - --entrypoint="bash" \ - secretflow/ubuntu-base-ci:latest - -docker start bob -docker exec -it bob bash -``` - -## Limit bandwidth and latency - -``` -BAND_WIDTH=100 -burst=128 -DELAY=10 -netem_limit=8000 - -tc qdisc add dev eth0 root handle 1: tbf rate ${BAND_WIDTH}mbit burst ${burst}kb latency 800ms -tc qdisc add dev eth0 parent 1:1 handle 10: netem delay ${DELAY}msec limit ${netem_limit} -``` - - -## Benchmark - -Here we show the PSI V2 Benchmark measured as above. -> The default time unit is seconds, `m` represents minutes, and `h` represents hours. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Machine ConfigurationAlgorithm parametersProtocolNetwork Configuration10 million~10 million100 million to 100 million1 billion to 1 billion
32C64Greceiver='alice',
protocol='ECDH_PSI_2PC',
curve_type='CURVE_FOURQ',
precheck_input=False,
sort=False,
broadcast_result=False,
ECDH-PSI-2PC
(FourQ)
LAN424591.45 h
100Mbps/10ms635591.72 h
receiver='alice',
protocol='ECDH_PSI_2PC',
curve_type='CURVE_25519',
precheck_input=False,
sort=False,
broadcast_result=False,
ECDH-PSI-2PC
(CURVE_25519)
LAN676972.09 h
100Mbps/10ms696692.08 h
receiver='alice',
protocol='KKRT_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
KKRT_PSI_2PC
(BUCKET SIZE IS ONE MILLION)
LAN383571.13 h
100Mbps/10ms12512583.53 h
receiver='alice',
protocol='RR22_FAST_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
RR22_FAST_PSI
_2PC
(BUCKET SIZE IS ONE MILLION)
LAN172250.62 h
100Mbps/10ms444521.43 h
receiver='alice',
protocol='RR22_LOWCOMM_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
RR22_LOWCOMM
_PSI_2PC
(BUCKET SIZE IS ONE MILLION)
LAN212230.75 h
100Mbps/10ms383881.21 h
16C32Greceiver='alice',
protocol='ECDH_PSI_2PC',
curve_type='CURVE_FOURQ',
precheck_input=False,
sort=False,
broadcast_result=False,
ECDH-PSI-2PC
(FourQ)
LAN464971.66 h
100Mbps/10ms625742.00 h
receiver='alice',
protocol='ECDH_PSI_2PC',
curve_type='CURVE_25519',
precheck_input=False,
sort=False,
broadcast_result=False,
ECDH-PSI-2PC
(CURVE_25519)
LAN697012.23 h
100Mbps/10ms747442.49 h
receiver='alice',
protocol='KKRT_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
KKRT_PSI_2PC
(BUCKET SIZE IS ONE MILLION)
LAN373521.18 h
100Mbps/10ms12412283.69 h
receiver='alice',
protocol='RR22_FAST_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
RR22_FAST_PSI
_2PC
(BUCKET SIZE IS ONE MILLION)
LAN171780.72 h
100Mbps/10ms434521.70 h
receiver='alice',
protocol='RR22_LOWCOMM_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
RR22_LOWCOMM
_PSI_2PC
(BUCKET SIZE IS ONE MILLION)
LAN232160.92 h
100Mbps/10ms403841.52 h
8C16Greceiver='alice',
protocol='ECDH_PSI_2PC',
curve_type='CURVE_FOURQ',
precheck_input=False,
sort=False,
broadcast_result=False,
ECDH-PSI-2PC
(FourQ)
LAN666692.6 h
100Mbps/10ms666662.6 h
receiver='alice',
protocol='ECDH_PSI_2PC',
curve_type='CURVE_25519',
precheck_input=False,
sort=False,
broadcast_result=False,
ECDH-PSI-2PC
(CURVE_25519)
LAN11412253.8 h
100Mbps/10ms11211243.8 h
receiver='alice',
protocol='KKRT_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
KKRT_PSI_2PC
(BUCKET SIZE IS ONE MILLION)
LAN373511.6 h
100Mbps/10ms12412114.0 h
receiver='alice',
protocol='RR22_FAST_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
RR22_FAST_PSI
_2PC
(BUCKET SIZE IS ONE MILLION)
LAN191661.08 h
100Mbps/10ms444401.91 h
receiver='alice',
protocol='RR22_LOWCOMM_PSI_2PC',
precheck_input=False,
sort=False,
broadcast_result=False,
RR22_LOWCOMM
_PSI_2PC
(BUCKET SIZE IS ONE MILLION)
LAN222041.17 h
100Mbps/10ms373741.72 h
diff --git a/examples/pir/README.md b/examples/pir/README.md index 17f452d..351eb81 100644 --- a/examples/pir/README.md +++ b/examples/pir/README.md @@ -5,7 +5,7 @@ 1. Generate test data. ```bash -python examples/pir/apsi/test_data_creator.py 100000 1 1 16 +python examples/pir/apsi/test_data_creator.py --sender_size=100000 --receiver_size=1 --intersection_size=1 --label_byte_count=16 mv db.csv /tmp/db.csv mv query.csv /tmp/query.csv @@ -14,8 +14,9 @@ cp examples/pir/apsi/parameters/100K-1-16.json /tmp/100K-1-16.json ``` **NOTE** -1. We distinguish between labeled mode and unlabeled mode based on the number of columns in the db.csv file. If db.csv has 2 columns, the first column represents the key and the second column represents the value, and labeled mode is automatically enabled. Otherwise, it's unlabeled mode, which can be considered a non-balanced PSI. -2. In APSI, selecting appropriate parameters for databases of different scales is a challenging task. If you aim for optimal performance, seek support from professionals. +1. The csv file should have head line, and the first column represents the key, and the second column which is optional represents the label. So first line of the csv file should be `key,value` or `key`. +2. We distinguish between labeled mode and unlabeled mode based on the number of columns in the db.csv file. If db.csv has 2 columns, the first column represents the key and the second column represents the value, and labeled mode is automatically enabled. Otherwise, it's unlabeled mode, which can be considered a non-balanced PSI. +3. In APSI, selecting appropriate parameters for databases of different scales is a challenging task. If you aim for optimal performance, seek support from professionals. diff --git a/examples/pir/apsi/data/README.md b/examples/pir/apsi/data/README.md index e7810d6..9148bb5 100644 --- a/examples/pir/apsi/data/README.md +++ b/examples/pir/apsi/data/README.md @@ -2,10 +2,10 @@ The files are generated with `examples/pir/apsi/test_data_creator.py` for testin 1. `labeled_db.csv` and `query_to_labeled_db.csv` are generated with ```bash -python test_data_creator.py 100 10 10 0 2 +python test_data_creator.py --sender_size=1000 --receiver_size=10 --intersection_size=10 --label_byte_count=32 --item_byte_count=32 ``` 2. `db.csv` and `query\.csv` are generated with ```bash -python test_data_creator.py 1000 10 10 2 2 +python test_data_creator.py --sender_size=1000 --receiver_size=10 --intersection_size=10 --label_byte_count=0 --item_byte_count=32 ``` diff --git a/examples/pir/apsi/data/db.csv b/examples/pir/apsi/data/db.csv index b4ea11e..fa683d7 100644 --- a/examples/pir/apsi/data/db.csv +++ b/examples/pir/apsi/data/db.csv @@ -1,100 +1,1001 @@ -dV -Zo -PD -yn -eS -Ae -eF -aE -CU -CA -Gv -NV -Zz -Yf -KE -Ft -uU -tN -gm -jV -ca -ng -yc -FQ -LJ -tR -YB -lf -Xk -VS -Wr -Tn -oP -aa -Ra -jG -rt -WX -EB -VN -Ye -Nn -gB -xo -cE -Sk -cH -MT -lu -TD -sK -dV -Gi -br -ke -AQ -TH -NY -vB -tY -rr -Es -Ql -KX -aE -wL -FQ -vg -Iy -lC -gg -TC -ck -wW -UK -rZ -vb -CF -xJ -tg -GG -OF -oE -KU -ZS -AZ -mC -aX -iJ -di -PR -fZ -GD -ar -iU -Yu -uk -Nl -vA -ID +key +fewPzlVQbdGzWcpIQNoFTHHGmyHfhtZL +cCDIklNjCAHiQVWjTWnewRWgCNnncqjr +DMKrrLBYyyeghykbDdhbMLHvIIyanEbI +mFlYBTHAmZvIAiJFTXWxsLjvKoAODWfF +hABRoHuJtFdYVnWoQuMAwrebLsXOOViZ +pxlSukFAQDecskXCDnVaykHqmfXTGMTG +FMmyaTPrinFxGOUxZykwDTyILrddUeoX +tyPmyhPawVfmigpEoQQoppQKwYgxrtAj +JrHuJQrxyOfZmxwoHmKLwdskfWXrnjKj +jLxnDiASAEJClKTNlbanWVufVCbeKXSB +upxLxWVeiiSdNtvPyQhQhWDMAIVStWdX +BMzjNWayGhSTnVbKVEITJpiRvpiWYOvY +RnQrBcVMKcNjTjbqCuDoIofmQzATZXyq +tJXgBSdxaoCpJaWuHsUPugjFimrsdjll +kbxkhGbbwmhOudwZgrnHjOuDoPbMkzVG +ccNmaQucrxYxKUILbwwDVESbuvDSURJA +cYtJdqApueSeouiIaLxWokSWttFtkFGI +JnGODmhAThNPZDHlemmTwWFqXzsCwKEA +EuxXEuwhAZFrzjEuYSDhlUcTKXlqsslR +mtfOBEdGdwFsyjBCxyNHwItiIWdFuxQq +mOnjtPiNUWTqRQmYqFYDdPEvAiWvKCJH +gAzoHGehWbeYKFNNyXGhajZfMrWamSCZ +TdOLinswZGzAQczTRitYSJwsBXsvHBkj +DLOkONfVtbcYvSppGgBwDkAhzLomNptz +xXmxOmDglBtMnCwtiIDDjISSJKTdlZTj +EIVBncZrNLTJKKmeiWcCZBiMkhDCYSlg +TEWmoWWwnlWlFjILdwzddEuaRBhpdBOO +TidDqjAENppBirPEcsDKhOYqVoxchzbW +qMoWOSXBwPCGxlOXuRTTUwFmWvHqMHQH +CvSByWVmjkdELxncJdxgOTZfHrjtOujG +TKScsyPSYwapYJJHTXoSfQeTwnVIRAQj +NkdjFmZdMIAZLbErMCFreyhgVYdlBuNb +wrHyXYlYDmDFnTYApeoZJRYBymZIzTze +BSIKaJMXbxMkNzobrpMqWUMfPzxbPmFV +APpWBOEujLsnAKHlFoNLTeFDGGcBvAZR +OBKGuMHIJgYWKmlgjYCPYdgXMpXrJiis +kfjVUBPNlqzFkFsBeSvPiKhWFzLyDNRm +iQLPHMrLYwtEguzexppSnrZWAsgElrfs +zrIvijvYCMnKmVIKFldpApRloYHWOTxa +EaqsBtkZLtAYsCjfZAraAyFaODKSAtpo +qZoDqUNrjSksPhpxkVuclWlQClWtAPkX +RXqDfGbxJqaiImQrbXyeCXkOrsliCQaF +IIZVCNiJFoPFKJZmyDzxWQubMziniBzn +pROXaPyTKYCTDUnrVuIVjpPyiwhkIPny +VwjvQCVxEQrGaPqEjuNKqyPiuDuDsexA +qMgLUcITtnzketADWMENxlqNwrEozPJd +UnLNoNonoqYmmSdxAkkxuVbhXsUEcrKY +NxxWAzoIopqtDaNKNjRgvtgkUbUshwGM +dputkmAkdvzkzweBrtfuWXUxDkrxkyQj +GkMaNDBILouPEHfvfRHPfpnTlwiKzCmH +PWOaFMODfsJzlmBdaFXoHOowwtwRiFgT +jtxNvfoqrXbiAXwnUkftjzbUplgZJmvW +IOzgHvIuAQXGDrvyHanRcMysXCCJflsz +NFHcxmYqNkWUMpdkgOAFSmyKZAerERhu +OLBOiSeNiYrdfBnrZLaDVEeWYXxMAQKs +itSvEIhJgLufRegrUgbGWUlzAHgwaaGD +qSIEKyDoQtopGpAAgMRqEfLYTeYhPGCW +lsNwzWJArJmwcsAucfoValgQVmLEdOWA +wNGJQNNrCDYhOrZwZzjEdGJjAbtzWUeR +kaaxylgTSKHpWFhEEywoQpIxAzwIxbQF +qvVDzsCQzCVdibfSkhjVNMYKebIstzEf +AcuBMoFVHHhApunJzyDBwPEmIRfQrmHw +StqqVommVvCXmENhpwwEwADoyeZDPyoy +OjHvlUCzQvvSXlLxemhCnftQuMZwFUbA +IZaOuBAyVdecJQiDTjGjflGHPZMMruun +ZqVqACToYwuQqOChEpXdOERUmNeucfXz +HlkdvxzBfhXgJAWldWbkLeNNBWbctXtI +UXryKBUKFcItpziIQARGMpdBxFaqdxLB +mEikPSXfAxyodVksioJZHAKqdqbLvJQd +UFFGIBdZwPwWUgyTYnBBjwvUCEYndYRE +OkVqoRSNSZlasCOdEJvpsMhKQmkcQSpV +cYmWDcxtySNUAzmfRdYUidtfRezZJbwH +KfqgppSfqSjJqTKVulCelUyoCdyAbBSQ +gOdPiaAzfuOZhccFrkfmDqFHfzqumakk +oXnWDDdYpWsUVmrLWqtRjdFPEtNPCapf +irPPkkrRoomvjNTWMzPCPSdWocuMtwZZ +oLvhpAVPdFhANLWFXCFVcVraMAumApzn +tzSqsgVnwzdsOlDGVJzuyGlUAgFPlxxr +kVFaZAbLDsQqTIOkNCQTmSMsGxYpxWaJ +mHEWgeCjFHJkFHsfMPpWtgKRKxVVhsLV +AdsVUUDJCSjiXmOBDTKUREMXmtAFPRmo +RXKCfLjsUILOzdFbgRSsoNbGFknBvYfi +dCZGPlPscjPniQdCAzpvWoyCXhbhCSCW +pKjHSjibsNNXtHvWUgDCrhaqNeReTtLg +aubbzViolGVHcCoxxKTDljFunPRGCUeB +DNRSQJedneWEeFLCVFiHiSyOoAFcnJXX +rGDHkcROyaFdMStlfPEruMOQQDSKnFHX +BkQifmlDBGHLVQmKFDlBeBikAwcpebNv +IIsxBTnreevnkpDFtuUzJqMIZuINnsLg +EIFgbGgQUdXhfPWdYZSGbYjEiyWkwZxT +HNXeBlqShUGGLdBFOHZthhffLOsepfoC +wbFVMQhTqBcToiLHcRjFgNpIoKjQJBCC +woSBScGHfqjtIYlTIaPhfGnwncgLnKdC +SyebcZPTsvhObgvbIYicKVLoYflhdgFe +iIGBgZBqCvodvjJHelZxfuIttrfodhRb +LKnvBkrAHKtkeNCFRhgCSKQADTVlcKuU +WDsJAYPhRhPKVlhmhjhzZMmwEzAmEUSP +LacIxeyaeJImPuripwkUPPzbESrqLeOf +hlPnKipavaorSuycomynrYDlRapbdatu +lsrVfTeydawZaBgBzNbwJdBFcXmQcnjR +FSibxwihZXkHnpMUpZrBHugeZPODTWep +BvxmpDBLnnXpegEWduGEIpAJZvJlJUIT +SpGyJeaxuGrONNKJbMgUQnRSjwATswHY +LdJhIZdtWfLdZTGrilovfrisAlgCVVId +xjgieZgpkmhdSuITEAczXeUGXjWFlQNQ +LEGukXRREQcMAQBZBIHwiTLaABmksRoj +KDuXEjdzlcXVEaBXCPBcoDnjkvwNQWDT +xEddOSDFRQYxRhKCQndvQyEkNrlYbtoH +RANgOiXXYjIScDbeXjoArldLXNNFrecX +WVOUwdbKQRGXBOtUIKySfGXFcLHsBzcq +wIYfrXkcPGjuSgODPhwHeOdpjKXRJnpI +khCBzClIMUyAsBEaZfCpIwhHQaJgUWoJ +JvKPpjCaanzdziGMRONrDiClbMyTXSUq +bdpnSHRpfmlycEbBcSCmvPjTnGWwLDPy +vKZRAMJrLKGHWqoqKiPcExpTeYKcTreX +dXnyJafgrpmURGZxUdDHSEbTBrmNAwpY +hInIaCGPbvirTsMqRMbBdDGauuJyFRkJ +xYjqLyCrWimUbEFdrmlrOjYXEBeOkBXy +iWxIdXacgnaZTfyldLBnkVsIPNnbRZYh +wzDBdZqgIqOdmtvWhmqttjjpmmAgfqKe +xtKcDERIZwUEzisKZQYFXIAsGghtuAxr +MBpJCODDeGJIKhQXytICfxcikkYazIhl +cKGJlOINpTgJncDJgdXwLUaZDrWoMyKY +yvSESzRDFHsGamLQuPoHeSDqmnDsaYlD +zVMmHFzKDHuuiSVIsOrwzaHTtSDybJFE +svXZKHuZquodTRbMeKAESHZerIvArtjv +VLhUqPeySsfAhBclYhzmGteDDqdnSsiO +OyEQCnAKarPhMyamDFNfTSPgwtzxeJGx +byfuCtJVuVvdRFvITybLuckFUqCyDBGZ +oxvvTBNAYmhCyFoYtUrAILLpyEkzsfpn +bCWoodDfarXxoxjozBwlPEULLclpECsz +eRiviropyTUwaZHfMDEVOrQiAOmECreA +NcjqXdtKWmQOALMpmMWXRDjmQCuEQFIA +IjnyZQQHgqEfiOowabCSyNBtDhxMByED +eOXWQYkcTaTFkXYhtvvttswVHZrppKcZ +eLinwWHQETMmbYCiNkpCjtAqctCHOIFb +xPqQmTrzSKRNtMwGYLQYdFiKxnwWuXGt +YVKjeCEBZqQlASgYdUKVJlxYrtIOXiFR +xPCEeNgfkYtxcsRPABZsqVUNPlRGzdYp +lUZFeRGaerPHzbUssLsMAyuNWYbeTlfE +pwoOzycqYYzRsbCKCOjoYaofYQhPNVIM +JFgrXyteDwgBsBQwBhoGtJBfNyWPOHjS +dJoLBBmyZTMpxbyWVARYHfUMhkSgTRfP +XmnNTjPKXaQgLBvalZsZqWsStzfwxggH +EHBoHSpPnZNQybdDOwwPgfZmTVLygsCU +OBtFSLuLAVZAmlfSfNqKqccgMYQoUNzI +faeuMglgRyqyOPjXoMPHGHGDptQIzPsu +NMeuYxvyqInqdyNjOCtRduizcQcdmZno +yRjLlhjuzgqlSenkHpwjSASMWmuHJTZH +DTGgixZcJAtsAOZNGKntvBhhKhylKugV +RUHNsjZyjXoMMraIVnzNlexLlRwjmCbE +zPWKQtNfiQOOxAoXHQQXYkYxdTJdaTOo +lYnpmbVikUTMZprFQNYxqhQLuRbVIOXk +KXSwaavStbBkCOYcbCjKSvhsLgHbjBeM +dqXDExQqBNoqpvKkbpQyMzqMZRNYnnbU +CbCzngDDASfhvXtEbbAeVpoHKdNCmoOk +WvxYuxgltkOmKDLrRfHbMsSqfBdlaaCR +RmrEvdMbfZQhNnWYBwPtvIdAODJMLjuv +WubbDPQaSmPhNJIwpYcLrGtEenCtQAgx +LeLtoxlBYiopFfvNieCRBrMSzhElbNGM +TvKSsANDOpiBBUaaLWPlqNcRQzLyIVIc +jeFuPgVNpJYKqjDHMLsYWvLBxnygsnAX +sNSYbYeKiKyIYbRocJQBwdLWNrdYVghg +YnaftdnGlIsvQFXtmCEbdizfFXefahoz +LilTuFSEbGGCjvooVJpUoRnGsfMAzNev +GiZmFDVbaiWHsnrwhbtHDPZtltexEhZB +fkUkYTIcbGAOBAlKCSLGOCVqeOxMJkoC +fLcSAeakzPnYejHnTfjXMwYWUbtEqDzZ +qqGrhHgVWdbOdaMksnbPFjRQVdngPaig +fwdXekVFGORNjVlDNvoNUPNCAvnQcJeG +pZvTjuWPJDyppgtDskeWLDSjydYDVeZh +AVTtbHobyIdEohUaCgGciKJcSxsAdWFD +yCIYLeNRbWMjTMyIqSLfpZaEzMkRAqIN +dsjrLRneFMyvkjLONapQupQwKagRRoCZ +RbIoBpGqemKgGhqSMdwdcOajUBDcYPoh +kVdOjWfriYkSpERZZncqzDtGWTlCxKhX +SJunpJbxoCARrjoMHmYmnqJPchPPSufB +dfyASCdpDQInjPLnmyvabhWoBEoUBKfM +ypwNjNkwtVrYwRedKPeIqsnliKmWChKy +oVJOXTrgMYkGIQlgvJAJFasdvOqgZtdC +SkPIimnzALRUVFuASucrhimPRqilGnEg +rBWfrIsJAopEqtHRVUkxCBucwpkwCWYB +CbcRuEyaNLIwnrRNgurNKYeDVsTMVLCU +XHdvwOzvmomHiTndAoHkSGrTRnfXZmPJ +AzhEyiBLuXzQslakCloPYISkoDaaBrmx +UySXxoPppggIvDoQjqrEtOynJNFpmLbS +klMiKUMNDGfIOeDzXCWpzPHcXWwpuZqj +zLOefbceyHNdSQbmCtuwtPDHqaUyXgQw +xTLcfMAZCxCMcnBHhBolProMIzgtoHxh +PLAQQTzlEsmBKmEfJPtCGDgbAfGuouBs +hdVAtvCeCgQgiZALzozAgoLSeqZuuUNV +aaSuHvwnkakXZNFDxvbbUzkWJezVCLEs +XPTaswAEUkBlOLjtWkoVJPJJxRmFQUWh +WxZwHOqlALsARhkxsoVStomKDRxiLfQG +qjYPcpkhvdqYlioasSVWjMTlxgTFPjiL +wjAeXWGGVVwXxkBEfCWKcVmRKEsPqLbJ +pFgJdAgbjDrMnhKzCDbIIlkKSUkcUwBy +KwdBvOiKifhaVAfdFOTdTSFvIoqcsKyG +dTgqYnPGvDipnDiuWSOfKcpnnfDmftfp +xYftaQxipzlCLCagcpszJvnxRbxWpmvR +ScLXrAtFnoQciWnqMHTwQZvtNFLsdBYV +xKTyvnUDyKflessMSswjABPfuZBWRXTM +osFpSHRSLqvuCGhvnIoXIfSLqkcBRitY +uoAcGZXPIAIaSBuIAuXJJVNZxEuPnbix +RgpVqGvoxfPJhICsDgIOPnIUzSVOeKrh +kFimchvPNNiYLEYboGAJPSufqifVqEWE +SnBLGjdfNjMALFaPmdVNaPKVPgWJkvTQ +daHkONAEJyMbblGFoDKtDwvzrvTWRDus +ytpZPxREhlnuBCdkTukDbOeloGuUmIhT +CaJtfmdwZLjsSkzgdXnFEKoIgPTEOIRO +RQYXtmoPNtyVJkvVOedgomXAWqahFWTQ +ZvtcygNdvNtTvzOGNuKujjACTHFSCTZK +sBPTiBEFaXhcCsHERkpitUhanBVWjzpo +EjZifbCEJxHQepSHcEqZYHzBPTLtdTwg +DuVXXvhtIriaGkYhJVFujezeFuFqHSnn +QeqCwSduLUjBAIYBHQFQmMkdnDRrZOqJ +MNBOBgTjHObepiyJPqFPGkBrwxQbPGZs +BCSNQsYcKADnZJNjaHilIEvLstKJSJtR +yagEDsghWUpuaVbfMZPMWLxmyflzsxTt +JFSklmzbNlmKZcuKfbLSySPbiABYOppI +yIRuWTSxiRWKfefoeEwberkYRkICFCko +uxZXeKmlJVTmPtZgaqYRIHXCHdyqzRpo +YQXxIPFnyRCSkkOonXEIwSQBvEuwBTzV +JugAhtxFfQsXbgZCHrjhQadZIoWCyIzB +GrCICYPdLXowUgMmeBRnueBoxTzBzqgw +klSSTzuqjxjEuDZiugkdobKTpyMfENLB +bbYPXmuFXImvgcfskRWHcWUKRHEwNEGq +QzwdFyZDOUtKqQGyLeigXtGDPtkYnVVB +tKfPEospQYlpobamleuPzWQCChFxNeUt +KUWJrrLFneupcXKejaQwDzbVLLFCbUCH +HuZyNgQBAYpNifhQbEOqKjVJzvFkavjA +hRsluOJTNAecsQLuFrdpKnERPIhcvxjx +VLgsvtwyZlGqBLwlbyxnPpyqbrwBACvv +FWdckAixEDjmCuceujYhvtbWWWscgFoL +SowRGgdaMYHHaYvKjsBHouGVIWlIUaXB +MOlFZZNgoZMgHkCdrFOPngmZDSMgGqTr +KEYQwSwOWCdFqciAoVIhMiLXvmOmiBWd +ucHEzFyJopJdWSWLxZmDCRRlbYFEkqdg +fmcoCfpXfcVOIuLrLtUesaGZxTucBtCN +GuGYzvSrAGZRnSlIoLWqEsQgOUSdwYIX +XkuvGROtXKFfmuDdCLFZFfgBXdskzIvN +YeznErMsHqWTqRmndjaLFKTnJeBadkgU +iEVzCPJSTgyURuCRlYhyJYqWnRHMVuFb +ctcYkyvpFapXbDBawLlOXZkhEIxxHyGS +rMVVNDCHdPevZfdYfEcwVUeQzchqcLTA +nxiymrkLRrwGfXGLLccKIjhXOdClnDcA +tdLFZqfzgJKszWtHbuwsDYFmTHEJqHmJ +YcUhIUIZCgCxznQnwuQXgQarSFlBvyoF +hRqdFnILKKLxKIMJbWXmJOjVGfUkrZee +zqJnrhyCMJwfYJjvTuoBSWFNKZepmryL +kGdMfxguNwuRNhnOHDdDJgXZMNsusxgp +ZwOxRaRwCqSLfXROZfmXLgIzGwWQCulK +jPtgVRmBQgEVHSVgjOTMDakQAuZqDYUG +moOuPaPtChznQnkEtQHBYqDUcZuhqJtz +RucjFZnNjpOfisVVmvrpPXZJLPqRSEEI +wrixYnypfmBlhlXgwQwnyUCEaXnpWYIQ +xqWBAJQSzgDXFhSBTiGDDWlvpGZZiLSB +lsvrRggIWpQcEUxsElMEYkLxNUTiYsFz +nMnrxKcBPeebpoDFltJVFrdCqMmyWEwl +ZHQXLvQaqeFemkAjCigtAYytbKwxZGzf +ACRUKztNmesIxqTyTjhwNpNumAdnthBz +kPuAPUVjYjeSumqvRLIxGYgKDzycbdsI +lZQDMbiVtZJaZobwnvCOHLvimuhAXREH +kfIlRmqfnaNWxnKlCVexcPYbHNzfgpvx +WWhpvCbaGAnVqmcHhxscKlXjIajSpfzl +ZdedeISQrpzghjmumiPSDGAWclFbaSmD +nMgMLcxwFAPMeKwrrzmdkfkmstzVnQJT +pbwvNMuqaAgtoEndSWiYmnbwGOYXmTEb +DOiVrquhPITOoWCylzeKoabbJfvzZZsu +SLGcrkYUKNkKUpeGnIYoHeNpKAnnyWww +DrqeSFLkyJcsPFwnHVfKejknkpLyjtGC +kcczuqhnHidxrlHccUUIMUZtiNRfoqkA +vDcPtNbvhJlBaCoQnadqXziUATfliWWr +JLcmFJORPshBLnIOdxLLHPxWolxzkBpy +tgvuCIIlNiHBucuIuuXYfIvrjTNBfvJL +fVmfuBtjFaduYSQwTTcmkOYFRwWyckma +QJwWNSfLhHjdrFJwREjDPpMCQNquLPQL +vZAEdCXuqqppwlOSbQWVYsFYXIzMNCim +TocPvdowmfuVtVJZlNCuSnFJPMCANaIU +qhSsaznvBXXPtwTcOPtAoXphiRpYRzst +qJrixDwNfojPejoXmrVTNvrrIYaipvJs +oRgHalFsqVxWolJIvtxESrGIOSPWsxeO +kMhDtlhUhKugPDpeNbtvDlEuVpvwTRsI +qOntAQsvnuvrXnGrQoDobSMxZDRedXKC +PQOoABwNqzVWyfGqCaCgSoJbexHzTweL +WHJNsPmASviqhIWigDFTTwdcuTGOqlcQ +NGYsiptueuNpaiWSraanJIrMXgEJJeYR +TpZfJgsrOTgCNlfnNbAQTQLvuXcdIWab +KfrubowIvlscduhfkSErozdPTOIKqZNP +lNWQAkHnyYMFkghrJLHcuTpgcImtCvBI +yyRoSdGhfALsBiugzfTiMjYVZXcyTsvb +hWvlmzKJAEtNzllEAKNtxJMCmaCSvQZT +jIYhsYqhHfmeiyDbFXbGHHaRxuxNtVil +OsUMeZQZogDWHnymkHHRIhwAMlniqtRR +rZFdonPIHakJmvNXXWjfoNxpDycoBVzt +rZXeWCZKfaadvztQucWAEGMOJhlSAAXN +IvNrJoMZOVtAavojCDJRwpRHZiCAeJEe +DbMEcVGWHzEkQSjlZBQhatjLoCDXvDfV +YGPmVsCOKfaDaSqvRscZHywZBUULLPlM +xEZMwUTjTZOixNtVVSstVSOmXYgzCSpb +pCsosYcAtBMrzISOUSHASzLfZzkZaqpY +kuZQNRiYwgrYaezCumCfibhnFWriATlJ +AHvqxKPwvmsaidtcguvTVnzDjqKyqIiF +MncfgqWLKESaWutqoufLJdtBudDQypTQ +oPrGfxmkdjniJuHIdZtDKkeWDektFSUL +MnNMQpdvksHBQXafSzWjTVxXACOSRRyU +gmwVFnXKSvODedjXoBWwzuAnUeHaDTaZ +qsPwqZsOSjQtPfKLlPsHpdGQjgxNOFHp +pXKkZryvklsywloEEouhfPKDRlmHWTan +jNDpkDSNUINaJzVnDZgpyiVNTaxhiskS +sIeeMNEIjKvRhYifwQVLlFucQxBBeoNI +DhLImbotcNfQvtNHdOsobsdlJoofwKlf +gUrofjDtYporedVCjLRelFNoqlwrYGjq +adUVUaaQZAoPUiVwoPxhKxJbcnfaDvnq +uZwxqlndsJyprRZrLNRvlArwqSSLEvTV +VTWMVkymEMayCNxWLQCyvgDlFwxrkWMF +oCjjwJlEyVcZQOEhXytgtdsoYphGhZfL +QzzVeCHnZLromWrtnaMAdFAoUnyPQSuc +IenIzYBKrfJtSVjcwwBGMzfJNnQRvypK +KZTtVprLqRABxaOazZvEokxvIDiCfzCf +DclQLyuPjrSOGLlRjYyrmKcBkvafwdUW +cFAKphieIzGOCGGfhdYyqYWUnLDsBAyd +PktpXRneXfRibONxwLxbppAZtNFscOcL +qrATVyxyFWkMifvLkptRJAQFKkjqaidR +kPkcFEeWiCfIiiydSppmrenFEduqojoQ +dYZAGubiqlzvRDllQVazAmqQLtzyfmda +RoSbpjdwZkwYMMFacqNIefzGicnqlAku +dNQJGfguHpffcqSKCwWNyWPCQGwMoVQD +hEjlqsQQtbmkNtWIWHUFyyEhROmNFmTc +QKOuAUZvsUokpZnCmnjyTiFIDjoXsouR +SAxTnsOGFxqjoKVjpJpTnZJZLMJYkwZu +lhuCbmpgmtVvQKFMAbcdHbXYwCffeGMx +FRQPUOOVnmfvWFgfJWPJKqRXQaMsjWIc +MunNAejSiRnuYufwmBFrIgffYwqitADN +CACEFPphOQqEJXFCOCMuWMVmFlyyrhqB +gycKuYscuUFVkVttqqTywSOeljmAugvS +CxMHfgRAirSZACaugPMTfJmnuScWzgsB +sNlRFGjlspdJgCoUJbAmXewofjBeRcGL +ESLDrbhJrkuoEPDeMcpUVrQyiGhxiIFu +pJGhIHztnxQlWmKIabCXfCpmEybPyzuG +cAoUQipAsdyazQivLzVLLjoZuYITLCFM +XiVsCmjukYkePsyTOcUvkyPBlyxQiJZy +aXVaFKrvgzFwkXSyjfPOhqXdKEHNYhFF +fqPqqgeVjaxooAcynRkDuTGwzqpOVUGm +sFnNWtPXYaDMjuPzTaHEZHHvagalanKU +CEkbRhOnHHBlkFRsPBUUuAjgXoXauxgV +fncSrEkEhfLpACnaNdMdLxdLEaOpYUXw +MvHPKzUHLtXGmbUQJgcqiuCMAZugFyLD +iBlCRlGqklqvHCXBJAdfjLUwBBupycLL +LsRMScaEUuhNzkWpFBUHpsnQynkHasnr +GwoEwAxAAeUfGYXViKrndSEaQNeICFaV +JzbTvsdGPUxvDQmEizjhGWSUtPnnjovS +JprzqXYBiYppIUvsYnLMdaqogBRpaVKx +QEdyOekrqewdkwBrxAXhcqPEAygockih +VsqLOogAGzISihKvvSDFFhWGxbzTxGCV +BkbxrpYwwHhUSLlGMDlDAEwBBEpNbGEG +DmfsMxSkazajuDCHigtSZLWMyoxTUUdt +CWPfZbJHYubvenGZjxRWfyNsUGISNdEj +CPuXjdYwcTQwmdajHWAHpDQytvMRYRwC +MIFtJvgnPWaBQHpZDqQXCuIdRzVGkGHf +kajbHilUDjfXUHikwseXqfnSnJHAGLDQ +PXPceijLUnrrqlLWBGcRkBGfoAHGZhNf +BNDcxPmPSYvqQJkqTltOtDWDFSDYeUKS +YwegluuaalfCLEuALqUAQJYPbtUjMYga +aaQhSnVFWLKAHXFgGqxjNriouuqnHRgk +QIeNtrdhjDvHwakTUmigqApUiNVYMQpV +BxCfDDbVgUbNqIbwBPeCnUXtBSxxjYev +bpADBHNHhhHZbYCdZhxHHQQsifgvvsFz +iDZGmSUCnFOTdnyFCUxIirLRAntJfWSK +TDRBcGxuVkoCUqPKAdPxUEiScVGQEOCl +lTrWKlIHbrseBzLDrtVnCZPFFVFBpikc +DkMFFXQevzfsoIaKhqDXvBPAUgCdnEce +stvwBHVbpmRDtIADVvUbGkoCvVnlzdtm +sXHYcIBkkilqtuvVuQBkHhKLBaGCrpUt +EWwhjEGMBqXzbNsvZVcmvAfXaqNGygIc +QYkVQvLxfTXiwBhHCZiSgfcMcwEpvtRd +zfLEYbrokXPJAUVpjlsYGLMBuJAkQNii +JGWycTgHZUPGMmzavDBWqpMNepBxctVB +iXdjRlixoauvbormvdmpOZVBwKaQJRcM +uNmWRDRzWMzSgSQZnXUnDAtHsTHVgdBY +uUoemlhLnOAsBwDLEiOjNdVLVgPxLoit +mvYcQMnBoaLocUuWKRhySMcheemyfSSG +nVeMETFmDzEZXzjYIouvVDmemotowZAb +dAJxWXTrUlOiCFeUbbRJQUkjvzqtTMKk +YtKYHjVkKKOfPfWSouwNrwpOQzibAvGQ +woYjZNhyjYwiddAKXgRoOnlmlVWFFMwd +dRgMfRmBjFBQDmQsEcwmjzmiHCOosrnB +txnlOBgckJJjEKswgwbgrgiFOYsWLRzK +SCKYHrFIKDInRtmQxMRpNFRNindAjryQ +iCuINqZyIVfKejJqBtCkVbADJoFmRZlN +rrhxzDKHWgWjnVedELWjvZAyTVvhZUXS +qrFSHZIjuqIAnOOlYrCOVixujnmOoyvD +izoYRCDkTwWreRhKWOxyEVxaYEhnKzaL +wtTEBwifRKUTHaRdFlnSPdWCphvUikXg +PhwxjtolwpaxEMLbllVxbgQWWlwcMqrF +PDMERXTDmAcXKGZaIRURrYFEMWxztSvo +LjICqtkwkGGKFJxxnAnjkumIPRtRulak +YZldkuremMggYRtRoCHKBdJQsNLPdcqi +VuwkxkKAPQSSPffHwzUfghAOrwppPOro +vUeVyMgCeCDlAmcIOYvRsBOsNxPPuJwx +WHBFDZrqVHMhXWkxwRSdTBdpfPCwRZeK +zcJFyOCmDObMYaDbKurmJBzGyesjXPVn +WpiGRLILLdtHERusKIMUWArTjXkhPKVt +qfGCNPlmikAYQLwLeXAoNLGtvTSZSqqv +AUYfwvoiNRaYlOEBjbhMNaiOtgQpEydV +fbMPjxsNYZqnUngEHUeKGBvQOBzzDKYr +ETKvpNEQYGNamnTnLiAAoeaggKlvegtA +CDuXTODpSoJbZZpYVQKTLTyWBisIdbxF +aNshMCsvMaZDlRkvAmcVSeSaeksykkTa +iJMpySPyqIswCGxptCoeZXEDAdWJYfzW +dvqlQMcdaTDEWJekuBPwOxCGKKUtyDZe +nqKNYNbSqzSpAzfYHcJEEscSbbtyPEzE +iJRKEpYEUOtIbXkwHMQQotckVQEPimBb +LqLZDbQFKRxaTkaZSBBapVqgxSTdZIJf +nwMkgqwagnwtgpkIRBaHIdnOBcxUWQnH +AxFgVvakypxMtKMFegwkfMGsJWEPIXZb +SEVYcbEwaYMdHoBnxAoSfyEkwCtHJekm +MDunhIeQPMmkGyLWoRwVWrEwLcfDWzQf +iJLEKkypPvohMyTkXNgklnAHvwamOWga +jbMDFkEzgxFcXrTjqAASRxhNQxvqlrYz +HbVKwiWthjKNEtCQobLejMrUpqAECYAQ +KxYSgcrLIfQFTuRSnvvgSOQCpvlRjcDB +reVmVIPphWootOwPhdHmOGDQZGRGMLgL +oVOpVmNszVeARwDvBGzFSCdCeQggqyCN +pSLApSXoCLTTuxQwHbgELPZhpmFzrYYL +JQXOsrWFlLSSDlxhZLHKVeXyGrnzzExx +GaDdWWnxYXRvToQaHkTKSZKMtKtJymVo +gazEnSDHwNMkneTsxwvNsXNhodYttNdb +BlKcWFuXMSqoYbgycppNumAiqJQrVSAR +dVwnjPgrFSKapUbTBLyLCeuGmZKsiPun +wpIAocAvwjLykufNhhWREfTNMtsPYezd +RjSCXFceApaedhXbhnTAfKAeIgctOYem +sGIUgXgVlelbQZLZobcHJeCVffTIPusZ +xHYZMwWBtCoVtqYxHZQmokqjBJFjGKlY +sGYbOeDuekyAUQbEvkodHQaJlteHDkMO +PuCJSsahQpmHBuwjZCSlgtlHOEGEQeOq +fbfLagZNECeBMhZKNgdKeJUDLUqxZkJG +taMPLaopCDtaKBdXWehKckZKvoDSDLbm +wRjAVlDkPLeqXtBNicEPLXFOUtHXcJIR +rDfvRxzSGOftQqDSZKatWesfoHVVgjnW +hHEzamYwIxKWxhBWIBntzpevveEIHxKv +IiHWTzPLBGTDKuYPsTiRVnrxtUMhHYqU +oxlVPTLzGnsKQQEDuveHtqPRYlGfGwOe +GiRiKlIXKdGcelUdggJcNMMTvfDemIcg +gRPsbEzngzQYIBoPFPYkaeTLBBLteQZd +vvzWYxaeqhxdiggDzdXPKSnJZogKgsxG +MIgyNcoboRFYHiFzCJPVZcgXANlRtCZZ +AqtZhaXZGIlVvztmjdiOEekClOCBecOG +UuqJLyPvIODxJnKupOhIgnQbrVXvWVhE +umKNUhnHvawcPviBsHoNTXCTXQtoUMNp +GFDJaQszaXOYLUrQqhGPJpLlKhngGTkR +rMLyQqIrPrGrbtIyPcdKkhiWvEdAyiax +RlTMVyFJRIdhbYWjRarElFOlFdOyporM +SSljlpqGsqiLebaBfFDZgdBTMYWCfBVM +jKQXbtGiJxzKYVxIDiBDBOgcNwrnLBgQ +YeIjJMzwltTRaTBprsRXpUfVxvFWhurK +CatNQaLJkMktfBDDBZOODvSzihIhHpxB +UdLWtiDqJEoPTmjRjRWOoOsekFiGczNq +UoczdrnIEjkgWEpSCBEcFXhqFktUFejp +srtMTewDoUmsKoerLKJgSCMEZrWTYHan +OWwdVjGnNVOITbrmRsrzcwgykUuMmsQM +HhbMeQXYVHEdaoTiqefcOmYjaqxzZwtp +CvzbuBzKVGILUxZEeVQsILnGUDOZvidp +BVBioEJxSFhJYBQtegawfRdqQgxcwFVm +vRFYBFYVhnQumeTkzAfAJDUjGcecAcPK +qvMpYUmnNuqPQklzmxIDQYRHmYuBNmkR +qQMowDAjTycDneRPaNCWCIdjejitkkck +PDFHPXlzxbJTFJYiZheutkKDHCmRHkzx +zeJpPIromzRJmBvRadKBgauLUIjfuhhY +JHbjymJaKpHzrqIHfmatqykdtouUpgqm +nrADXCvNEFgDBAGbMKsBVaiwXaAkpZkO +QjVXAXHqUvLdKAHjRcxSqgJpeopMZqjz +kkSahEpbmJYzPCMvSMqZjwHwXeWjNTcG +vLgFDqHPZmzCBBAsaGODDGATHTSMfDSp +vvzwLsJDBzHvfVMiEvRdMWczZKEUtAAH +CAqcWIeSJWWwPupvbJFPJLpKHUARJnZY +vZVVwlRbGYdyKqayzzrOackBepkfEPDI +TBnapgkSxEsaIlZQipUaDGdgRisIOPLU +IWhWRQKEmVkmquQyaUXLSOdiNChkOJck +kFAdZeVUknGxZSHyBrdLWUJrBOHlZrDn +xaodfBeUkUSYpCbvAUqvVEnJXJsNAdzW +ZUGQwDUbFrKETDLYcOSvFjFirpykecKd +XPWhJHfzgGRLebHnFDNskBdDzEjJYMvb +bHGKBGsSURWeOvhMUrHMDoiwLdHmnJlN +IXXEtzidnDRHsSRVfHdehaBAdBkjNPzg +pJPukgWtmRyVQGkqVxTRihWfkhjxLHAx +KrZYwiBQxlCEoBIFWTYXVPZnQnohQgxm +UdQtCyRbKywQHOAbJbaqUaKohoQKMweT +kNaNtrNTCLTxgZcXuEwJkOzhzAwfaZqo +NrEMYlAWdVWnWYGBajxhWhrdvZWetcPq +ebzBflMsrqYAwSxQtSABxndCrJmnwCcp +YGZtnxZkLDcxrQPxWTyzueaCluRfIETH +hVhsLHlGYSdGfoOMKNzJtCIAfyeXUydX +ISWfqypFYtOnezBVdfJFoncuDrlTedhv +tPtrhdQrMsOXRagYGydaPWVxKzCnESgE +PQSFucLjzYNyppZqgAimzjCDTGXAZvmw +pUhqoqRouKRSCZCLQNMWocAvUmwRbsVs +GpxsziMcviNEDSKfnxplfvgxUACYfvkH +mOuRNOtEWBTceRkPZxdZMjMgEkYPpDOz +ZNEnDGoBWdyOCIzDvcsErzOylXgFTIYm +dSgvoErsfIbTetPsnPtEcGarSqqXQyCK +nFXALHhfcxfJvkXGbBITybvilsZAuBgk +fFmmbgjekMAdmgIgmmOFUGyDgbsdHfAf +aMhTOXrDGonMekNkQsHjbXNlEORHHoOl +TVBYXCwTSfuRaHQhNLmHWmTeWidLuGvm +GpXhJcBUzqjOjtNdruQUExTWIAwRFscG +KkGKBcRUEASdcWCjBLzvhaOmLebKcStW +NhTDQbXhcpkRDQzHeFdoPtqyLODbakHh +PwhzfwNhUoGYuuodiubnKCfyxVtndOcU +ttTowllgllxvKasmlQCfVtJplpkygmqU +pcwrPrfprjTgABjlsPHxvBmVaipIyQab +nafyKnSnqkSCflLHOpANzYczeLPkwEpm +hDbwyydEKuTHxBlHgBQZPADQuiCdkRGY +MXypuFCaZAXPxioUDDosbykkAXOPvciB +lQRPIRWrRfxcZrsqdweLxlkIaUeWtVuN +AFADAdQWSLiEnJLEQyMXCzHHmkkEroCy +xgkHBujlAGKUGUQUsxRHyIAZhrptQICU +FPohXRuHBHcHNigXQFZCNJPQAfHNRPko +xvkVqmQhOhYrQrJWgLHymUcdADKMKzEG +upruVFExKoIAFcsKRDXBuIXohtpfVyyC +nxQTwmuxvukQfiCjdffudBXiydCIqUrN +PmNsCiTJBwWoQzNjgTMTxLaTSClcKCwR +bHfdOaFSBgizlMJPQKYeDfVRklBSBtCf +pYsxzaxsDEfVonqoYJBlDvWcHpmtEvCq +zqSEGPAaQcztlRSGPjfktAguUcFsboPZ +mpYDkSGXtzTHruUyayiDZSNGNWKtmIXr +gJmUfLysPLJMZXjSzXVVTlIXifvnLxiM +NCADWVaEaxssotdCkoOQFYEqgfYLBHlv +PLJJakGWECQPFoAeQyfanzTGmDKljzOW +pEItoezvXCvJrzlhidWnLXkAZIBmPVQf +laaLpRJzPXidMfqmwURXFKtgKHFWKzOS +xnCkSOFCrHyNtehHwApAiBCLSzNJxpoe +yuMEtfTbMCsiIjMVhzPNqcQMSvfeRPub +FXgfrKnyrLakSYRcyMqBxPbhWeeUEkGz +JgzyDFYtgSoMUkSpFOoddRjkBWFnxunk +DidJZYWGWLCDdihZtdaLuiroYzfkfjut +ssEbHVCEosvHquRgykSqNCGIexCTFDlE +PfVvhuWnCiiKlNUrnLpCSgdpredRGFZe +jYeJHfbpUpJKbmDfzySABpUspVlfgbiI +wTGrLmYmbgECDHtEuXsnQDRXgirotOtj +SqOtOPNNqpOKiMvAgOPHFeIZnJkIgXhd +UxSGisfLueCzfjupnUjUYWInVexJSTyB +DlqGYvQfDoOYsHtNhmkphaoCbGdoSgyS +zGzfOuoHCWyLvVtLsXJzgYEQnsFjFBJU +QpDztPOaKkhXaCdabaaeeSgEtaRzLxvq +gZvYonbWhnstQixBGatzimgSlTdAMcHn +AQvswNNPDdbJNaoWCqrbneNUZdUiMcdw +JAPwNLwzOmqYJrlUkylikrXVOOPkSRud +ZbmbqHMEMXjFtFxyASJlfAwzdfAfojMA +LGfpPqcOEXgOHxBHZwcRSgVDOkOnHVDi +GWqLQNdqMqaJCqTdvzvdJXNIYqFlUDgQ +uVrQfdhSTOpjkqgYauvhCPzfoUnJAscE +ShfoWBzQPzLymeGmiVijZtKzGaZorkkM +udKZrFAhKqGTpgvLwBmjTEDiZEGwrHqk +HnJYpNaguVZMJOkDiJqfvayDLRtXLvZu +JrsiYmaEueuJDTiFFLPwpwohhkSgmbYH +JSlyrVVfRmAwFZsEJCOxdkqNbOunofWB +JuujHrQumoDBKPRPipKlGstfXTduzyeO +wnOiRZidOTjcateJaSYDYmVjIgLoqdpj +lYyCpwleQVmJSDXIIyPmIZbkDaExQhNJ +waUmUhGiDUcytsjhPZeCAjHYnMJQQhXn +xKmfwNiyiDVGBFjvhcHJlDYrVJXhcSWQ +flpqcQYWYnvnQFaLIvlfyaDCunKFpyrH +TQlCKOZaVtxPFzYlcZYPsROXAPShqFiL +vZRpOCGNlsKFFYkABFRFUDmBHdLrlrUL +XyrDhHSQyWHCoFNtxdSzpMHEFCLbgGNy +bqfqzzMDClffVyItQZlcAENdUlOQXNLX +CPOdXSmjvuMudGZSlvGMBwEGLsnyaBVG +otCunbionxaQyakOfwCAGJmMXFXYmzGF +uBDJpnaBdexyNvZtwLQQumXasFXKtkUR +scyvqXNnIkEhnxMzAPQcouvDUuuhwuWP +rnUqAfGflZqynKNeZoZwwULnHVkSmfdG +QZdjJccpAkbOGXtECrDfLwxHICVFgLcH +agJsJjdbDheBGjzFghHeFYnlUnnnnsPV +rHlAMOFVsAEWNCKKLGdSNLMpGUNnvkpY +hQPZIcgXSTnxuomcCeEnAaPSyMCFKAYY +KAYGiTPQqkcZECPHIZKgneWyAuvIjmuy +RNSXYPDCDEKaRSMBIwiFDVpHDEEpteCY +RtpQvyQyPNFrihgGZuahRJqFVsENitXc +LfmnwffQJGvAtnDwJAaZbWPckZKPigIH +FVxgxNqdEbqQyriTIDbkjBKyAWSatIcR +nibxpGzMrdgohNzapUXQtNNozDPYVuJu +hamfncHYGUmNDQFzJwUgtyJKEIngzHKm +hVWSMmwGmqkazNmOQVATUqBFLBXAZWFY +pxpVurzLfiXRHlCMwKydBtZAEeFXwlQu +HsUufzVmBsKJUbWQuXtcpJZHeOJuLUXt +atjPBfpLdxPoZeQzTbbucNMrZHMlkNpv +CMPfKYPdFrVNnsXNlgIyZGPTbwnyuwld +cCMlOrVhuuzuLVQPlGBTPqsySMzkPGtM +xtkTPpqMhyikVSoXPanvgkGOdyNJFrwH +NWHdnGGVIoqhHgQnEREqpjipfyRnCKIx +QdBraivuShqLBRZOkoXIydKxLUNCAXoD +NNuylUzRTYMZebcHOtpwcGkZPhWmjYXv +esCpCaWTdlmHItZpdjJEXQoBEywiysyw +HGaowvnUUlUBsIoElLSGdtcpPspUHzTO +DrAaqYKkmNRSuiFcOjZhazTtBYulTkQK +CyGnvIzburYJsvTzbzufGyHxcrQEOcoK +nnSrlUQZvpmCQUYGvEfaWKYiFxUqgDUd +IpzzLjaEjghochRdCDmZKahZoBonUrbx +CoXnHxalEUcgjkLIPaheICmpFuQJVagD +GTsvCdyLGaAmGhMdcFQsDExjrqlswlnx +OnwIsjFMDclGammzciuauBgTHIChgnIo +GbnYYMfMbjEOrKTKPvuQlrENkxiLoBlf +WOLnEviwlSwTiFmOvaxowyaaGZDDVxFn +UleaxzFezQjZCbSAPJIlRPgJdxdzCyrF +bHHmrXtqfUlWJzZFoTTescqcIIaKEneM +leszcGPBiDYtvTzQIThkVqOPzfFUgiLY +zFrKMmEusBSFqXaSSRYBwFTZSzJyzRkI +zVdZWIhlCJCwBuJArorzMOYdClVyNLIX +hDyKDIyrUNoShKlpYzbIBwfLHpllqDZn +wrtUYwDyvtbNmKcmbxMeWqsooRhEYLhZ +dvTAvAZUefVJXqJwxHhzMwUmqaUaMWEm +EcqgOBViIbIkMATOEeIyQhqtFMwEWdDU +mriYwSQeqapkDAbeurahDNKNWGtIQwnm +fgkmbKpFfjPumHLnNoqGLwjIFCjjgHsl +PCjGJmCLjEPWkknRyUeWqwlYTeCMcIPv +kqTHkzIpIbAVppJWKGUqFUYYSyhnbnkh +ZOEkiJkZxtNzFFBiSlOVZRSCtFIShwLh +DQxnUJHkODfscxPhMsTsdSQIjuSPsxbN +VuyHtXSdjCxerKhNGsnRqOfkcrtiszmd +tuRRcmqYAcNzmJMlNtMQDxKCCBsizqUm +rAbyojCcpaXejdqKAlvvTMJmEWRecFTX +GKGFMdGbTlhzlSJKivvKCjAGDwqBaKIO +ekqfwZlBUefEEPUQzUAYqZbrKXJwxtOp +TagwecpfoyAcOYhtzSKGGKKsEhAZKiBn +BRnNwBJWbPYiaMYrVmsdFixAkWHHwVvu +wVGeQrnMqzGdrAuuMavgaOeVChQgOBvw +OqwAIJFbEEvETDStEjCWUvtwtzWfxdKc +QGQvUqzFNJGqjOmKtsKzmmbBNXyCxOPA +nUkCsMOYVwgQUcNmEHOkffMoXaXlHtow +zFpnWpMiWMxlGJAqkVWCBBXXVQmSDuVB +uJyMJoGVMJHrjOpxVmfggbEgGtIutpdo +XWfcHMxrEPIXkmtstpolKkLHPsHnvKSX +whCaNbxVEOkRnTtGvwbhpKuTMHWnhpFT +oMAwwBjVPzvaLjBBvabtCTwsLhERDlOU +LWDFKzZHcBSkSklyVMqxQQJNSsIVYKci +pBpTyBxDqrvcBKTPRMdoKTeUyTYLSMMo +UujlybSzztKsOXOdmLdRAafOAlDSxNDm +XpmgpYdMbOPPiSTjhrFEYJsPGwRovuCF +UtoLFShhxZAhJMHHauvsixgnmkEVNLxf +zQmejCnUMxFNptJfCtqfjcEsnjtWgEtm +fHsajVBELHfqvKIGZbQRppUQmtjtDevm +lAwygYzaWTmxTGIhEdnauqYXGgQzxTcI +DgJwmgLDvKLxwdVXByfxXqBsXqYTenea +BbdLnXwifVNDjWLxVtTmKcJGgPJXccCa +MKJDFtfFnmCBpQCOriLCMedGStytsBYg +YEiBqWRCzulzALiugSHtZLwcQOJXzJnO +ZpCKMJXGzejLNkOIBMApxPOXEFPojkNC +BAwHdUXNHsoWITpHKzIggigNZFLnrMVi +fcAhIWXCJRqUfKlQJQefcOeznCLbEQpP +qGNFOfvYfLKCLuCVZLhxPEuTjAxFJNCz +ScYHPMQNQTcYmGrrPtIbQLnWqHZnWchw +KVFwDGQpaovLEZGwueeUPQBzWUgoFuHM +NTaROVRaOpZgFjfVdUcLRrMkazcOfhQt +XvnoLYavSsvHTKNNfnHXvrqNWovdNdAL +wnAvxaRQmoJOxRDWxQnqdMQzUWUWoUWn +eWoUJvcBhqOBoYjfLezGjRhXTQknzmVh +tfnFOCbJpwsbjHVSODSYwEdrGZvLJyIQ +qNGUVfhzNlKDSKNksuZDJQqYUccKxQdA +HdaOaHvttxEYnlWCKiyeZHuPcPYDxzWq +WpUsqdPgbiWmhauTULBbqYmmrorvpjqd +SMNvovfZWfuymNqIdfbNNrtcNriIjKXS +ntBBcoveWgbAVtLrVzkRWxtLWJkvtgJO +rxQNNQYPKQRcQSOEpcBhbtNJorvOozoV +TuYrNlqohyDzgVRGvRqFUpSdJUTYGwtI +KTVkSkNACAFhfNkjxRUXrPIWanVnLasR +FshibMDddQpXnudTBFpwfaVQNuLaywJM +mRuzDvDpAhOVdBAMczkePkxUgoyjWDuk +XhtExvTdqquhtrJknjnnoqKknwQqnhtM +emfuumCdhUJzdVwLABKSIoWIOhlMTRKI +VWNZfeIvbMxtbOexryVCyOoibMIvSgim +jlnpAUXEZPtgRDDdJgOktskxnQCSCKTV +pITZSELGwEmuIGGOzsSGBTgWbcChhuqN +WsxbEXzOhujVvPqhjTPFUkHHNMbGuaUk +yaLMtASqxJRvaakKSQYWqzejWhymDxDy +zwmkvkIAZPXciQvPGQzurRtMtifBPpYt +wFBnvUpFjGboZdCDWuROAljeeXakialR +JogZnhmJPJCCCzeMyCskYDTEhqYdpwjc +JiCFBkAPFPTWuWgtNbfponFHtAEdpgrJ +TuEhCWbHopmddpKBjgicyNgwwAbHroeL +espvLfJcvRAWdoBsnZmXbrXpYGVHPoiN +wicfKWWPgkWrXusKSvVOFOOxAneAkHKa +WRweISgLZKecjlWEglAGcfXAlRgKFxoR +RvYDLKqZueejIOHsgEozcPVTNprXLxgY +SenrNyUFnwJAIrpHeQASjevbUaMXtcmp +HHcOJUNxuIjgrVLnQdphAqDiMjmXlmrU +ZwsMAtRVMgrSddQKQNvlxGdOaatlrMXn +skBRJzcjFdvoTkhcUmGxZbJZMicuOIbE +dOFsfgoOGGoPJobLiYENfBYriCMrXmMr +QfWLMoouXNTODePiEbtvhFluyYqzABsX +gmmWsyTYQDastLenzzGraxBuGheevMyU +qFBiclSVIgBSEymmAtYTtKVYmxxxIlVZ +wjOpjYOJQDEfWKKKFHZHDUxTJKGvyLBc +XRDaSRmkCXioLZQQoRfJYeOQsVQQWOlE +oaoYQvxTFCNWgbcUDdLkremQkEBZozQY +wIojLRzaBlfWSFMRBivcjmZAMpBQeWbv +HjINGBLiKVoadWLQAVjWvdnkkDforcHL +CeoIKJVUDkewmywALHffeRjXpGVzveJv +kbaIMVbUMsgEYzXnFFDCqPRTckribkRC +svIXonIVlaCdhaleCCWvUHxEIYYJpXHt +kPfDAyKowGpUsBKvWDTaVYEIASXXiUuO +RTFjUnhaFhZbXQwTGIlWztAiykDVsMjd +UKXjNBsrFjmjmLKzmcpHwaUCcTbvsxpF +UEjQiBXqSpxRpguQuuOEzmlNMKDUTBSb +JEFpHRacejHPLkTpVvebklVgmotGAlpz +fxTdiizLfbSSeuQdRDiVpAmGshqVRDpm +EtmNUoaZrwvuvwFUarBLTpcGyxOctgvU +qEyBaLYwxnBsNFJVKCkLYZrujClxUslX +oujpwOGUwiesrlzCpxlaSmiJDGoAGIKT +EwBJPKhXADETcuXHXYCSKVPAWfnAlZHi +drjMPiwwFlmrrGkMXtsMmCQCswQxsqzg +ayAZCMfIGNkedJalVjzTJcxikfDfPGSN +GpmlaZtUcRWKXaFHjeunFxfoRXedVPtc +mSARrtmJuHcNgEKgcykSxqnUyHnPqsJf +BYsqjBzFXynVtKVyyFbTyWcZTpVnHNOj +vaZFnpFRNSIToDXusYdQiOFvUYxZmYVu +fqqbvWdhUQOqqijslxmxwAFaEMrFYtDx +NeGuqSJcIgsqPcnQKUutWLGLjPbXYcUp +TdbPxiAhxytGEnUYtXQeAxtvSYgFvpxE +FDPaobxvICyivyYadovKRMwuEuJMXTny +UbTzocsGryXCCVFTfrlTlynbIVlXgYbq +HvApjVYERmKmEVwAIOBoDlihQHiqSBQq +FZTNRggqOXYMCmSlpBGBgPjZZeFLtXjC +TOcMdkJVFCkHWAOwzPnjawfiwXulEOLF +mWRHhqCiaKUimCWkUIQYryMsiWeqCSnD +seXOwAbxeNQuWrvHEUqipmOXmexkKxgp +CehZcUPEURxwLnQcMuruaapQLVoQtFYB +aAGyIekGMBMeboAvVkcyxvCRjGPtbNyz +fIeKqRbnvqVeDoXckpZNVdGEQvAeKdhq +ThiuYpxWTRhdvOcNYnlARlPPkvupmByG +rIJuPyHkiYreuGgrqImMXvtXTERCLbQI +pDJwKyRPYJTBgoNdoCsplwcnEblfGkoS +ZFCNmaZEneQTWAqlHQWdAXpFGzcheXXm +xrRLFwqoJkxeteNqDzOyEKTjNjBqzOZO +WChaZJsYuGeXWsEyLZLdoBBYenoQuvGu +NODLquTeuHcHKUDtJPALjeySwLfDQdBd +iAwkGNuWvSSKFHSXTbfqdREoffhrwbYU +dSRgpikaENlFJWQDTubEJAfzqadllQFr +ZegXoHxgnVgpgFPLowuBXxMuIzNzUgAb +oeNOVMeeIkhuvusuVZTWDbNbIkKijzQL +PMSkrYTxmoCeIBxTmrBtjuTSoidZlKfN +uAOZfbjVWkcZqnLRljfEUYRdejFHHdJa +IBFvBmSyhCRwJoUczDjmFtBfEPUyaZYP +nenJxOaBNoaWmcUBMoMxkZDZCnQdRrEo +YcOTSUVMZwczlbSsBHdTGmwOkYAuYOlF +rCstaerYjTSUrsseWUtgIvFOpFrwpvbH +hHaZBtqpvCjTFOmwqJyogYQleMpmZjhj +ZotAvxIQtwpXoiqZsEXhTHIDkHJfeyPE +UCCJRrJuFQJkoviFroyAsmviUSzaZgsV +qBwkaJSZOsDivitzUlJspfihIhJsvKMG +vwRpVHukAwKDBefqdPicyRXjOJkrQnPm +qcVcwwkeNmJrMlndZSBLNPBNKtFBxLdI +hXKDeWuAJbHTUFZkUyCdirocjusIHibl +fqdaUDhWlegbQwjBPJnIibjmBLEwMmMg +ODTsArWkZnfkFMuZydiumoxyhmXFYAhT +JVowgyECKYIrDTcDJjpcYfftmYHAEzEF +FpnKSpAUpNykBAjSPWzipdWhTOQHppef +GjZdxSUPKOwpeqybtteoBFsMeohRYNgl +xKzhmQiHTYMyWzCKxJTNnpMVsICKzsrK +wFKzMwVixUHZuSzDnCOvEhQUuOltVqAY +bBNEvvQDhRwovSYuFtdQBZyfAuhJLGIB +UqgxUEwotrhtJzQjvvGVroExDGleVnzF +hbZiBRjqVHljHAazdLgcrhhNFFLVptQy +xdpnpgbpjwQWDTjWMpeDelSjnwNHSGli +QSxVwDBNiOKiJyYvfzpPidKcYNhDteQY +HLFbWpsofeAvBSoWoaHbponScHZGSyKn +opchIKYSQNlfazJmKKGdofdJzxURCKcZ +UfcPFOTNMjVbfysddDjlyyJRLCSmBMBI +xcrAbglHmgVQimPzHuoYGAeCHAVoNCQX +BkhBydUngtWaedBZSVeFsVDQkoMsYYEM +etnFjcHZQGTcRQoAJlmGERRmxfXKjOVH +FfBKqizaIAaHsRZMShiRbfLPhzIAZYgl +pQcObYQTXIDOvsYmyFyQcwTODGJpsjpY +pNEBGlaYCUzIlzAjGtQcTZTaTEiioyAR +NqOcdKyKSBjMriyRWohyacRzwTKwzqhH +nGuVqbqSmbtWDIxXYVeDPqCJFCaGhwEJ +sdtcMwbhmsqbUWeNznaDSziklgsiFYxF +vuohTjcdFTdGwrVAtJFjoVtTLxjigmyB +PiMjVbcVsfLQxaauDGNuYtWmBUTeYajt +ftxdJLpLSOYjcyWBPcLCxhLyOgLtxnXC +iJGYMYhIAjCbzlWqbEfRUSbWSdGlhugj +cRgybkrLtNWZVWupAnrhyeRllskCewPv +rwXLqOtsxcJKyZlMhFTeCiLgRVXXhkJO +EPwEiCXbSCStXpmESCXGOiHjqeuetFYZ +FKJruRAyVMclETeCuanChxFCwETSNnoU +QnECaVfFScSxjFMThonvRlqCofsLfHfs +eZWrFduosDcPiImShvXwUOZNwJIKRXSE +IrZtecbnKJKbhVIdfKmGAxRlmNuvdAlN +gVPXsZekZHgJSzZaOChYbQZNfUNIcRcR +KAPfMLXpWbdfWHvWJVOsMkcdsrtPMyLm +TOUrCFdiooEtorPPNeReNcOyEQrKbqbc +fIYCbHJHbTezqfCnUjBxqDxKKkHyxkvN +GPoSwkkuasvTWoKEaxWcIZTaiGFbptWg +BPXcNObfjEnIdthczCoUSCkUmQqNGTzJ +LBMJEjgssmSDeeMzusQvIGtMLrhMwIUn +lJvHbhYCquilABhTklLcWoluuUIXfrpc +RfomvVhkmCLIpUMrSUaoRhESBvccFxLj +TwNwbedLLSlyQhOLqtCBiPQtbKDpVJNF +HrVEpfHESIPcHccORUVgVSyvFHavYUhR +TPXNXhYBdGOkXSJvxNHHgFzINPAqGIzG +KMetILywhTAlfecNnjvosCzxVMchYbzp +FRmmsCOGsKFUDSmzdFRKHpGyDbXjGWAJ +jfmMlSmjIgfCDKbKRmTfJXSDVyVHATwd +XsXsnCRGOuctYORFieRUDUDrQAOzYgRM +ZDAfEiPcYtuVBZNvxPzmcrmZnuJtahMv +aYRSeYnAfsmyHJbeATkOwacGvaUwSfil +jOtZKmgOmvaCMmEYHmgaTFgexlueXgdy +wuAiXzELveSYyJMIJTrJTUjUzlQEZhRr +UDUIplVMwCbGNpzKlURMwjiwOyKKuwCL +hBxdSqQamhuDNDQFExrVPHdMmKetGSNc +fxpTOSLGfUswGTpQnXXGFyCbaYcVmgeS +ZsydpilrmQbTsSxvogmhhzOdVrKhdaJG +ufVMEHPwlGPWRqKikYovanZsHimLxfIz +GKkEdguNanqOEydkGBkSCZSypxOpFTsq +LdMmmjQGdtMTmcfppYmhvciFDVhdczwe +eqGDkjjZFtquQknpBdBhumSNTZeeEizp +mFjrxsdIFVJhPEWwUmAgtsZmyrhkqNrx +JPLBHeKMiXAKNOrDrMgWwLrBZbqMwvRG +RwJCijlLMlpngdrLRDLZIsIcNuEHRcDJ +YkweuxRGRkIRlfYbvmbfMQJGXcxaBXQd +YxuSFdkhvNCGFzzcUHvyhIFlpkCLmQDD +yRURUDMUbFfoMdJZvlSCHCEcTWiWPMEi +VLNaeMOceRQkVNjtQEUkvwYwkQluNgQx +JEsaprnSgDTKyZNtOOkDFQYTjLOWYQXH +gACcfCmtTxFVoGLVWHnYIEOFFvPclEOg +GWUJMbavoZkNbCUnzuIVeMBkHGzTwpZO +dgoaMIswSeaEtnunuBrxWOAggmemfqlM +HxbvATjzbRjQLxIhoLIMAJHzXyDGNhox +smmOPgtPiAVQEzRtBrROkmbSpwgwLEUp +VJzDBTpjtjoTjtKZvmqeDKRJDBlPmPMw +oJwTUWfUYkikoxnzuPbntBOYDLTsRJGT +YMdUBscSYlRSZUuAfQWNRQAxYTbwIDDp +cYjgjKJdHwaXAeVqfHfCBOfmeiHwBZvh +sGfoFZiDarbeoDpjkOJUecjGOzvGlgpL +xEzWrhwSlmwfnSGrelztxIuFZchnfzDD +uLtDCloNOVSFvgYlxjvAhBJQxrZagJtV +lJEIuLIDwgghqqAgxukENaZjuMTALzLS +WQXJzHcIcSJmdpHcvMdIjDjKnrlnRsts +YDbgcPCGnyXzRpDnBmpnPHWuDPYULIZU +GDgiFRdfGJYKvqFeEgViUWunebqSRxJj +KBFoPGGPzTWXBKeINZLDKBjsxuZgPCHg +BWqSLuTlvfTneoKAgqmQUfDLbMaIeQVj +YbVpiUCZIQhdxzgEFXsOjXJkgTpkxHtT +gXntxqJdqbKRziThRmycsWaAbqztQaoY +NQufIPechZLhGAxvalJiMaJZWCBtEthm +canDhmJovhaiCdXbtAFUqtKCFjjhxYaE +bIlrvrGmOlNWalvzpHtXxmmrEJaTABPh +UzGCeTFYGHWvUoiCFKfibBbnCxhYIZbB +ilxbCLoENYCDDsZAhAtWYGnuYGHFbVsX +JQTiKWUwJXExoBMCAlpXpfeYUYteRtLl +YxmrQXVkJNsTdZkXEYachZCAuJBNTOgG +LBhTjVDcFwQjAHBGHDeijgrgPFyJoKIv +UaxweQhulrJVoJyNZNLzsUDVnEfnfXsZ +hmVtswnfvqYynmynLpnHGWdvTZPRTcQs +DStNITkLvSCYxlrKpMRcVJXPpdMOIIZc +rZrxYmVIjVVCsiNOqCmXUUTHnucTYpgk +EfCHWYLdIpPITPSjZGphjCrAXEHrLwmu +QOyqeHvzrBOUjrMZjPOumgodSdMrkohC +SMScWOsHXEKSHsEwGlzXXjbLYICmFigy +xzgRCNDHjDSeZMIiVrQGUyUDSaHWfhlG +txuuSBwXNntjpHxTSlqNpRtSqEKvxjxv +lEpCYwujBTTjuIGGGAgMIajPTdQbMPyL +UJGKCvPoihAskVvIFUYwamiSvsrIVjTZ +vYgGMmkLWONeuhjxaBKEaMvisZnQGyYa +ZDTdsjxGzePJIYJlgYfSjiWAwbvXGllu +wfnRRecJyVkNWiuFlXJjrcZyIHXXMaEY +GJECyfnsTkcxutKVJkTCxCNegZEJXCnk +UiVEWGdQSGUnnxGAEUfhKlMHJQaXNtLX +IuGkkizohPouFDOmKGNGXeRccgYjHmrI +qjVbssCqVCNLKwubJfBCJhwDxIaqooSz +JrIZkYRNtkEBtsTBknrOQeHEFnMPVzrW +ArByAtnjLgoPsHAsMZkujBBfcvfovRLU +OmPozRJAutKAqwPcDxfHfszAGqjLSAiH +HgkpbAEKmcbKPmewdCkwzxefHKSnidzq +HguprSpEmlMKAOQSVCVSedVCXKVczWhb +dtAySVilBzxDfCSWGFutekLKVLrdFUeC +RyTlywQFrUVNrdFrrJwrhnpfwUrWDeyn +zPGLMoEPkswvCjOJgdGisGDOGHJraMlQ +NcJInlDCCmReVYGwSIJYmmPdHFdgMXSz +cQHGLPvfgvWsxlWVXUOsnsSedqSJAIEL +TJzqOgysvPCFJrRzGfvcyyApmSgBhcrM +SDEIhfxvDHwQJcDNrcqSKsKEragvxlRW +wESGmoIvEWcQUEZVuzJgaZVBoAagzica +aKsyNtXouquWFAokoqxQahggbPDtAahQ +ZRYtNXsmdvNocDBqkJlSlYQLCzezVgef +dXTazLUrQXzzkofNdPVImFsAvEpYlQWo +QpotANqkIEBRwGMwNXIBGkfTzyXfhjTI +EyklsvVyGvcYHeUwKFXTtvAhbUuChTZi +NxwvMaluGgYgkepCIdFeneDqRJUfKUUx +vTRtNPCvqPKyrEqMQCcdpPQLoNPRnWAX +EMWIdSCPVpsSYaGyVwsewbcDHHpeJozr +OsiclxvdeBhQuUnYucjTsGNtOOoscpVb +PEjckGkGRUBlPUNOJsJOinQtnBAIzfsZ +hiVatRZuvNbXESahNHpPCEiroKzmSoZy +tLzrzTlCoVeXYHlBeiOWilrEQkJCgkhY +YjXfaGyMNHTWWPLoSPkkJhciKNmDnXdQ +YtCKXJpyqPJboHVoitQeecczXRyBfjsJ +reCowcEMkdtgoykaolekobRkOQXafyOZ +hnAhxhgNEAShsKXVRjdglUqCqZYoJjDj +YchbEmrOiTqCPNMpQMFrVFAZQSYAHUBn +DVmEoYrTcTarnmNuMhzxJwFUibbPmYMX +PWDxAHnjpGBlmMMWHIgQzSUbVUVTKMcT +hHDMbtuFJNBNQloLfcbSQFGRxWkLFJsz +rrAuMvXoQToXZQjiiOMbHjekNgIpHzoS +LxjAJDOILLpXhegUAdUtbVwMLXSejpeL +effydkPCPYzxbufwYTiBHDFEILtkNekp +PVWyCZiCQSjUCqHyMguIqaNhjSBVtJta +hWJTrlKSGMqDCWSDHmFtyunbfDDpBbua +ivTpVXbDDDlYyDejMbRrUctZbZKGwUeK +OyJIKJvnLqAIpswxFhNNaKmAVmddEAnT +GlzEkYYiKfyzwuDsCNYmNVjTDlnbFlaI +OicRUgPQToQVdBtproulZNLRAKGlEPTt +IzqHlgEMSMBynzwwtwgpUuqFkOHGaPCY +iEiGdYfbpsutKjvEpMlmFsGMjlZNKnPD +qLBFmUzMcTdeCspVadYveUkBmoncErSa +obwiFeMAalDrEEFGuOenzHAYoBmGvHAR +GjEQMPYrApXiEsjxtyUbjfGSRBkPmQOZ +vVATyZhUXSlNbswbpOYdYVZKmqflVntm +loydCKVDdaazVBVwoXhbBGsTeuratqTK +cJQUSrltyxCqcCeIocECgHCUxtqNSAiQ +jVSCnRaWgyjiLqxcEUVnZViqqEFOVtWx +JBEKTLZtEutMifKSNfBGHxSieFIsTxRb +OIzRpWeHIsvkPQOlrDVFnfMnMcjtbQgr +wpvaJUgoWAKQHObZoclXEdBXUkfAxNKF +QzvvQCvpyZxKCYMgKQNZqaOSYDtplPye +uBeLumxHMQGKbXtGfksIToMrJKkBhbsE +XvCLexCQZtlVNyqiMcHaIpKxRFdmoPqX +oUFuPduQjDlPiBuOdUeWcjIQZKqSJYeB +RoeFqMpdSsTUAItVvHvHvIqrkmoPFZps +kDetEkMQkCrTsgonqCwIXINiPhHgdMRu +LguAeUIgaXZHPkJIsNdUXNiFQEZXYaYs +RBnjWwAveZDMPPHeiyDXSSgUVnJNKaJN +FkIZKwvdpnhVUklXHOdbWwXQwqiVlFxZ +GawOJCilmlHqqgSJOnopZtVzIclzLBiU +gimfInOAcYwpHkNoerUOahoDhDNMTckL +yQVYKUBwwFwGVbbgUijTimgPOYbHUpLs +VnYsfDWBlYxIDpFlieBVDVdGqHjetQae +mGOVJoAsLaRDOXZVyAHFoyYAMeKVMqoS +MgDMmyjhYgBYPGCpiwKqRSVTOnmqFQbT +hVfQGxxKbMsmOXOVyrUIMNLTtltrMEPG +BkdGXyffNBxUvLIWpSsxqsphtXMYWsMW +xRYHkaEljTRTmXWRKdneOdpRxLoIzYuc +bRPAICeQieLhSYYvvtjagZGGBDSqEcEL +tkaBjQnZOGgSqkTcoYrOFPzjpYBFyoiq +CQuUwpEFrfInRkHluVUiYTxoLQnipiwa +LGfzFAIQtXXRrANEGYJaQYbeFdVYOLDX +TaTYKGjNEuhRZMkIzdyaFxNfGLuXZido +ySMgZINYZsJnJpzwFWMzixDhFNFWHeeh +KzcwcaKbhFIHMZDQKxpldTEQZcLrfpRg +gzuOWajCtdbmXYVnRNTziqfvCUsURhRS +AchUiIfSSrvGYgmUWtyuaIXhUIqxIxoj +LREoojbRMAvGmLuIaaxnsXyBnvYqdWDp +voJDGucVlCCrBDiPZFcuXMkRsvjVkNJH +wwpVpJqyorYXujkJxHrWVnyMRtSByhfM +GBaVaqQseOHWWowjGVuJHGIMEIRtWjTH +qdOSlNIGpHXsHvLDseLXLeBJSDQNJgZW +aQzCmbHYudpyGfWRdxRZOomswEwtEjVE +WEKYwOxfDpJPnLbAcpKLAaFefsbcnbBN +WHxuEXSQBfdZidYxzQpONGjlygtlqbUs +QXQFyOVwebjGuBLOnuudCwjOTGHrsjeT +cTQNFrXTdQqXUFzBbVzoBNOeGUCoBnSh +ahcubtyYRWSGSKyMpzcNABEVsFrenlAN +DNclThCDBRskVSHtbEKNrDytQCgGFXKA +zURrcvDTQtqHcRltKmJloKMwKEFkUyUV +bsveRxynJpbFMGXlYHTrTPkSZUXEHBoF +tXJEJDPZBuaKlJxDrbelJqJTozzSNtvY +ogsvOFjFHhRlOYYWLwjvntHKXOnHfnCi +cPcuGtBLqpyROSyQpftZxVxwPtYidjuU +EwialJkOQxUPiOEnMoipSCTnPOUnNtBY +DHRWqYnMLsHsztYgGwskEeElrikMiucM +bHBIPqPrOLmUvylPEmLUtuEKPblYMckb +EQbwMGNqoeAoFdlCngaMEbPMnQqiNpcY +OQPZfCoXPuBhdIADaBRThqMXNRjJKqlr +giywTIpCqlZVfqQKZaVNwjqESpwYeAwV +EdUclpMEQetoYApkRklXVDemcCotolyS +NUwHMNKrheBuYtWgNABYxlnfbVdzReOC +gTQPcedpveqOHwbqRlChabLkBHkXRhHt +SzVAlBmNBOCocYnfaOVIcuyjnAqvEEdM +sHdxgsqVmDRTyjhqCOdkBnPCAFVrVVWx +GfmFHJBCBIHsWjZiQdhpYqBKQuVomKqX +xlwnhKuMyfQrZGEXRLNMOChVbKnyymnU +eLnOhZlEsXMgWHYFdlCgGKYorpBNtyfM +pBmoMfiWfGIlJTMvvkQxidhtLtupMUiS +OibxIdWoPnvGkvRHFQJkKrtaFoGkaDqo +CXXcstuUbrLgLFIohProuDrTESGSlbXP +NrUBRhVqqwcyYDMknEHhIliYMjuKWKra +TDfcWFXFurIVMEddyriEQrtGATkaumVz +RPKGnIRNdORitxEkJTkhMhIHLbkiKHph +KLTCjzLPNEppVgzbYdGrRcCrhAzRXngz +LGscNzcxGybskhVGTbTNUcgiYIjrzdUP +dkGovvwUGFiSpFuHvIchckelqNygnrRV +OdPbMxtbwRJwJavfIEitOlfWXQXCZyCD +YKSYDdHcbXWkogTVekcshsPkyxFldkwS +ugnqMkmNigdkVSejArWMDAYAVuiYGjsq +fPKitGBjHSfjwFWonFgbdPRbaUpRepaE +KJIIxtzgjDGhTBCieTOfGwbMPGQGDwgu +KCOmkOUpEhGtFkKJfLXvnMRTDPIsGbzX +bbtXdCqcaZTeNPvwSlWSmCtcXlvsihtm +nzdhhmvNObzGaWfZWADpWymdfflDjCLP +ApcfhyMVxbCsbnbWaMIMeTivbSgzMFaF diff --git a/examples/pir/apsi/data/db_100_300byte.csv b/examples/pir/apsi/data/db_100_300byte.csv index f4b81b9..c4b6019 100644 --- a/examples/pir/apsi/data/db_100_300byte.csv +++ b/examples/pir/apsi/data/db_100_300byte.csv @@ -1,3 +1,4 @@ +key,value XwVPJbjSqBueskNeesxkuMVktLHXhcriXlWDwKgWVBmkwXOusjjiggCmcLVpWFrG,mTrOvGXwEuTowCuItXPRXjCtaZNZCZByFPRkwJsYMtgogeLZzaORGAqnSrSGarPVfOuGZuJUTEathkZUqBbeEKStudwFkchgoBJLWpGcCpgzyhbaLuBdSPCNWfOZIwMZQECXwRIMYUWHzzWJxgWrFVPtxVNWIEPtGUpvFZLxgRnNEiLYFuRlicyKvdoMYGBKaQIYECSbpuKPpirFFjmskZNGYZqbaWqKLxtlpwjRZytMkXErHDSFXmOpsXHyFdEUEgkzjlcSFyufKwXFEyoHINTbMWyqDeawLQxvqgfSZLON cafwKPrgYsnlmieZpTScVkQNtdYwOsBYMjhseCNUjrhwjABJtZhxzLJOfmuEFisF,CvJdrvrBMMztisTiqcESfgCoUtuEziyXuvSTGqoFuaTbueWcdnGcpwbmoVhsdZdzZZVLvbzUPNQguRXfRtNmTOkXTSWFtPwHUUSComvUdltxVuVdtOtvieBMCkQrXCJyjKFHwoVYIccwcTvdbtrlJlNCiNdRIDWvVeKDWSTQKABnThiUVlWVGWcoFUKnwsAPUxvxjkCKBKAYjLSkWAQHTEmnKxErZpjiiLVYKYkGGDSREfUIsEPgarJNesWYhOpEWPnzwFDARiHeiyAshlAKBQTmCwPOUYmSkLjGdcRdjdID FvAqycufsPwGSppDHwApNMNLJqLfwuLGsYnsfGJtajjZvFPkgSLUBBlcPLadVDQy,DbLfxnoaBGgOjZRSIpzLfXHFmcjZdkVwYiNJfOvjeBcwyBOvHfalLcTTbSyVoPmMvgqbfqmNUpqpWIzzXesxFssAYQQNXmtpbsUzKmsPJqPXoCUUZEErwIMcnDVLAptfzvvttcRzEMbQKpoakYmmzHDjXHQbOhOKzMfpDlfLWmjKhUWneVMYjcFlAHCZnsBAobxlVLicInnbIhmirsXatCnrJBQYkUDRnyJQIaPJBijMZnnLxJCYlrimxhqRWsYMbZSpMeiNoPqwMuRnkVDiFYmvkYJcilqscWptiKwALBVj diff --git a/examples/pir/apsi/data/labeled_db.csv b/examples/pir/apsi/data/labeled_db.csv index 62e8fb2..8c0dcd6 100644 --- a/examples/pir/apsi/data/labeled_db.csv +++ b/examples/pir/apsi/data/labeled_db.csv @@ -1,1000 +1,1001 @@ -Yb,Ii -Kw,uO -LA,Oc -Fr,RM -NG,vT -KR,ui -jL,oA -eV,cX -uu,LK -VC,iA -ri,Pm -EO,ol -LR,NT -pZ,PX -LM,ub -EP,mz -cs,wL -TQ,bS -Cd,qL -fd,mp -bN,fk -cS,ro -Jn,Gv -QW,SG -DI,UI -Sd,ct -Pt,Vr -gH,WS -ut,GC -Yw,qS -WO,Yj -uZ,ov -tt,dh -fQ,Dr -bK,Lh -VZ,bz -vS,bc -qH,cj -Hq,eX -sy,DI -Si,qJ -Cj,Ri -ub,fr -bS,Ca -PY,LH -TO,Vv -qT,Ht -ep,ID -JK,jL -ap,yy -Xk,JK -IG,wH -wu,LC -Nb,NM -AG,Da -Fy,Ot -qJ,yF -UW,yZ -lb,Fj -eo,oa -PF,qw -oz,VV -oK,cC -WD,mh -ZR,iX -tQ,Za -Op,Es -dr,in -AC,bJ -Ch,nw -wS,BO -lP,Kq -Cr,RY -bR,Vm -WC,NA -Fs,ks -gL,yB -py,qY -bO,fH -zx,ln -Pz,cv -yo,rv -xf,fl -cq,dd -Ma,cE -PX,iA -Ud,EJ -Xf,Ag -vW,SG -DZ,pJ -wV,ZQ -tl,wt -ci,sm -jx,KP -sl,EL -zK,hE -ik,Eh -MR,nk -ke,vT -Zd,XQ -nv,Ad -qn,HE -Hd,AV -QU,tf -An,zS -mY,VT -tH,VJ -hH,rW -Hw,vt -cG,yf -Rl,eJ -zt,jN -lv,Zj -AS,lO -wj,rc -ER,QX -PX,PJ -Cx,bV -uR,rW -fT,fB -ss,gp -wx,LQ -OA,vG -zG,aX -gj,jX -EB,yp -tz,eh -Lw,Fz -Zw,oA -Ok,Dw -yZ,PH -ze,gd -OF,Bc -ou,YZ -SO,rP -CB,dC -Jz,JK -As,hU -ie,Ip -TQ,ry -bH,cM -IX,eL -Ap,ep -DV,At -jv,XL -sb,Sb -vU,pD -dn,Am -ea,sc -JF,dn -YL,LI -sT,DB -Kx,BO -Dj,gt -KM,Ah -Td,CE -eM,GQ -WE,TI -if,rX -Yq,rz -xF,pI -KE,HE -kH,iO -OG,ay -te,Nx -sy,st -Sy,TQ -BO,Sy -OO,VU -il,uS -ev,Xc -Mm,Uw -qZ,QX -WA,EP -yh,Cn -Ve,va -JG,NZ -HO,lG -MR,pW -av,OQ -ad,At -Tg,TD -Oz,PM -sT,bn -QR,ws -sS,ph -ip,ZI -lm,sW -jF,Bi -QH,DJ -kD,lp -nI,BJ -ct,Fo -No,zx -ck,fz -Rc,ey -CD,lB -aD,TB -Eq,Bl -sY,VK -DV,oY -Yn,Kl -hu,lm -OP,uR -Hq,yr -fR,mb -II,XA -zZ,OT -zm,oo -Sn,LC -HU,uc -Mt,Kt -eV,PO -FI,jh -cB,Gq -Sb,Fw -qL,hG -ZC,NB -Hg,kE -xG,En -cN,CX -Th,IG -Sl,iV -aU,Ni -PG,Gm -SQ,XP -zi,ip -tu,Jg -jZ,uD -Rh,Zx -td,wu -MX,qG -hW,yG -FU,FW -Kw,vp -Rm,ae -yW,JR -dg,uH -Ti,EX -uU,pq -gy,BJ -cV,Mc -Vf,rK -GL,ge -Pz,uE -CU,lT -pC,pa -XO,qn -dg,rJ -OT,RW -en,gO -MA,Yb -BR,lD -aO,DY -LS,ZU -pf,pI -xi,fs -OQ,AA -Vm,De -Kj,Ku -LG,PE -UC,LQ -pD,dZ -WY,Zb -qJ,Cg -In,BX -cp,HK -iK,kp -KD,zB -nH,qE -nz,Oc -xy,vE -zX,cg -Nk,vH -Dd,NS -Oa,YH -sB,Kd -NE,Df -DN,Ms -CS,VE -gi,uU -un,En -bq,wi -ku,UQ -MG,qz -JL,Bq -qz,Ay -hU,RQ -lc,yv -se,zu -aF,HR -AH,ca -ZF,cl -VW,ux -pG,ZF -hV,Is -Be,DK -Ot,Hl -ra,Pl -at,Js -ix,yo -DO,ie -Hp,zv -Fj,sm -zP,fg -pH,vk -UV,qD -tY,RG -Dx,Cc -Kt,Bc -Jo,GJ -NR,ow -IV,yS -bI,Ee -ge,He -qE,Pk -eB,Xk -eg,nG -rr,ET -jl,jy -Wf,Fr -IX,cq -ny,ct -QD,UH -kF,WW -dp,jU -yO,Dt -zo,EZ -CR,GW -IP,dg -Hi,da -Ta,Pv -Qe,EO -EP,Lb -zU,PU -HN,xm -As,fw -Ka,pd -YP,DZ -kW,IN -Nz,qp -rK,iA -Bi,IF -IQ,bu -ih,WY -oW,KC -Bs,zo -pe,OQ -Wi,ak -qv,Vq -IE,rY -tT,Vt -UZ,ry -Ep,ij -Pn,YJ -St,sl -HS,At -vt,xI -YN,VA -Zm,PO -pv,vY -Sj,tQ -eh,In -TC,Xb -hm,uf -NT,vW -Th,Dr -Jk,mM -Yh,te -mt,ow -em,ux -OS,nS -uu,Eu -Lb,Ij -uJ,od -gs,Pg -DU,sP -nu,GL -XJ,gY -Xs,bc -Sv,sY -VL,ko -VM,Hj -rp,mB -EW,lD -is,Wk -Lk,lP -Np,Fe -dl,xL -ka,EY -ns,If -tB,Dn -Di,Mb -ST,nN -ld,ll -yT,sE -Kh,FX -MZ,rW -zf,MJ -MM,Vh -OS,Rl -PB,tw -nU,tZ -HU,XU -pR,aP -YE,lv -eG,Sz -KL,Gz -kT,Cw -GI,Dn -aV,Yz -EL,fJ -qw,zI -GE,rh -je,fk -Kh,Uq -Xa,vk -BF,qq -YB,kz -UT,mw -UO,lS -fF,Qv -Sn,pt -YY,ik -ZU,wy -Ft,EM -Tp,oU -bY,Pe -fT,QV -xY,wz -cs,oB -bk,eK -bd,Wh -Ii,NP -hu,ER -dS,nV -pK,Ia -ub,cF -Zh,VT -Zj,VU -Ig,ox -eV,UF -mD,ri -FA,NK -Om,Jo -ZT,CC -OO,Tu -IC,zd -Wp,jt -Rq,nf -RW,Rz -qx,bY -xH,qT -Pr,oQ -pE,UO -zV,SJ -Gp,xt -ON,qI -XX,ao -sd,hy -za,HA -vL,Hv -iO,Xl -BS,iD -Dv,DH -Gr,vm -pP,lc -IE,ap -Zd,Gv -gS,EF -FB,rU -wB,FE -OI,Aa -iD,Oj -Ha,ag -WE,rI -Ep,LH -YB,fb -Eb,KD -Vk,Jh -Az,uP -Ap,nX -OH,xt -HD,Na -dK,nq -KD,LG -to,py -SC,Gv -MC,CJ -Le,Lo -zR,Jj -WG,Gg -gX,BK -Mu,fR -ba,im -hg,Bs -CL,MA -Va,Sp -em,uz -IR,dU -mI,ip -Tw,Zj -XJ,vS -Ny,CJ -Ft,Gf -TB,ll -KF,pk -FZ,GB -lc,tL -iC,fN -vi,Eq -Nk,DO -Ib,OT -Bj,Fl -uC,HD -Pq,Oj -zA,tH -JW,xp -uk,ey -BS,IU -Cj,or -fZ,fI -sL,te -xc,ml -WX,MS -ZY,sU -Nj,vb -Sf,yh -tt,UT -nj,jh -ER,qp -wW,sg -Jt,hx -mU,Od -zW,Am -Fk,GO -qy,hW -fc,RG -AH,EX -Xe,lr -vt,RN -oJ,nZ -FF,pO -Pn,iI -yg,jL -eo,Xo -Tu,EO -Cf,BK -Kd,Gj -CA,Oi -pn,Fi -Ut,iV -ty,rH -kl,WS -xa,HH -mc,XD -qt,Ru -Hz,pZ -fB,Bc -gT,Jl -Wc,FD -Rh,MC -CQ,cJ -ot,UO -yU,nF -rU,sv -ob,gC -zI,lb -PS,xp -WT,Yk -MU,KE -wt,sf -Mx,Hz -Sb,rB -Fz,Np -XG,vc -TR,fE -hl,EC -yJ,uH -xQ,dw -vM,hE -lG,go -BW,eO -rq,qy -Be,fx -aJ,Kq -as,nD -Oz,me -Zo,eY -Ht,cB -tL,EV -Vl,Kp -Rr,Qb -Hx,Fv -eu,QL -nX,DZ -Ic,ZQ -ad,iz -fx,AM -Ta,uw -ya,yF -HY,SC -YV,hJ -pM,QG -bQ,LB -Tn,sT -oZ,zO -yt,um -wn,FR -Qe,mB -ub,cO -oN,Tu -Hb,pV -hH,CA -hr,SM -UC,Vf -UX,NN -PN,lq -MI,iU -Kj,pv -rd,VM -KH,Nb -uQ,eB -Mx,EB -VB,Lj -xK,jI -Cm,BS -JK,os -GF,ct -bJ,tG -dp,Se -OD,pi -jD,IN -wJ,gO -us,Px -sC,GU -Jy,Ql -FI,Pf -po,XI -AE,iV -mm,VB -cg,XT -VF,be -XV,Ux -Sh,WR -dr,iU -qa,Uc -EP,os -YQ,MV -lf,Qq -ZJ,wZ -jl,FD -NP,eK -yk,Mk -OZ,ZB -YI,ET -OZ,ua -Sa,sQ -zE,jx -Xg,Wu -xM,xM -nW,Gc -RV,HK -js,PK -Sq,HV -to,HQ -ue,xe -as,jw -Fm,yF -Zg,Gq -hW,du -fA,Vk -zA,ma -MG,du -nH,wu -Bt,iR -CE,HQ -in,pS -LE,Wx -oJ,Ww -Ki,Ea -us,us -pu,bZ -XD,pz -JF,Sn -cr,oT -Zi,Py -RJ,Ni -Nn,aG -hK,Wf -Pb,fD -mD,aZ -tR,cj -NE,CT -sd,dx -oQ,yH -pg,yR -ai,Pf -Eb,dZ -UY,iB -jf,Rf -uR,Al -fd,Ld -iB,RA -Fn,hF -Mz,xW -yj,Em -Md,CZ -rx,lR -BQ,nk -IX,mT -yd,oG -kq,MU -nw,eF -Tt,Ob -GA,Dd -Mi,Te -ir,LI -AR,Ia -JM,zU -qR,fn -Jl,OB -ca,dG -Mc,Yy -Kt,LU -Hd,qm -KK,oh -bi,gb -th,Im -Gg,OX -PH,aB -Bu,rM -SQ,SW -En,JV -XF,hJ -ZL,mx -dB,rK -pe,HN -hu,tU -tM,Ca -LZ,fx -jR,IE -rH,kn -MM,UB -Go,oO -bj,CX -FP,Mf -ym,nV -QZ,Gb -bQ,yL -tT,kC -VV,wk -IH,jP -wU,sh -hG,Kh -dU,UM -KO,Sl -Gm,tQ -OW,Jn -YQ,Pa -GI,RL -aZ,YO -Yh,ja -Ay,HF -fm,uD -bI,LM -Al,zt -yY,st -cB,hG -DS,mp -RD,xf -sd,gJ -hL,Av -bx,fY -Zi,jc -kJ,LZ -df,Cz -wk,UE -rW,lD -jE,rj -dJ,FP -ka,Hn -yG,ga -FB,ib -wv,Ed -oh,LW -jc,ds -vC,ae -Ej,jK -rq,uT -Jo,yF -Xi,rr -yp,Rg -Xq,sD -yB,Ws -Go,TY -Iw,OQ -kg,uG -gF,by -kb,Pi -Jy,bj -CN,WL -Vf,UZ -Dq,Ge -ug,yX -Kk,GM -Da,wr -ie,Pz -EQ,xq -nS,nb -fY,Qx -LR,aY -Lm,Yv -qK,pd -ME,cp -eh,cE -cg,hZ -HF,wT -hO,mD -bC,LZ -Ko,Ly -bT,vL -qB,Ij -LB,vB -Sp,vX -Yp,vx -JB,rs -Vg,Gn -Oo,CG -ky,to -mE,Rl -zt,vz -SJ,TF -xj,Lh -vo,sS -HC,YO -Tx,br -hS,Mp -Tp,Cp -aL,pM -nU,LG -jP,zG -Zg,sU -DZ,Ph -fI,Vl -Cv,Jo -Mi,jH -As,Pw -AQ,Ss -OD,mJ -YS,MF -ah,Vo -Ey,CK -pM,WI -mI,sE -Ia,fn -gX,Gm -Cf,Xi -kg,BI -mP,me -wm,SJ -DH,fN -MD,NZ -fH,gj -pU,VM -XQ,IF -eg,JH -mI,HJ -VM,KM -UH,XB -Sa,Us -gi,po -II,mZ -hC,jm -ZZ,Tu -UB,FK -AE,yh -Xf,XW -Lm,cW -PR,qU -wv,Ho -Yb,xc -DC,ea -JE,zJ -zy,mh -tE,rG -fX,Uy -Ab,Ag -Zm,HN -Fd,UB -Vc,Rh -df,UR -at,Ra -Is,Nx -sM,rP -nk,md -nW,oq -mW,sZ -IW,zS -nZ,UT -HK,pK -eV,Fz -Sv,sq -pm,HE -Nx,Aq -yZ,qd -ej,UC -bK,LU -Iz,oC -nf,hm -Bq,jn -iW,YY -oP,cd -bp,qB -hS,kf -BG,yY -dB,tb -Ul,Gy -Nm,wl -bw,XJ -dl,AA -RG,Vn -uY,Li -kG,en -fp,gW -xf,kg -Ky,yS -qX,VN -dT,wW -Lo,ZR -Ou,id -hf,xg -Lj,mM -WF,Mh -uc,DV -Ei,DG -KF,ei -jE,EU -jU,Zm -Wk,bA -Vd,Qr -CJ,AK -Ai,HR -qU,tI -iS,nN -OA,bE -Hu,rR -oI,df -YH,zm -ff,jg -Vq,SS -lP,Vp -dy,sY -jo,ye -xr,qj -kA,EH -FL,ki -qB,he -Rd,oV -Ov,bU -ze,Ow -QU,kU -Yb,aU -yz,fe -kV,uQ -wm,eZ -Xo,qE -Zr,sd -Xu,SS -BG,kS -sn,zS -AJ,Wi -nK,cV -Wj,Mk -eS,CQ -Uy,Xi -Rn,QY -TV,aC -is,CT -Si,Bo -cu,Ae -Ww,Yp -cc,SQ -rC,CT -ed,Tj -Rh,Ky -VM,Jd -Vu,KN -Mh,yt -hG,Av -Nm,SP -Sv,EG -Xp,VZ -ms,Lx -wX,RK -DB,xG -Ml,aR -AL,To -qI,aM -ld,CU +key,value +PSSAEqtkeNBxGsdgLbekOPlesVzFDdzW,uqhUCbUVKXIpCWHfHSERHqcZzrAwBIOD +WbJAtEXMQdiRUPQmLaVHOXaSSalWCivB,erGxcCLeLobbiWTPenQoXPWSUFIdrFQL +IUYMRlEYVYjCvSofmILMOJDuuCHoiOkb,CBoAzUJgfJJdPwtDCQmrhRubnyqqsSZV +PsxQKxvhGcfrRIeviMIzmGzHkeqWcLNJ,oPyfiqSdNnoiJjEvCgQGXmiCpHHGXuna +uvRfIYpzyenCvyTwGRibBsCIcgbYxMIq,NJUXmmBVsPCUsDRCpVlTSlvIqtLlBhNI +BAwGmPuWeuCRjcevHXgcUQpqzxkbYeGD,oDpTqFMOLzUtFIxKxpBMwDXuJcOrWzpH +kGBivxOLSyakNKUbEVXJRfVfdqEuNlGF,oQxQtEciKvSxFEDrimzvqVVShQqZxLBf +MIJXyWSUPawcLQlbeuUfMcQGhQtYmaAu,ZHkoKplMnDjiPWVpKKCTvqftemeqYABd +vWnpnBQEZwkAbsLfjikRnAEUvOpCiuBu,rMpGqWbDWXwKirKziRnAJFHjMxxyxxau +NWSFtaMmilRGquCfJJePYcYLxuNrDCEr,kZlppczrSaQIGHgwJGcNjechMyHtBhMf +CcWjkmZKwXFuLaqgnSsgRgmLccGGxtoQ,TXFcuzyYIFjatnrOAMupHkeiHYOluywn +pFaVuwmyhGNqcmfgaDbyiBavCBjkHTzt,qYdlyMoMLAtWeroDxtGYZCXQBUVBYayC +JEBSofPthOhwFfCdplnKajLDYkmPsgzy,OczTqMPqnlbBbvbHezlcCxMIMmpWVBlp +vwsTwCgGiCyxgjQvrJigJDhAEbmbNSpB,LJLRSmgqrnKUAGfHPhQNYnGCMvgnaYKi +zdFPwnEIPHPWzMhqRnMDDpoQXCziCdXW,gVGmrwwNoeKVdKyHWgvjUvvUaKFvwVQD +EaoPFDZowtXulNphmaeaFTkNjhORiIJM,AACzaVuqrXUXAUbnFuQwPLfpaWtbefXt +McuPizEEjLVTKJpyWQCPHbHxMsOLnvhC,dPhsInJkFURLCYskufBwaJCavqdddrlg +mpkIzvUxmXKChuYZeepVOPxhGqGuhOil,SkfpSAYPhBEDDXGsoHutoSjkysJDZlOr +fMyfDbDLZFAcLKlHfjScMkVUAfsCPEcf,JchSFhtiqAcUKiPFwvGVwOmgZlKMSJdN +VqHduuHKyUvVkDztYssEfDTZVLexziXZ,lDHJoSbZzNRtaXMTvYNSmQowFuCResSe +MDVoPzBbTRrfzDJxPZmGDvqHPVOgHksd,zBEnMvLqeiYaDPotQEpKIWgVmhoSGtsg +aYqNlGDyIVJnoBxBFWxzhDdZOkxawmZK,GOaqXWpHAbHDodHncAFACzreFsJbqeEv +nmOpYVznhgHwmcKejiuSEijBQtimiQwx,rCzYEMvTZzGHQRrfDiJvKMWicCsUtzLH +CIjvOJqWjPNaKGBfIBOnRSifJPcFcayL,mAmFqOrAcHKVUzWOcLRrVeXSkmzRKNCj +wqcyFpGTiNWsOYNAUkCoWvNJshROwzJO,bdkiRKQZxojYLfSaWaDZjgEbwIGzBEjC +uEgMiDeCJeZxXfTRBnvhWlWONPmRYWUD,IPNFfaSUuTEEdfoLPUWEFdCgKKqifWsg +gvBpXXSOhBXcZJilqPBgpSPcmzuLCVRK,TZtRWthsSLQnHBgShOCSbxumszwJdTwg +ROCLqepWqvkcyXxERuMSIkFRDuieIhsu,DktCahUauWVOYsMdqgGqxcDpEURUxeex +EjoQhUJOtiOkQhXIKZtCBrYkQlJzhnyb,WwiKuEqHEfDbKoFHfWuNEYkLjQquaeeM +ZZGfUFGnKaNNBaPJgtLMCxRzUxHCKAOl,PDXXKPCjUuVynNCMlZIRsfxmumMYcWop +ZaXBoFAqbwEEKlBehaIiRMLfWwiqzOjH,LLnBTuwMbxNgRjliuUyNnJGzfkWrewzj +aRHsjzXwrZYLSJJiiNJhqhLQdCkfDVeh,uFkgNYLcBlFkBQAvPnUoCRnLIfjDpXlC +tkUVoAhZaOiinkwpLJwxvSPExCQwSQkO,kGDCkUkuoxOtxLirxJIwMXJVzIdVdNmD +ZckDaAnDMJPbkkHqGFUUNpRItTcznEnd,siBIEXYfeAzABPlQHAbzhCPJrkwBazIp +pAKwGaCvPAtmuFxzCYAmBjixczqouxmj,jQGpTYWPHNamVeNchEWPSFoRiVlMDlTK +FJigPBLlcKMRiZTqWxiFDPXlzPYAMHjg,OnPHiypyIHbaTkfAbQMHjucBETlBfowF +xZjrrTJltltlvzaazdItlYhbplTQSNDF,SIlCXtVhZKpNiEeMCetYRPWdZnWbqxkJ +hFHslpypJMttmuEAqqEoCTGhtRyWNDqH,rWVhYQgyARnBZKgElbBnQtzLoSBEhXfK +tHykssZjNKFaXPgGTAQhZHsCSWoMKmjF,vvgTJSwsVmLJUJcNjorfYDdYnbCCtpnN +GOfIYjALxvJZIXQdDVKwcpDzrlXqvhKY,GdNttwLwdfpSRGObuLirrCzbMenXaRMC +ifGKMLXwaUwwMKiusbIOXAGWnFhOxWoO,tpIpTKezyThMdDLlOzbZfKGShJhpzGFz +RfQWpBVTOEKAQFaGiCDhXIUQdigqUWtE,GDLcwzSGeTpmaisvfJITqQorYFkNgfYJ +bftJwnilBZREOfUcuxbixVoJbmjApick,MazBHZryknIkUbZyosjWcJIXzdQgMEAp +lcSFWciizdzVKujOaMCGueNbwZGEWpuA,wRNdbqpxlPqPBJRTZtjLRTfKCuslBTEN +uoOzFYcAjzsWfSNKmaTNxBYNmgawZvmv,AWgarIGwexlznAFXMYyoWrXgyDYJQCmb +JHvSwrGnUlHQpIuIhiczofvAcHZyljzk,bYbhopWTFIbCpLVyiycXPkmtkYhyWPiN +HYLCKtDcuEYdSMKNYJeqNzTfziEIGGkz,MRaXFvXeiCIFYTysClGIwqehzLOiKxsK +ItbYUKtRFRNvKFhsUuKlJDTkqtTxObuj,oSfeetSvvnxrDDjckuXDhrKietEcgJPT +AIsZQsbOVnfRFyYYyRVyRUXagxTaGgIx,ETtgVXQEgbmkkKHhdpECzeXlXjkoLODP +FXEseNgcxyeIZXAbVRFgoMRBUpgCZLvT,BmztXWczKLkrrcXgtgsDhtHSpcpNkUWb +aghpfVKIrFpWYmpGcGuqcOObEGkQslMU,CfDzRoDlxnexCYElWSFvcvgmTfAUiKLB +ICiJZRZnmiTvYjRjMGyEOuWimsVGPEcS,ytaZAyrrlEcsBNrwnNSEYqmILBMOFZcW +gsBwJYCZgOnbGbWdHvqEyhKeIslAsIqb,whOYUwNiZJqEZMiuAWPOfUmNzZzEtWfc +XrHmueDeedKLyMpsCSnupxCImDLkxJOg,YLZEpiwSyBkbkmWnHGJPWZkSuLXPFiLV +TyiJbVWmOGQdCGcYFNsbDvfNyXAKElaz,OzIxYUNTDoxMLJgOCpxsujwJtaWzMmpD +GfFLNzkgmTBUsFtZLyeEoigyXHNfIyYr,BbhDEbfQgAhTLBAfvVgefheJMOItujrK +ypwYSMAxkWMdISrdgbCdXLyjIIGzZjpr,OzNpOzfGoZmUoUyEcyHVllXLYuxCchXp +bYhNBaTRmNeOudRYJlylhdnNtXDjKuyS,qrGZNoWVZGmpjnagwwPOHkJpgAagILfn +FDtWUoKDjAhpIcmwCSmtsLRzMFtODCBf,DTrSZtYAlgzMUzRagFJqsTFFyrUNqxXN +SRAkOizokdOMepovjlLMVeFNdHQWQsWM,SJqesGMhwXbIMYlhqUDxmlyDSxqOCNal +wFVsqNdUcUaTrYNYvVMPfiVjmixmuiFW,YsvYIoPUEswTJpSYQjVpuIfcBJSWKBtD +WxaRHnoBUpGGAlAebIUJODZXRjvPwMnu,lWXetMroYzctzufyTNUGbaiwPJRZvAMN +MhaVMxIidmiRbKFOdWyMBAbiJYyVmthj,lWADjSTulsLkDduRNJEgxoPhkcCaNFif +GeyDbQlNXOJJRrqYnXpcTYMHHxPTiZPV,YYQVLKwBGTRxMBSxVYflyddwUybqPCru +YmUKuCDMBvALlzzrOrdplOyQHVBpmLeO,uRXkzbyBhTBrXCpJUdqfNbfzOrHnawBl +UtQvfJnxnrXjyWVlWRnaqvvYOGxhrbWI,wIAliouWVIypzIcvayqXDwWxeyDSTGvg +budEHZUeAfRtjlzYYKwCpMEAvmCmoOYf,CyunXevQixDgTNfEFLuNdkkHSVgPxorN +gslmLuXuVKgXYktaQZWVvnodYxdLbsbW,pqVWEgEDLnJtMSvfvbGyzXUVJqhVkUSZ +AxQhIEbXFXmFoSXSyDbMlVJDjiLKZQer,vZMyixeCmYpeVGwVOrvDtFOlRMzjyxVY +ATFaexTjJyivVAURSyzulVtVOdvuCwnr,vEDhjukATgvVFdkhYWiBQHrwaRytorix +EbrutYZDjvOILtQDHuRnzegrswyuzAon,ETKEbqkeAfJHMvtxSLaktaODPgivymbt +PpHgGybSgzaMVtYNXnCZzvlOglbidyXr,etDceenlaxwiknIgpQnBdvRjbYdRHDtV +jQJyVgmoxYiVYZkGDDJGJbtmyeKHrxZq,fJYWKiapfiVmTwlapmClsAcihyBaLwYh +mTLODzILWuuRlQFGVEDqyQshIRAiRdmm,RTqjIcLXPSPkCGWCQGNKlnjAkrXZprsS +EnGSAvprnUJPFmWevujqnUOIgitsNLoO,ksoPwCSLBWRAWaDirLGWfGIdsHiGBqhN +RSZwqwEtHzORTbPAAUVisqEytYbPwEwm,yUYnOVsFNmjgljEGyXBInyHGeQgonyaL +JEPNWWRtIpyIwIuKIpgUHOowSIHFyvLs,tUrfQXDHrFLuNMMlBIcwAqsgErVBmbeS +BluXifOeFwoKZSAzlkFMDZpkWkPhBmLp,qFdCnAMajRybQcksQXsnyBdHmTPrILXW +hQfqbPIcsZicgJKFyTKrZESiSWvwrhbq,quhkaePihqKQEyKbeyDXVxFyUeLYIaeq +CCFEfWYsDatPXkKoOrqgpvKKPeLNvvYl,kinJwHUkIsREHLhKTcJyXnMXOPpesZMn +mKLabSFuuMUBJqnuXcwDywtIgecLuklv,noCUTKKAgylFhpOUngQWbHqCYliIjkqu +DrvxCyEesLRRoyhLYutaJyedfexriMSr,EuuizezwaCTKchrsYguYPskRzNoPNdAd +jwTGueHaRAWvqNcfjElqLGFtSoQEycah,kfBvYhXHWTCCJuHdulEFHOkkiZHOJnKf +MTgjDhzkDSWZxiYrLFXtRwGDmDlRgXAX,lTWtFtJEwgoiERRJLZtmgOStCHlgRjsu +dCUNbiBhCypkKUXodPhWKmYsibwkLhHz,HTYlWXwiCCpupTOApnmkWCEkeUtyOZjL +uaYYHPkZkhwlneFsNdRnLRDMGaXCtrjF,QURIEfBThOBblcSCnXlFZYUMvDVYCFpE +GoztwjIabKJzHAbsgWIMXLGgAxCsEUoH,kgUsaGyYqeTjOxVfMDkmheDxuaQSHaBr +SStroXjdlqUjjTZfQtffqVjUKyjRxsGo,JWBuQfPMaNEzerAtkyHygwiPhMTeyzMz +djkPnQygbuqTUDdBIMLrdJvxELzrsJpj,xFgYnglPvOzmDGAetoXhequvvHZtUXTD +PsKtnQGmpKqaEsjVIFFLDBQVInIfnxLL,KXZJwdJCwOLETMCHETqcdySxEcEOcdSV +cKQbzZvGhISNunKnmzZZXJNpeqcgCrLd,WickcKtAiiVvhteiKZrBxptNYcNxKfxd +jShfRYIiXBSabeKIwkRXoUaFrpQPRcyh,EfqRMRsItRodTBUlfusOAzJKEGywovUr +TBnKjAdOsbAVWiBWCayfdZqgtyHyEcCw,JLcjGSUVfLsJuNywfzwcAwelTeIHxzKL +iUXOFQRrHBZWcPfMHMguAIpOoSmGOBKJ,IHVBUjirUkkDofeIqlHcwTKWEfcNfqcY +lpAiErsKKjvlBHVJsSizvZjGkWlzrSck,JfECcdvgUFSOfePjNeKMAhFKXsMpYXKr +NUquzFNploIZxdcyGdqlJHvYRSwFjEnO,zQLceelobCNLWtIELwwlCMBsMupCMbKn +NVomAXjyOMBLRbAfoBKwmrnaOrWyQsJE,JLQahxGpjmUHuahLXadmaCdoKrhtQqTE +pTeSAkpQHkXuErtawKyVRCImAXPCfgpB,HdhPzthhbaGwRLHsnBWYHzKEZEHILbYd +QBNWpsoWqZvVLuSlkKJmIODVViyvpXJc,PGiLQMeNMxeCdZThHoerMEGChKyijlOH +tlWrrdKSxyKUXswMqatIzhMtfCLeLgtI,XYmbuNNeqgtKUZDuAoshmErDqFaHWAZk +YWQucQkvmTWrYjXJvtuYIrenYZOIOwgx,ImqclvLhpCrqLCwJhTlRXrPhpiaZmvJf +HDfeIbvmCfLzeuGbbYwKBmnpWxFAPmxX,YrHoOlIeLmzwPreDonBvgWsxfxVikcha +VvvToMMvfLloRKyYDjrJZHyPXrDkOYQd,AEqJcsxfmNZkGggkAtOdNlbSlYLWJFvy +jNNPnheTBdykKQgPwiyGVMajESqdaUau,SUqERojYSXxmuHDNcKiQwEbRlqbYzLNy +BdMFSngVSZRFsFFWiodPshuSBGJNTDBY,LVuDyrcFUHswihjqjCdLUpsoAbFgZQZn +dKyeRJfnYLcdJFCNtUWXZSaunqflGxky,GYyYxoMfCGzQmfcIYetHuOxYEhccwRQs +HhpqPhkgoBPXmilwWiSdeuuqFNDLNrzg,jcjDzIbwKLuQRHPVbdnQBItkPWFzdyBO +cpJRcAFUjSUklQWDtKuiNGMedaMPQChn,MsODUezhcIIrxdKBlqezOLHPBVUfHuJk +BEVYbxfmRDEJdFaYLmzYzgWJdziWNjhY,SsbLUqGsplZYeyTKaLDWdKDIHuMRgZKT +QeXLRTbobSPbVHSIrBSwdWFeyhekHZQN,MFZgFEzTUczalLLRjhyYxgvCjlRlzvBW +PZFRsptQAbIQFBvQfUzPEhTaiAqYDIuV,tjMhfSWUcwTBvJfFMmUEBzYcPGbhvcAI +cIvTgSzMfKZydRrpaXTnRVTVFdOsfTYP,UBtjhnNuclDHKAgNGFOQnHXsZMdQEdOn +aXTlPhPTRBGkGmaPmzGZMdzioeuzBjsb,wpeURiXKxGBAnmaqTOCdzPKjJmBdVuuh +YXeMDNDJVhCsAWfMpupmokfwMAsoEKJX,epCzBacensGxXDiJEFWNRFpBTNVuRFGg +CZvCfGdLtLRKFwknHbLTPbjVOmCOsMau,SOVyKfUIZkOtlAVfqASuhpWdKbAzCwkR +JpkyBqIIOUpQtQKpDfHMMIILGLhUaZZs,ckacWuxifYChJbhLhHaTZbzDYFBOuQfE +RhWDTwtbymbbrnNMIaDrexwJMRxObbBq,oVciBJDrDvstAOJXEvoKoRrWRoDFUKtM +NNbPPGKSJBtYZveDAoRITGJdBOzwGwsX,WmJfSeMCFfGEnzHAYRJFlBiYBEEyaZNo +fpWayijwNBDbpzNPRzLUpSgetKBBhrEh,uOGoIYROqGmqGgfoVqQbHCDhApkWLpra +OcLwvtKYRbkVuCKeCsOGadfVrluitQvd,EaLZYMBKeNoilQdpMUAPuqRnXnxlKEDB +lcTuszfOwEWCjIJuEOoAHYGdnaBRtpgh,IndRPEpLAaNzcRcYkiYayeNBRaJgMpXk +iUGUGvqzzBWwTMQdYDrecuYlXfyUmJPg,EJCTeInKGNkKoXbsolMebtYEMMZnrXTq +yfaxPpzTfhHZsWRzGmtniVLovljiaoBh,XlSQjcKEfsFNsMvyTVNaarInSfXItTaP +oKzkZHtOjLRDhGoDpEuVquOqLWSVvzDv,OInbyNczSqbhntYhDrooopyxSfslEZkT +TLYBLAPbsCvfvFfXhOWnOCgykTtCGAuq,yhNebgdLPOywZhZMlCrqmgwpsIPthzum +zXdFtaspkVRffHWjPeEULObqJWMJnKZr,ljrmyLiVieeqXOVfUAeWMNwBkXiZRmHU +kOtwGVsjTvyIRLwawzhBvJFUgzTfqcru,qyeCzcpAqhMmnkarXwsZBfvftiuivFkV +DeUoJMxYMzpRltomuwjhRtRdqiVvsdrv,QzfCOkvFBYoIMpcvaUoANkBWzzMRTRWm +mBGoxjifiMfQVGhgorJSELLVgGTzilId,DZOpAIMHAVudSbcrceUGsGkwMBSCqGpM +OYlwpLLRVIiARQhnKIftPNQLEYuYIiSt,ukOefURICiWfzdNWWzKiXBQaImuOqKAx +ftCvcoTzgUmQoseOxHTNZOBTuiUTMoOz,woNUHvLjVLobNnsVkUbBKsDyhHkMELdH +lgriTgTVYZcuQCTZTdHWIoYVfgfdrUMk,ZnnDPYtFOwoHRjvVtxjdOfmRZwFuSonR +RBDwldOyrPhyNaMGHWukZNGDjreFnzrf,nlFRPlWtLgqnLITvUWoZVPiTgoMbnwOU +junrRgFGzLXRYRrllRKIlUBMHLAhicAM,pBCHlyHaAJJoFwYKGGIunqvFbBdEyqqn +cjLJQuhFjFmjsKyXrdMBrVNflOfMjSLA,uoeNlnWJFdYGFadmhqYrQySTICRgpEYP +ueHayQisIbKWKRvGBXHlSHTVQVoeunaW,WEJKxvSIljmQjqBuYFkjJaGmOlUiONMu +uouADCjMPQIOTaCPchrTzPCvVHnQFWHk,qecDRIHKmjAEBjVzrfDfoCPYZNueMaKW +naKfHTiJgKUANxYwYgMGtSzhqRKRiYvz,oITDRgvLyBDbBOsfmASQRWxqtvsrdDEf +AbBlHiNqasLcqzVaRBUiePduyTrsWqEX,iiHeiqaLbWkkRAvFuiMlQosqNOebIVKy +LsphXUVniaHqlQSZGDjmfTzQsUGukFsi,MuuuiHpnHxGKNupWDvmySIrcXAzXzZRV +wLQLaBSguOZjQJZjvvZiudBDCAwlNcjE,wIKUQkJMTpJKlnJsiQhiLIAvqQLAzHHt +ZLsEaxWDrbhPPEgsjGIcLPPQFfczDHKf,KWvXcdPqnxQtzdnYUgnQyXEDMfbhDbat +OvVwSjntaKTwggJDDGETyFaIYxmuytJJ,EblYMCirfbeMlbgudrNsRyYfjxUlikzP +ozlTPSfoHMDJBIyHXKoBaBkLWLsRaSRw,xpIursOCATyRbexVYFstNMoRNpoIWHtO +OLfbrXMqloNUatBekUBiujIyXvwBKYhf,dJxzOflYrhaIVSdsAlodlgxNIQMAjIyE +IOqYeWKSWiOJHbkGjMDqRHyRWTGoQSuY,iokHMQVLgVfLnOABWQCCQaOMjFkiCrVy +zLdXDNvVDYqVdzQnWODQNSpfyigYttyS,MvkjqRGvnnfCisoLaPNNFoSvNoJEZAYA +xqfQwUcIFDrzORSbzLuGSueIBWqHCIJo,DMPbHpIZrpyQyJiFvtUdiVUVeaWxQDFQ +BVOUUJiaMBWnXIRfwlsxVQqgEbEfvCml,jmhBATntqbfiizAPbLCfqnuyMcePtTEy +FiXfMeOIsgeUzpPGdmHEUgBnrGWrGSqF,YRSiUEyhYgXtUtGpAzSSobVvsQROXctd +gzyxFobxKLtkbRviQwQchObRoeNSKbTA,QhHlRpNJaGymESplMeSAMVpIZGKXZnXl +lScUwPMEGHymPCyYYQvopnZCUhcGxZGF,hZFIeFKsIiBUFbbIKNKdxJCyVZLWYPbo +lDPoIkbUBrBGVNXuZKjGCPTEbcZnTREu,ilJquwCdZpeBWRCSFwmvEsRPOFQpXJDx +ViLZFaaISPcerGgEOuIxSNniRVNJuBnE,QyqlUDSmDVtdLioyBOOdakzOZxlbDBgH +SAGmlIfxDnqHbcUCundchTWlOnFJEsWE,ExuJTuncoVDLkePvweMdEFkrEbtOWKRK +YlnvfiHLzDMOMBRnPDqMednolAPNVzqr,ECrFuUogutLEvIzuggHjageosTEGmEOs +vUQdLiRDqqvToUrAOGKVHhLivthQYotr,yzqIzkNZtjsNcPZOMFpXsmhMUrHWtndh +TtdaxOxpYASUonePBHyDNlggqwmyqJbU,fjflGnSAMlRvCJcJFAGFSHPUlQlrPact +yFUxCatUyoScToGOunIoODYfZiYSlbEr,ekJjLtLrAsDzeOpLvbUDAtbSbbzWYGSt +ZvJJrQJULJCTIszDLXdhICZOfwvkaIsr,kcbwvCtnAgEVbkqfAcvUPrQKBJdXZeTj +IAfVpLeEVFmtUFWtlXTCIiWdMYvLQkHT,XrooFomlKrswTaVWKdIzyGvbxmLgbaWT +troGwqOwwnuGNkPlBlbsZyUMjLyUCQZU,SLggUCYJlrpKQFQYqxbFErODflKYNHjH +RogYCnWnheBQAXzZTvIdGcWbrmoHbeuB,RBkSZiPbtaQdqVybZCuEDyRNxLQZoVYS +RabMtVudvghASoyNkQSqOnjGnYFClOEZ,LNwdOhUxZuOqkHDDfDArXnEQquShqsfq +MWzCEROCqTnUTRrVUCeaNPPMfGlSmxYa,qKitLPFAeAwOSHAJBzdESGKhdSLHBFgZ +SLCgrMZGPyRyBKQTSRIuXBWEdQiEzmDr,CofAdLhLPSTZbWzPNdfigqrVsHmPTiNz +nqGWpdCHcoLHtWTMERavIUTmVPonrWgp,DPkkcxAuqGStRBnMivCtQDUQynonKDZV +bFLRaGRagMDSfblqDlnBjyMCXsvihSYq,WknSgkBirzBpTEOHTGuKIuBWUlqfHQju +yVwnGNCUhDEfmTUJLDRbnUVoIMfahxsm,KVxXBttJtVHkztbREnAjQZcOIBNaEaDz +oRNcDZIhqlPkqGsjxnOxeakeNCHkxRao,yNaYDcNuCRwhbrJtrLyPMVTGDPQIhPvz +WBtlEDRohmlELgExBJhNZphLEgHZjeIL,WuDQiQNOHOyDCUpSurYhkMIAEOuyjTnh +PZOYfZeeQtpHfCmQtuaEzEGGocmCXrjd,wtYElIYyDUqhgEPOltCLwKsNBzeEIudi +ZYzeVUZBajUtdUOnPphxezopwMydhxWy,ibFIjEfOONmszoHNBQtUhlgJPkMNdRQH +MIBQrtiVdWwYYTdQjodksdExBRDHuHHM,QXLCoibhyvEZqYsEbImFwhqmCXtOsXVD +euSYGhwkxUxyvkSYLGtyzMYxKefcKbvQ,TpzLAEowWKOfpVcQakMpTbVodesiqTtw +xalbcsFKsjYWQlCLKMHtHwkqwpIBealY,MZWTmMvSEkaZXqTvKFjTGUUcSUpCGCgK +UNLijgWfCakapWqbVfDJawYbsnFYMRrf,svpEmaVGVleZdwzEJfytkqoYBwgreXwx +jziVgFqfkArTSJfXgQzBUWThAkxPHTNE,RNUjVfVmCecRtGDMdeDnIIZhAjXybwcK +pAVTVmZQLCOiWIPsukKiIlsyZCEIsNTE,emAoWUKQgWhOMTwfUZdFRXJwjKEmCLse +XTWNWXoNTBQUXClMaYhSIAuPjwCmBIbX,OqUywwGhOIXCazzErlwVKpPXMAwOpPGq +FgaxTLNfsnVbFatISMlrBbDSheXuHbZg,fryLzODsAqdXlvJRuwbdEnekPJmUvSwV +YIboXuxpmWzuYjtvSGnziKpkUFZdXZXe,SpMItzhcJMornHFgvKLYmVJDPzBUCcJg +CoQGWCwOFXVsYJcJcpdPnMsQGuvWPcOQ,XxGUlOlJCnqarIIhtXVIIENwmFYxICGG +IjyeummPrVaIDEGBmfmChxvwBQtdLIwd,KzPVhhKNeEhkEedPGsuwDwGGykkRjeFj +scwVjyJlrMJgRjNDWVjUforZjRQJfsQV,WUaoPfcmnnPqYBUSNSGcvbBfNccBvzpk +tAnKNfBIyCbKHqTTFWvjXegbuqkJFIKC,EYSTmuUmIwgqXqMENinaNefUGHuWNljU +IDmkHIrUnAYBsBjhlQoDxodZLbogwdRj,jwurZCLfxjPVptsbAqxzwzsNIELEqOPf +SlIePKAHQukkbLsYIkewRGnvUTueaVhg,PiWgVvlToaXTihPunopEDuralEHlFPGz +vgDFHWhIFUGdPApXRtZHEaGyBUnDlzsw,nRgeZpSKJsUmYaEkyxtclxdlnwtxjZFz +aLZzKmaMWPodllpKUnWIrOERilJEyxtj,jsQWLPXdIljtjKoupgyXvCNuGUYHoXzd +ulAQxEeocXHavOzmdCnHkvigcHVmGByG,UqFUopUlGGwOyIbigrIjCrXoijcYkxYa +DdzhlIdMRrXidvlcZgyQYJTDIuZEwlHR,lApvdKiHwPekjPtMhbmcFzGoMrhqmHKa +gXiVnJqWXLTLxtpAxMIzuVuqttpXGPWd,RdYkYaVaFtMBSbNHzebCZkNIiUxYtvAP +xittWPEmFjzJKKXTGRUUZmrilpEgziAv,KQGbrfMjKNtbCASXkQYRXBlxzuKkqrrC +HMHsnPrVtFmgbqbqsWfriDLSJiPyqvVH,TggkYiLbCXdZckYwxdAlSvkMgUOayyXB +QXWUWxwduaFrGevAoGaZdoKZMioDKpcS,rSJNcRjooAwmfqbjByxbULikitYOogTi +wTHqRaFnxNMWOUhIxJeNitNjMccKElgN,HUhqqzeyZUQVtuhfogrADeKdPnTTTgfg +aEsnGIkILSsPRMeUtcNhXLJUYkyBxtom,GUkXNfXwZDJNURVeYxqFngxtRmpSgbjC +acVHKTiecnpXDFWKoahubHMveUjEZmcb,ajrBemdcAYlZBvxKePcOQZbhooJmHrEc +ngsPgqykGiZZvoMOILXhVbuPhxEGnteW,PyiWscmiFfZzSJcaoxSApGepULjFWFHB +aDZAOwNQBCWLeQbYKnGolULTcdKmQHef,PZAngEOcfhdIyYyVOOABWOdfElPILzfl +ZadtpMkKidarGvTimcMmelGCsaDgdyTZ,ddSXUKwgrihYmuOHytdAxEMpuwbyHfys +pakLuSpyDlvlVYWJmaulXrPLjZcFgEMr,jMXZxDjRViSdrHYuuUYkNFpjXMdcckPK +XDZgRMRHCBormQgizMBOztxchEiAKEFy,FqiSWvzrmGmDzejnrMNPdxhGPxrmtrFR +xUcEbgDQMYhMCLznTcYrruYxmcqWdApr,NueAPcaaHkukkUVRnGLfzhFUFKYgUcMB +JDHsTMdoxfXUUnJCYnADCelGvgXlXrbg,VretbYNOxvKCAsQzHlCEHoKzaeBuMWGD +ffDodpsiEkCGfrXtcfzYIFqaaMqQfFJM,APAXwgKALQIwbCsoBTXfElsBgjxuMpbD +KFqrPiDgyftJTlLwZmQMHzKCUbgtpVrK,RqukSRVRASlPjBzHCjDNWDsdCkKAeugU +cLDCyVmabGbPjrPPmVoOiejNKOueQkqJ,RIEScZPtJoojusBwzwvsLcrYkigweBdc +OSjUaoEbkynqhtkHmYkILxJQjRSosaje,GVHuTGyBcjmLTAXGEyrzjOtewoQJbqSQ +wDQgJdquIIyBsjtRphbUrAfpiDCiHcdz,qSHxjbJwmPAzquUYDlJkzeKSgzsIbHrF +GXuBTmnhrbaoBUobVENqivXEvEgVHVbw,MBQCAgJRjZxWqqEthaFCkiRUeTOmlhpg +vmwnavhQAUXCTwVGxkpTsnBVtTHhMTWj,NnHWbVsIySrRYafAIDMeGGhOxRKXsycr +SvwXzwGKHOgjbwQZADkTYNDnBpKhxGtJ,mXkFPsquWLrkKLrxcilFaSzdjpzfhnTM +YVVcIDmQvRoHCHsQqsVrettiYHivrWRd,FgoBNcpWXoUzYsrPKfhzraIxmFGpgFDt +RtbixqtjyVjYzMulAzuEwCBHtECjyvQh,dhnUjneExFRjTUndJKGPewKbFPRjFxXx +aLjyFNfsgBOUFAuQBwxAvdJQSSfcItfO,jOJJMLehxivnLRyuLPwWONKGkaWquUXN +lRAdWyBtBCoYecRMsDuJANBQwMCZupsm,jENEneqzTVaqJwRCWpmuLDLtxakJekFR +dSKEpDHDWERCIyPNkXTxVXgpvgdzQnhw,IGbbQmlTKXymomDiTfJkfSLTIzPmOwAt +KTPOfvYYruNjNfgYOyaPZtdOcATgOCkq,OrrTtiyHAhWKZsbZlIrLxKTFElxqeddn +YJEePPnHQrXXsmDnYTDRoamGeiCuzjXj,ZWNSILpzuxuXUzQozzyaBPGAIgTzAMwq +GnnOmIBTHELbeWCgmAInPdgmrKHFvgIY,VluRWVKeHVbonjOHbZafbVxNqVFUNLxJ +jVZWUojkosUGhnwHkoGoBoSqOLXKNXqh,MFdQPGVoceVdmeZwOoSxfwiSphVoeJuN +BfKGqlkRJlBgJonsepKNbfWnynLqhpvU,hVVqRuPLjSfqeoklXgjWoAwHHcylXclc +pLgdmwuwAhCAUJtXuTJZVVBLNIrKEmIz,GyxcFmDKFPcrRtTTUpFgOOYxjUCLWEZe +RsEcclbbuFzHnZswJfJtSqWXlNCQQsCW,COUWQNkSAtigcDsRNNwlMEUCMRAiIYmU +fTLeDPwYMwNGgWuCeDBjdbEpVumgMQVm,MuBYPWzOgBkNBIgVhkrSynpHCBqouIob +tRQGZcXKiXJpCvRDScdFxUmrdShxsQcK,xbSJpMEIoaNSMFnAQMWQJASoWxhPjIqJ +wbsCqToIYOoLiNbzUlQAMVxuADQusqeX,VfakhbcCkArhfTVIsvvvRAtlEGZVurHg +dFoVCNWIUwGXYKgVeBxsoEJfJQOAvFlO,vzhqErqcOfJwPLuWYkqHlYYwTLGcltRt +lLppgopgLOOyulgwhNVpDwdjGXrvwrbV,ZOcqXLqyZwjjOmmLqPWUrskxchEuQFYA +MQBynhHPcrGrkZzgQDDmqMbuMAVOgHdK,uFXhnhaLIjMVGXnwnkmbqkjAUGcbLdkn +TKnRFgDaDRjfCjYNviYJcwrxlqPgGzyS,gIzbpOElBsCVfKDGCEfYWWSTFcsnSjBw +KXHwbjwOyvWTOzjAXLMzOoHCpHmbIVTA,SKVQCgpxdxXiJxwGVnreOMBuFqYnNpKP +OENNRDAjaKdpWLIQyakNuXmsHaYgVDKM,KOBCpdTbMrkfTgToNAlxANjgRyWItfsk +EEElPTCOijLkBQanjbpImGsTreuMmrgv,yDhQyQtNEymmVWxZNAaeCULlOvEwWmET +xJjZZzqVVQMrmNLtYLCpyvZgbYPeyrdf,ztsHScqObKYlDEwGVpZbGgzqKoxLFmjD +kDjWisySBOOYgYOzcDAkFxStOeogLSJb,pIqJBSFAjRfltNOstrEYGNSRodpxSsZJ +ySyUjqjTUJkibvrWFoyRGQTLMfthUmYq,NORBENwoiCYcoenHAsdZiGgRuGGyuOJT +JioWxLqZTFWXEBkflWaemxKHfMTWxqVk,CBqhTBQAswwAHSOjZgdckZzRkTfcaYdu +JEAQfxKogHRpUWDduYZRexTengBrIYqy,eSBSjYriysbQZzvlDxdvOOKjVknOlRIQ +TLKZsMvvSKSZFUoOmQVoiGqTTPsMKWLw,vLWvWfgkvhezXqXnbppZvxaNHMXFkIBQ +hRYbFRovduxAQKazhbLNIkPehmRjLxoQ,WRdrECrVZZBDrVbqizQBclwCwpJXsgVx +qkZnecfvzDRJyPxTlSaDsxcCwlaJSLtw,iFjCNnbPlMQViAzRwMHHDktdDfNycTPs +kSzRrqIUspiofpOyQUsySmpHtqiVnZSP,FNaAussksyBeiSrOXTWuhyZkjxlMveHM +mipHDvMteKOmoREXqxDtjvXFQbEtLWjy,FheLsAtFYsAyHuCTPWXaWwhLqnCifJAL +aRcFQppyAwlMwKECIvNMyIwYHgjclTzo,yUWmASMPFCwLbWVcUfZnJyhtUCzTGmkG +zewmNNmEoarAZFdzqaoChjZkpxevJqfY,PQVzXRNjhjUxqgiNowufvCrFIeiwwTzd +WFuysGLsacBnRhgSjrnwmNCDdkNDsZrb,BNsWrJptnJEcTLaDvnbHXkQEvDZspmET +AFsKIPyrKqFfpZMlMzMrUnEnbHUKEFpd,cVFRgcvAZUEbwbIVCxOrVaTiAMRaoEqo +GVOsYiKeEDGhFwDGFYaswCskvGLGxnzA,OoCLuJozWcySfjRHMJrYhFnZsCgJRMle +fQRjGKSxrNrKNZnKiOcYByoyeDfRFCnA,uCRWXITCEKskHGcmOhDgypHofOWiacbZ +qPhLFxXrsRpPFrphhVJkScThdNDkxLgN,oKrFWSiYBPAxclewBFypMNNLnoSXwQRi +CzQOXKbDhzWGjSQJhYLFOwcoqITUJjXc,gidoEOUwpdTpXtstCRpIruAzNDZZZWxL +fsNdHttmJuULHUiyqdZnXTDjopegxrPQ,MtKUjvmMAsXRcSEcILMMEQMgKNZNqkHu +vvnidfMgDVYmWfHLyPglrfngsOCpNhRn,XDYrVSjrAypaMTvUkoBJZcXsRuUwIofX +jOzqWCAJDEnChfvtFEMoilswNLSvixGJ,CjHMOdMCNSHqZDTfajgiLaZkvTFCLXyH +FardpKWMfpyXAlDNaRCrRbXoRFEkBVeL,JxivWzBIOaJlCHbXLkAdQGtaFhPlsniA +KUoVqPMzgoICZwgILLYgtHUdIaWOZiVS,JZXdtNZUbqnXaUygRbfOqIClRtOjeMsq +dFWLWLKsVgvqEkSCeKHvUnnpYPDKNbaH,iYhMGEiMgrHoMUDSfORXvkjUMdciGmIq +TWHdBGjBttWQWsByKBXFPUpzIuOyivaq,ScvzVFepvywGPSYhmyOCbTDFKqdeJWSz +FMUCIGWahwEcQsbrdNvtJGzJGPJNGvGr,uoqofOBoTaNyscnGWOtOpmnzApAUtwtV +yELCaDMsKXKGnnXGMPUOOQEhCsduwlYv,DVAuwkskRtwPsZlpKjeopoojmVhOzXJb +HwhkCPGnsEVfbHwpJaEPgmggDFCgOEyo,xmarHWgbLgUeZotjJOXaijhOKGYIgztw +jcesGjLmdbXQTMiBOSLqSGDEkDwwJeqL,ImaiwXhvlwzZjnMKwtNIGYQDkawmhXYC +KCtVtysPajWUDiACrxIhcjqqftxmYAcL,voCUMrgKMkRMUDQxHClEQaTfJpTyZQrl +NUauZkFPLkWfRUspyfUycgBmiCDQZLon,xgJjbuBmcQLhWuPHaXIPNthuHYahlUdu +txihfXLOOWYHyJdjiXRaocKzPtpyKiUt,WOYCwugcDfWTCHlEvqGgHqQaqoGPsJEB +OIzEeEWJlDsZMDltPYmuSWpMADtQbasd,gFCMIFDgOGeNRgZHQQBLtegrHvLivbWo +pVrlhLRQpmSMYDqzBXZWGLfsoQwVeeyq,rzaFMfWmfEpshdrBbZmMeAiMKCbLOuby +lwCMYKewDWazYOGqvPpyGUyoTnvbcxXl,imiKauWVpHrRHcxJsoTFeXnDFgnMjjsE +tmHIYdySMlUzuOeOrTrsxnyJcjXuSQHc,EuyPtNTCvfaxFoFdjZLFPmwKrcmHECbJ +oHdIBaOoCkedCFGdduvCvHOADkzCHiFj,fKJJNcBstQEsjAihGhZvZuyglVolNAwo +TexvKlCmHZNTqJEtPfBjcuMenxextEjs,sRzVekQVakrkMkjzwOLTPRSQVLWUAAZy +xIZzJPzFhyUzlFDhCsIIehMtnIEhoBSR,DRgmUNhPIHdfRMTyzgIAWchxsrtOOdtE +YXBHUGMstnTbTsHhWLVXnGyDYBdSZAYE,uyRNsSjYckoGODashzMtJwSYMZqJwotQ +aYemShrENkvaucJSgPvzvmkxMdaYaBqq,bbggQiQrlmPWYzlaxgZzNQAneVAYkFbY +YYKxiNfzKEpzlDsjrQWLzssyzPtfPRhq,bODyaEBernVdBoMOncRssgwEhZqLSzuc +LpZLCDCpAaWwcORAvngCszhEaAwSzPpx,HhqFEXklmYwvPqVbTZTDyQukLZcyDwUB +gGyePmlnwYfbnyzyBzGdgtHQKzWtnynJ,bQkeOLiRjGvzweMTVQtJMFSWRtbhwFhc +NKhOmuWIBPUXDDhIBZzqtOLlYmUKjRph,KzMIkxArPmcAFuVvzoUxmIzGfhsIJVez +LtbQyRqOZBsnpLZzVwoTiTPXnZviGbJU,AfjUNdAayHeEvSmSCwQTYLYilADvIVzj +SmQvGHXRIYikOiQQxaTBFWSwXdcJLUBm,wHCsKXgerxmWuPSAYpJGnyXGAfUnDBRb +kXgWYiXuLxreLFkIfgJNWHbkequgiCLX,KlHheoPHNlNKRBRNjGOFERsNqXRGwMBE +jRzmqTGLmPbjHJFNQVZuNlHPFsuiQzmN,sAWyCcsnAQcSVHpOqpxdnrdGIeylLWrk +PcbuVHQEDKyjOKlNFMVEtARZOuwwNfSF,EFVBjBXOGLOLvjYPHgKlsSbFtamCMqAO +MUvfjbDozxCVXLKdIAaUWzOInbBnhHzl,vrEMAhRgwcaIiprxWUCkdUuphvxumSJv +jRtxmxoUjvmPQKONYcfAXLqFUViWswPM,wxxzCUzvCvjzIVjdGLReFKkUmJKAwGjs +GWPWUDZFmnlyYLnfSoMcAxCMkRlWedVd,aaTRbIFYnlVeuNWkjonyZdolRFbtJHxs +LZclCrxHDZlpsniAVbbjOrHfjpsbKBbG,XQVzBSxRILTVGRecsuWeJVbUAtNlpnMn +uXLIMnZBnzobvqKVkHyVumYIbBFVnaow,gdzhGILINBCdrhuvxuEzgfSSayNgAjrM +wWzUHzcblSPoHDFiIkvjpXBmOCEUrQkX,zZVZogkNmxOWWPhyNtGmatbHRNQAIpST +nxZqRRxhomMMFFsifAokITvVZqOYjcCE,wnddckWuiHWYXhtQsUxPqTNUOoJWetrp +VLQdlFikyneAGRuRlEsIIolpDepLbMdW,CrybESHDUMOlcPniTAWhJjBHegVFJEJV +xXwBfvFBiwNQIjVKHbZZlOkRfhhPRyFU,fMSKhIePcHFtGUySXCkTaaxTfIOtIARl +fTEOJbeHNNVPswiAcpYdULWSjdKgVGVC,evabSSmRIrEsWmHbXMRNBzTQJqzLGnCJ +cJdJAXDvCzGFqJxfpqlEOjbksUOyOAbG,FBTGAalbrzOmNmWzCQHMZBWnnvcgRiyE +SIsfvWJosjocdYHKRmOFQrxysJLAtDiw,sFqCOsSaffTyLxcWjfLRmwejysrspndt +DFKdIvjEAPZqchVNVfGieehCZzuuBqEA,EVrjSetIAbBGazmuWtoLQIrrYCnABtuQ +bPjBgjzHzkDWlyHRcSpzjLVkWdJTKNpw,tkWWjCIsSsBjrtJOJUUeRKoDCsObNIWF +jEhEvPtlYxYoGRvPZktvDpouFMnlcRma,mWEivxyTKNfZTCHfNlRpnpJSDxetvSoK +DSwNLTGXDzXSQeDrWZSriXtRwWqlEfIU,bgqsvdWnJlLOnKvyYJHOhbSztrqaVVNr +hsmoJUKHTnxRBpxMIPNxLRqAhlyviriR,lWbPYKcZWpLAIADyLIsJaKVjLAUBJtwc +UFaGKGXpqHNhWqEDmBuXfyojKYOstaLg,CdPipqzTnbxyNfgHQLFbbnqHJrgBmAkO +HXROOvTtXaUgfiTRROTmYULSrtHTOzFG,uQNnzVQtnVjwJQkFiXyuhdWTlMAOEYGp +FTNBmJHCwqdYVIcYVNIRNAUtxEeqvyAk,UtWdQRqpFRcaGcCIyWvWtzsrKobNikes +kpuTxwnUAWBaVdPtVSjiynxCzPTCrHlW,RtFbTMIdFOBLqguFWjeFkNBDPGdJLxPz +sLAKDCwQeasFDKDPLAKXVdmaTpTFUUkp,rAETBrKgeNdnwImtoTvTbxtqqyMYtWLT +CrFQuDmFwFTrrSRlyqMAPHJcGSNiPRaM,cCygtZAMrTNZrScrXPbrrhBBxRHiYizy +MYfuhMMZkODYLKRumVFwgzziiQgSkrtl,pDtcSoovndmxWENddfvZDOngSYMElMMS +qDwjQNQzKxsZXBKKQPxILaRmNfgVLHKS,NdTXJxRPgOIDyXwlfctYLuGgdojcNwfz +VyJjsZvFUFSFlrOBScilhknSXAouxeHf,MNsALhGZfuCRufiudLTfUGrCDIzrTdmF +McoGwYjaimEvsbpqOvQAbcFZqpBosWhs,UKyXsGYBLJLuqZGvfpsCCqIzgeUiFGwC +FPnGHudivuBzRkWgvaeNcMyXaANyuQIw,HViEzaUvEUkZvcVFtgwOFlzYpXTPHXFZ +XHdOLXhXCQizJwuhJTeBvJeCMxsKEhlN,PPOmxnJAVoVLqEgtjfTJqlqdyCaUDVjT +ojBbNuwtMFAUFIMsOcOFGlmZTQsapqMT,BaSraVXZkbEIVhqYqCwfrStTxSmgIqlH +RGyGAFsYKySzDlAYFoCgDnpqkxchCkoX,ZpAwWoxDamMEsyrlwZwsZPzWFlBkIXyc +aLWzkPKHqVHdbgptFUFseMAHrcPKXpga,JAtNuEdymMudZiysdgfNaeoubFweSvgV +TPBBoMVWXBUMJwdqLlqcPKxSoLkzkyrh,ZRYUSKVAjOHNBIrwbPdKDgQJRhwoIxQF +ooJAUwudupsrbmbPYijxYEnBeCHwggSN,JGNJkZuSIFRYxqsDndcGzPZAbsyjveeo +rMcOQhZNgnJWfJDeqTPmGEyIrlSCuwcY,RoNRmqwXrKJuqYNhVlpQFgapuPqzmwIf +DRboGxErAfGKMDhlszlnJUwvlYAcgzwz,HQGhbiXVHddDgzyCFVSCVDEhsFIlTpAD +UZgTcKyKXSHivsOSPykhHMdYDCvSCbbA,gXfRjYyzZtkXrfkAhrsJXXPHjEkxGLFO +cSinJBHajNrIOhSpjOsncRocaAzKblPm,KNGePcpRssXzHpTKIEzKEOAsIISzymka +vfaAfidNOTxGCsMGVWhSOvlHyKZCYxoM,vfMqVYyOMnGUDkTUeibvNVweKsbzuINR +vodhfZmdOgrdAimcFJxcExnBUQpHpNQI,vFKvGJViHsIXrpOblUeFDkZZNbrMZNti +oTZPzZCBIleUSXzYwjhKZIXKeSfmYxZg,XmpVwMTphTnEQCAubDyAQEYVplBOYpvy +gHzrKrcPXYvWhYVQzOtNGCuOmtCXMBeZ,SSAinuJVjqWODtniBKHdZeyXtjKSidBN +HusazOdNuhlFrOhpsondURDqgcNagcvZ,QTlgkaeUmUfZjAFWWYOotoZNwtpxwSIF +TVYxIGbkiHgAIzEvzGeGkvRHAvXESLPm,pnDOvjZislrCGEzxFPIIgHsKElebZxFm +ZsiiRafFdGPZMHpfVBufOnCrXdYyHSlw,yYcUTbNftvjbiYxEFrJQGXVjcjWxgGqm +UFAQHyvqSatGcijEtsGOoNqaROZbOoQw,QsIQKyugzVLWkQtSAQljBKUSrOXxnyds +PlkBWjAftzUxWfRLSntuhMgRIXclvUed,rpXwlleKgEOeJjHgFZiHXwYCnnHtZAXZ +UKoAKQLeHeSuJRyQfiLGJVcvsYtxwzcb,prYNMoOYAZCsgjbxPPRZuQbLLaQDYvqa +yolFfiowdKsZhQgAmclDQdHYDqHuiQWt,DRmjJfIzQCYnIqLUSKgKTJixmamRZcJH +vhjVzaGAIorFnUjObEsbIUDSNMSBbBPI,LEaeyFtpCnGmvcHRvpWDyWLJbwlqeeIk +ZwcRHWaTRJIilNHzstjftrlKphQsHIRN,YXlqNsGfabALKxaXHomrjwpQPQrbUCch +XbSXHZhuiFAHXLCEPmMJZMDbDMxsERti,HgTckzRJjeRnauZyLPORnfYwNrIGjrhN +oMpwsSCgmTQGRmylWeDJVXTYGiICBuCT,FHWGTykeYlpVKORQHsosazkYvuPDiGTq +WXYgJkkDJNFjWcdkTUVWgUorKARgQQUP,cPcPInnMKWsVghrzxDadoGrchrdiWwas +gICeZtXdfagDmfFJnejSCYiExGvIntge,qpenoNmZABmUkOipdHxBEMIByjYStGZK +txgXzKVaEkqurxIEqglSpCieoJzBPFWs,jZvtFRYTgJvjDjTMAYOteWQjUyTxzxhx +KapQYFaHuILPftUzERMvVrvwwdWQBzrp,ZlRZHWAbbBTTDxodgosUfuNmknYmrNBO +taKlQFGrjnIhTlkVManeqVPaHuLiGuZl,SNMRItfmOIEzUyMvUvROXSOUPplZDFux +cbzJcIOXamkCpwsFZLKbJGoZFgtnEsio,byCOIUMrgyycqBUAybLvpVieOcPPLmzs +XcFChsutFQQkoEvglnMRSbOlQgCdiqpJ,qeQNdvugvkCKoUtXehqJMjnlwmjkRWoy +lOQfTFJGYRLqtXpjeVrkufGsBBAcZKCe,ORGWbhRyxlsJUSyOGlBXKxAuZrLxZEUh +yfESQIctcypeplFTMWdGBezjuomptIox,tIyJvVbEPHVmtNUIoqKqHRhoMxTXgcWf +eldjEWxxMlQmlLSaDKnNVESwmLPqUXEa,ecOBVMGhITOYWHdWsMCGxapTuWSuSdcJ +bTMiiPTYOYzCVFqqXESSGiTNxukIMNYv,VNDGCWeVJEaVDVhqvGpcbTcfUqYKqgHY +QBBhvWiVTKoMGlJfwoXyQgKhRUvCoDPH,KEVNOlpHpuLPjghNbrHBgxsaQCmCTYpT +onKfhjpkAhpwAWDuqQnBdyfkdxRSzLki,gCwGQPxxUzByYVJiEBbXIRVXEFfBKCyn +kqqTyJXXYdZFXjsoggNDBzPFLafSiZCO,TrBnbxwUZYhZvYeTlGjiALCamFudxHBz +AorkZyotWjwXOxQVcRIrwPToYgUFmBtB,fwdGYGbypDetZlwtpeoloYPubfBHkOZi +BHjmvmJOqBVMmetcSEXvfTsWEDAchokF,vAUEovcnudxfADuDBayXVAMiKneiDUft +axEqaUcYRFrnrdeAaOTlHbrxvJIFAIPe,GPwHBgoQnBiaxLeGJjpGNqUOzsFSjXqy +ccHkSUwyfjSCBdJrAXFoRpUnIhjiiPcL,egZvhwIUbOnoVqZBDVVRKxQRAJLjFJZV +mapgBAPgzRThjDUqCYGFgfLxGQdpbbjQ,SrGaVcTAzwfVHfMpleuVcMxXkjTRygRP +OlcZjjiMOlGfmGGGMGdHfLHSXtNnluqT,RIkrCweHlsBhSoNruupGTRUyQeUWKBie +hZidBVUnhWwoqncjzUbfTrExueEJoHCI,DhRiLKwNZRbYGXXHdampPbBRKkDDqXlC +UinqnpvybEaNRBKhFVLeEUiDthIHexbb,VlwsduDSnQnnXSNQBHkVURelyMeNfjPB +jKJMUnLAxkpezGPkxBzUhPCCSpaUPNzY,nUSmosdOWEHnzJlqCVuXfjQwHiJmxGOZ +SSqZGieVQBxfySuionQDBhqDzlwTwxmT,EmyAldzHgCyAlKeLTvcNGcHtRKSYYWbe +ZcgoVzSLdxAcQDvPSGBsYcFdrAzdVFOt,VfzFlVzglHRlPUHkgZEcuthdZKtxecfr +gXXdKxioWTHJryJddJdeSIQyiYztpdEz,HswFhUBvGuRhYyWSoQVCdFOOvmAaiSto +YxdPoKMJBVANdrZzIdkYptWVtwAiWKfG,vehDDVhRTyztcpPPxQLnhYKXCzIxApxE +hXJxGbCVovZCDyToFKOSRvkxyhMHToGx,rMzobOToHkxXqBkXvaslPmpvNyYOGpqb +WklEGuXKxaqdalHdwOzQxLVZVoNczIOv,yAbRBgKtAcBudJZTJWFkwOEQXhjfwPHV +IovsOWPZlUxLWdjMLYlHqNVqIYhWsAou,IsXkxssEHGgfRlMNzcuJUvcLerRCRVXd +colopnNQqDWtbZjuYjyapXzJpTpTZHIh,HbXgwTyeHhriPPwMPnBLogsNsGToOnmG +HgFyLXsjbbnHpzmWVnnOOXfTuFHUUFMM,UqiAnEyJUauICQpYCYiXSWZkMbToEUsK +ZJiqlKThntBfRijfbDuJJbtfmOpGrsbf,JvBfthXGMmZdnMLSjaUJAihmApevcxlG +niEzNCVxHnAHubItmhLPITEWkjhvvAxz,SzTAMLguCjuicQocItEvlXxAMXGIEmfD +XRzhcRGfnCiRUMRVOrIFMhzxKzSsjQIN,bVnhmbrBjiidywnrWzMNcrxbjDjDbzTA +KWqhLqYkYnixJkPglLcpmsurVFNyEgon,lQgyqklYgshLptOfTGxYQKAxlcwxxtIO +etBbyBUzAqLQGxXBQWlFXmHQtSPpImRU,WufSVkfHnNxZOPxDzVPcidtqouxMkZiM +HDpAQglPGbGsTlfDdksykxuXYmEnIgXe,addNUTpoCNVUTBhPhHgqxlvocUOyJibm +OkiOIIwClQosHogdKZweFHIOQdOhvDIr,sIYcThltVSTFTuqkGKjULVYFLCfBlzkj +XFBfpqkSjPGzoQPjUjraGLQzjdKlylzN,DUfKrqRRJRBHGwKAUNNwDEKsaQyTqdgW +DDVsNpoqsdydvcgAiMSzMqZSjnbzXQRK,EzOjpmYwHgHxlsqweylbUxrBBOrlNyQM +ZojPYxjKetBGYBsARJWYQyCRxOkxyoOk,PxpMNsTeXeQuGQPsPVTsGNcjCKooqxUy +mIeVHgjxbZpXlVSTLWBwhbXZxggLiXsE,nFxahfnEMtLOniNFmHVGFyJscAfXNSRK +YKDPKIatJuTdyLQKrKJLYyNMABbsOCLJ,WRAYFAwdvmzQGSoQBkzDJcYKsHPmnJvY +PWmRctvgSyIvAtwpJWMsvKTeBbegfIrW,HarlyuByYLxPAoBOCtrvMkXKFDhUBkfR +ZmQpbetFbDVGNERhMkmcwxnxfNeCzJfS,ZUWyDszwTxFRdnloSLitcyJXvQuzSlOa +OyQStIuClqTTrXiAJTdfrnlIKHNgdJmO,DpMfUXjdBYJYoHGVASxJEQsxQOSQGWbh +mPaHHGTEOCfEbWpXQOurSaWuYNuHokfk,WDhlqsxxpSfiqLEooVNrfhBFtmduUyPX +qSkblbKLKpmMhhCCOjycArSgulPwlsbD,uimkcJQGCRoWYZezzQjwegGAsmDCLCMz +LKhVXMQHHsjiKdyjwefuLMnUpbgEwPZu,GsjbwstbqZKoVcKEgVXevkGiHsxqmCAa +krWCnANpznMyHFiALqgrAXbIUitqzDlS,RelrPcHZeAPpfNkziAedMiToiDXVaiiF +nyvbgCKYDWhEtCgEiSRaXDnokoCWqqNl,QeFpmYRWHyULFCCnBWwCKChtTVBmwFys +NNPknaFDkMhSAapksEpobcHNgKtmWkrw,oYGYWnSciaYKXvhsARQPuTRFvcxvLCzl +yiCLfBEoXjnYLYJKvsNCCAYuXcWEsmMJ,KiPgazRsYUmvNaIfUqlgXRzXglWYaCjk +kuoAzMKzmGtOMCfOyurgedqVwjDUbnaM,NkCmcPDAvXMrqqgMmhpFnyOFCRUQQtmE +FJlwVVTVYOUpTHfDtuZwlDWURxgHEkEL,FbLYCdgLiUaiQQEDSBcEyszCjeLATQsK +vCaQTGSpMvjvPTlgGGJIlsJCMIBGupKP,bVivHcfFoSixRoNmJGhBFaNRAdZVBpsw +rEyckZcPUZheXhGETOfCQZLtCxEmZOgF,WdAkcLCdFrvzhQxgQWIZicGcelecFbgI +rynzmpigyTmMvUMcndzMzkWwoVlpiHsI,aGehPhtQuIXFCuaKzboKqferqOUZPrcv +knqqGGvItnWvPaUgzrfnkDMUFEjCvFUs,UmvDPrBXUwVyhymPzuDsJADKPQxxHeIW +xXelcLzeUwcPJjvHWymkNgRqlRoymukU,AbeHgMjLqehNKvTheohpYzGGYJIEjVxN +RpHrfuhsZuVGRQAjBhrPLhHMOUeIEwZm,WVzYvpllnHtODuxjasBcdZJDXdeTwPrH +FlfAKUwpHUEYLZLIhQSaSjnvEheHGVJP,mIysbTbiNtAkwVrvEjwVXnOzMzqTGecx +KvroLaTQoTmDdtcabrrWwAiMuOgFqsFE,eAeKCmcugoEVKLLVhGlJqeUrRSukxuBS +zyrVoLOMEOOypsxnMOTuvOjgtUSNKbNI,xZyyibPUKPzGyxEqWcvrcyatmASLvWAO +XqHVHckfaxarqtthfegIqGoCTBBIPxlF,neaouJAxujiWNVItyyBaojGcYkBaIywN +CXamPCgQXuUtoOcfxeVlADYlpIWuZqWu,nBIWyiOBcASdLDmiEYTGaCqgekXoNsEH +XcSVIFfpRwBXySVVePxhuBLiTuUQtMSE,YaGuZWPJEYGtCMDLTAXbiuHsCwItwRAV +PIxbWIgvZqKuuXPldvURTGEzscjNbzkL,lpriSNyrXACADmZUlSQNIsHByxQqiybS +zkjVUEUslYFkPmspZiQUFECUOzwhKGJL,vBxQLKcyGnBuUWUcUYnhvfFDyZagOgaE +KmYkkyAPCbASggYOMUhQypmgzFjjYHAS,NnZgFumreKMTqGOVFbVxNlVJuyvpltIP +esoNfVCbgnStoijKdClAcLqGqxYDZZfS,WnlTPdkHtCCutlAROvjYyEhueKVvNyWD +IcwzKcsBwpQcFhffQYfXaUDCNfUlbBzO,wcytwdXJiAIUdlsjYApXGHvHnbXMHWci +DmjmdiVttCfhvpQrnORMGuFOKLrdkawQ,MvxQTUZKSJmuVkVByTPsEZKCqmqMmXWm +SeRjophnMexrlskiBHHIiBYenePGPtel,NMUrbsRzsXbDugXAIoFMSakOdzAudQwE +SOhqVctEEaQtziGzqYdwIogQgmAwiqnu,izwxRuBhTdCKlRdOMJsmWrXUMaMRDObF +yCFrEFQfDahhdPocgyiMpGRiPWjEUmSO,jgrLpFJyqLhvtobYbFBxGzROwjsOvRwz +mwgWRJEERPWhnADkkqbkQTNPAOnFzgxK,PlKWUweoOMgHnZlOKglyPBgpKTWRiDnh +sACRwEDZiVsezPkNmouypFikpLIOLpkp,omfBZtWXkhypQYzdubmeQYGzeWQwDPdh +mbLluHnGWLwvmsgcJeSKwwywhjDlnKjc,qtkHhrwzUohgxhxKOEaEMNsiduHKdxsD +IKGjQotnNimztoTVNObBKVZuNhhgTnuy,nflehMCPcYmsKNtTIJECueBdAAANUggB +YVvYbshizylrZurePXFvnFgdGmnYwPaY,esjrgavWwIfgscahVWabdHXuznyuxUXr +ASELVezvNKlXjNdiKSvokMRkSUmjGaXG,CzrWMamSwVnzRcyJLHyzcPHLoioQgYBM +qoUQouNLTIJOvdWgYOaAoPcAImoaccTl,JhBooFVNkLfhksXwXxEFVlifrtRiRznz +mXHGGuUIqeczIPJcrZpYQyzyaxZpEKZW,IWAKOnqDoZjVFxJlFTOkJxqIvCDXdKxw +bIxefMYJItNNoNGUaEiuhdogRkTDDAfJ,nDrcTjpdFlzixFuEVQzyimQiLrMTUjby +uTlFxAELFrQSgPUUzxKddqycqOXTKOUZ,ANLIqEbLGQSxSfgtPHpVHbHSRISLYyiO +sFbMsLumlODoIhMVeauNJGAPHFCWxsCM,vdrwKgMVphmydZZEjNBnuzxVCssCAwCU +yTufFlZUqZYRDFRXmaLihBLuAmYQDaVm,FSgHDfsxDqcJlkgqJIMttUOcMQdKwArB +dLHHfTKVEYBFMIDaXDABFVLOutthRbTP,gOZvGBUKrRCqRCAmLAtPFafmkAWbjDCj +XBOqMZvRaXiCMRrvyaqBiOmdQOYZhYbV,FhMfefYLoEJqJqyPUzwHLpNgzKBaWeiW +CQdAYvzDCXKjvOifMlddNAUJxeZsVhio,rDukugcUarKfnyXpnVfAvjdqqbfByrYY +qlGTILaUDlyYPaYkSQRKRXfAoCtpKnZJ,IYxEHFfWxdUvOnCfhxWTXhGiUZurpoQv +nPvjGyFwqQYpkVclmxcsSGJxDSgRFptQ,HegSZqCqpTNLIwYTFESYmURoSkrLTBQF +yoExeRKkEdGMDVZDxvswzyLKOoUrdBXe,RhsQJQbFUhVLyiXebMeQKHERPWMUBwsr +dvaprKySApTbHekIOeSZgmZgfXyAtVQm,hqIoyXPguqQPaiJFGugboalstWILfWPm +lOPchEgkoWkRYKToWBAxugONjumKWgcj,LKTeTllIevQVMWXFCRpnDFNiWEJaAXFS +AoKejHBeOlNPUKXSBtjPjIRhGKVMEDVO,eJebYvRJGrhurBvCwmPIDzCPMlpBoSlx +WeWnEQavnQqDCeaOkBeRwlPsopZDOaUD,uRayRNDinLyYcOQjvmpOjWNWIUbECXoW +CcxkkXQLaKeKwKwaOGnOumPuWhZAMdyO,wXaVVFSTkRwcDHiUVRfUgZYzGqEsgTLc +aNzlWOQHfomFSZWIwWaCainiCTADHRNn,kftXuLGfzcvWHsZDrORNayivcgcbQLSo +msGSrUwUIFwRBgRqbsEoPoNhlggvrazj,itVijiHJMJNsyFzGxEdppLlckxiCTFdU +upzDKINcBgQlvEHwOOMkvBdiwwiExQkL,AaZQzjqKCWkFejNytjcORaPsKgnWUjbC +CnxZosfrCNOQjJRrGIWgksisDSlLnllZ,tzYULIhgrFKpqGXtMIlDCJmEVjrVxDip +amzttIMNbdjCWfCxSWuABvfkzSbXYWdy,XcxDyIviooxQddJYcFYjJlNtmUuxEoQO +wbhrMReMGqSoBbXDmNxpjeuDzEXkNAqQ,YQitQQKfIbmKVEUAqWJAoDWTeXGMrOMu +itZTcECxveYBeaYdXFeFUCAMeKbXTNZz,aCjrVsypRuVkWcjyRBbJaGWgbzJtyqTz +wYQMIIzXFJnMPWeSvcQYhabyjWdronYK,xhcRVguQxAcngDkFivBUqHGhQuTVEzFE +ISbxhmEGyKyJMIvcDJvGoIsFHwvPjClO,rTjrDMTyAltgWDhThWDMTlkfdxLnVrSw +wHIdPBeTapiXcQgQRXNpMRRkaBOZYvLB,lMXduYdQfPjalpoghTWXsbvtVBMQhOfO +ftPqgnfEoteqIgrzPYqHXkfRwGXnAFdd,wgkidEGwfchloSBgFpEvqVyPvZgsArgg +bNZLlKCOZxohwpbdCDkAYiaUuuzRPUFG,CVWMFctakfwdtGVlGqXansEKEYlByLlK +mmZOGPQMFKTedFneFxQCEhKaMiGoQybH,GUHBgRwEqSjWgfDaUKAJnnYQUITYzSRy +HBhiSZOQqHBnLzMcyTMvCirJRWcjuqin,hOYWSEzURmIESWSgHWIqeyDBTFPwpDvm +sDRbxVgNAMdLUCcFlmTcMUaVEKkbpMnD,SACWnjeVwDKdmQtTvkqLErecvMUOTzQX +CqjdoKbyYwRZbphcaImBYZHSHFaMspaZ,qrztefuwCmqWDDYulCkScXBKbTaOGOXC +XrIfghqPTjqxGSwdrCmicKumtnQihIjs,poDHaKlNJdUVycGAIMpTKYgCIOVJxuAE +PsWFHKPrRnhjtcLcukdoSIilUGuJiVLg,NNKRIzGnadXvCZCKcQZEYwocLjpPkxVc +QGemdDwDVpqGreAIVGRdBdkNiJkfcUJS,KDkfvJbkVRhbkGAHchxBKLPTfjNTIOgI +SSizqWuFlhJkWpUcxZhbPDLDxriEPSXv,jONlnewxksaqefiNRjjnthJZTEGhUJnS +vzgjWEZBfHcYqoCDrrtNHARPEVQFwpCt,yUwZlxfwzLSukyKHMZhVqLZYXBjLYGRW +XgRkDmGsCjaAqOrIvReIKaNKsLlirBZp,ZojNiTVACuyKBXNaWIzEyiPVhREflJJN +zCndPvBEqjqzTeghfxJPYJUoQRQeGnbU,lsROOahOjNCEbFxNMBSLdlMOGPYsqTmB +kygUJgiRPWAJAGMjVOSoADilOTAtxJQX,ENreBtUzdjVlDRFeJLTjLZopWBtqHtvB +tXqqOSjbDkumReQbSvsPYTGDLtnyhnYG,avJmmcTdrBRxtgfHBqYoNUslefoYuksU +UcmxoUAdlqudnEyHVfGsNJXgqBvMztlE,OIRLgkkaKZmgilWPhEdLxIGNjgtYJyda +WkRbCeZNPGIDRGPBgVrUaOxFLWICkRVW,qtBBFekKvLLPWkrnosAzkPhvTLyaCXKM +YLiCChbLlNDdczCnLcSiUmHmBQabYXYC,hJypDStnuHmsZmcFBUWxCToBfFWWlKuY +sngLCdHitjoDkLgRqweKZRbmVTannfpb,wxamRUStMoKcMNgOvvjacMcRGpAyzabo +fzeToWtItTCkfjCJaJjSSQtvANrkvkDr,SsTLZURMXqDVRytTiyXVUBMGuuDfRuHr +xluPluladnrTxjcRoMxKzLtTvFlrtvaZ,JIdiyXwBvYKUToJLXLlcjxXvzCFesQjq +WxdkHXsLCjONCANICpzPePGgZjVuQuSo,gYZnWHCMXTDFtKYfWlqVdhCzENhkRAxD +ABTlgeaIGnhNCSAMlzZAljwhePPeAqAr,RLotatliZZmtyuuwRLMminfAMgjUjADi +CQPdWpwOcDGAkjjbkdJmsHkglkbVMYxP,SZpXNVSgWMlMgnQTNYoVWDQyefMrsRDH +hurxWHiqtbYKsvLElokxnAigsQpaztcN,GmOGFyhaVjBbZRukBCqsRLqWpnmKIVRo +OLZHHryFAeYyLzlrpxjfuukyqrexQsdA,aVNcXMlibXckPvEhXeyBgOAaJdwgMgrq +zMAaelmLBVHdqSgTBDtJRKLWllbFxKzA,AcgDQoTzOahggkFJkhwEUfSsnZiMpfkr +XCYVpTJtVwFSxNHycKnEcePXIwlPMVxC,QjNGnTxUcVihxgEVEeguNeGmdKcWJYgT +mQuJYrmHwnzFfRXnMTVKepZbOITBJAdD,NtwNxgjoKuUVRkQQDElcLjEeCwYQMMaZ +kcxlZOErpAUSnWFkpRfRxcbLWyfuzsQT,SLpmejPniGwdpqewNqnkqVKaYWkgkWAU +cPDjqFKATkxOovBiIwyoUOgCPeWwkERj,EUuUWHlxWNenrBuBiwaFowTfHQOSMFAj +pBGPebIWgHgLJuXcQcFXOpZOCHJcYcIe,XacxKborXUnTfbNRFauHAuSPQicndpOA +uzmfmAnzCtowtSjDaVwXuFgrTdNylDcV,AIbThvsnQYRtitOeBRNnzaZbpbFtjGrs +gMrAXclEsaEiLBkJKNAUYnzDlHszxizb,GAnYRpnwarsUuSXzJihcUoUhFgLImofW +sqKLYjUlNOLJYjkKNNruvtdHgsFyOmTb,XPPzPLxRTxjZuPbcxuEFMnQfnrNMMEsh +GHLrimPOLxIffRSSwuVNyanMDYilPQAH,tpbzDobTQcwwKwvRzeAmpuufneqLrSqY +LZQkpUBNhmjAkOFBbQuDnwjhnCGHTsch,HeYAQebGSEzfgVJopWZVoTxNsFjQOvLe +QmuCzjonbxIjMYzEFDXsOMCcjeSaTvUy,vnjsmIWDVCMBMetGtlllDroTNMbYhhUG +RuYOpmGyQBpQEQKFwptLJQZolwznSfus,BZSgtZgUULFXcqgQRVYWYMOycGLuZszh +EgLUOFoLMvpblqsJABCaovLSIScGhhYj,jUpBGEypIRAVOOSgAJUbMYizHPvbkvQk +IzHWgKWlrPIMdWBzcNkKgicNiYESAUbS,kiQHFBSkppsefJIgInVDlbIDNDAtxDRI +UXYUjYoODEXssuYHsPzzoiDBnjJHogTZ,VSdxoiVihDkFkVmvCVMXquUTLXDytrTy +ZjPFawMIiuXUMhUThkqZJySSjfBeSqFb,DGEmJnFQKeMIPjpxokePFispBUqMNcXg +JTjShLxJOegKoTmSlSFmAehihyeWmhBO,KFWcdUNoghHHSZeuempFDKZqHTsIGfgk +EPsKPzJzzRgxANaKgRldTUwSsxHpxxiR,qtwzvGOFOXpmEdSsHAXhARNBGZGiPZfL +PfKFcwnKOzdHzCdegcwZmwgdktpeKTmi,CuIcqacrOiSsQceVkxmumfsJALRXtOrl +NbQNRueqQWYIZzoltNOQcnZmqRFeaOLN,kQLdqwXkxGozNeStDXRBXwgcoaKRkWqk +mamNzFLXcaHIVIueVvlfEKuxvuGgYYQx,VNQYDRTPMoEVPOEBrzDLpreHHSdPaSNz +HQWyemPsphVLXyRrVJHhNVKtmnWILkJI,cFzjDGZWzMheDHUezeypXYIWHBTChuGA +snGoAcuQqxyjrIqyDVNTILAKQCBPMxVv,LughLDFRGRMxaJgqJQGaSBfiRRXfOUpi +BVIaZxgSZZsJRdLpERYqkHEDhBqKRTuO,ESBzmZJNCnpAXVcJTzUIMMqgjBamAljh +jMMTDbIWLnAsHGDCDNAYdRhXRiAXpVHw,jHJnKrRyxwrbuaBhcLeqENXnQIYNQKDL +unhSkVYYqVMCFcafKRTqsxzeoYJwwQuP,afBkUnntqqiOvpgbdELgfGMzfEQccgHL +JNwvlqVzkbDFBSVWLEODdPEnQuSCfDdI,iiKrBhugfrbHrsluuwwNdRmtaCjHcioI +akovDXfjiqkVaFLEVmwfjilhXwbQRSbq,YaPSntApTkmiUHkKsXoKDSsaVQMugEvV +HJjzWFUoqLoyLyVMgdlguhanJOpOBitK,sdJZXbEmpMVBgMqFqDwOnbEXKXsSLUbH +woyUReAIHdEyvenFqzOADxkCtdrNMnxM,INfrqWiPOAJyLxLHjHSDQBpavHmXuhgy +GrEKGFuxsFpiOzMFeNVnEVjPwKVoiLXg,iDCWPPiAFShtqHcqBbBUipTMKLWYyOIK +ngicpEwGzRpyNSSERcPSlbNQZqyPfnKF,aUeTLNFmeMECaDDBdomyFlfSbMuAwFEW +sXCjXWIpPUMPYQfzUDRodvqPDwfWBvQw,ruxPQZWhqexMlOGsnqmMSzFaYZIeWWLQ +sHkZBQnQIjQczSvpHmaOUWBSLSvFRhjR,SCNLbrUgKQfFVEKrNXheJEXYanKknEhS +IadHsgIMZxRZMvAQCOCZXoZMAvAHnqyg,dCzurBxkejqBWFgIgnMPTUHrrgfeljCX +jeQtRdDMMHvHTSPbtVwLxIkvsodOXYIF,slDUFXAZpEzgpthtLVtDsqUFxtibbrex +XnlgUyJLLAAqyiBXgztDpIctWxZEmlOZ,nJSrpZtNTEaQQYBDbCUBlkUYkKTQnQeM +zcFbEuYpxfgVzDnBTuALgqZTBNsIcQjU,zeGuojluxyvgJjwJGfCMLAaYlIuUuoPV +OlPdwyCVjgHnOZbVQxyeSYtixFUeYJwZ,JTdOcNAuNdnpIMugiTSXKwUQkERkiARd +dilXtkozHQTgtwajMqEqxqArcoupWyfa,qwNxHWIYXLIYqGFnXFsLXKsqLtscGzVp +WTrDNpsBtsxQSQJVgjyQAKIGvoEZJRQs,KTsXDlMdOmTzpdTtBCsvKnSJIHXmNHDK +gwEotEulfbwhLweqViicxqPNSOluJVDC,JAgBKpznpSAneAJPvWIlNCaKhpAaoLvp +KCvmdXhBJzrxIpEltRCJhLRdHaIQxAFJ,ZgyawRabSbzLdPVrDcQjdcSqNXOXfwOj +TXGonWGAeBsRYPtwnlXFdSnImtjdUWoD,cbcUbRfLUEPznMOKmSNDlWRdyYwPpSMT +CCXklcFyhQTOnjPSquKPJURqBgITcBlp,rGCvIHPnmlSqiMRIXJUCGWOXCUBtYUNt +GdPCgnGtpjdAVVqKztVqygPDFlvrrIyK,PsfmCZVobkJTLFKDIzOdKcOALeVxCyEG +QmJEgKArYMIaDKkNChDGfKwQozJvvBfd,loZlwqVHnmgGxuCRDPIaLIyikqgTbjVy +UecqeWpoabJztBsQwpcygCCVZeqxsGsh,zJPgjIcgPhKAwQroSLBfFMCRpFKronhO +kehBqQTZCCyvAqZoHFWLzBYPGUJNBexS,mEJkzsyDevJsQtoxhvwoGwBHfUnGjrRE +XPFnoorgMTWCEFiRCokySuTLSpybTCnD,KsrSNRoBulhtyrDFCSepvjwDODdIWUdR +ucrKAHoVuXccbQpstivWeCclvpuRnPqk,WKYGgZlqBEzNmqRZWCXpMMEeLntJcYlb +hHKGRozYbzIeQzSGDEzlVErydgQLuBMb,OgOHgHyGbSaxWUScmMGRYXzYOILonmnF +HOVpkveVVZBStQSXppTrehoPYtTaoHll,PfiqSBzJpiQGaqkdzStGLogOHmBRnTAo +SXpdhpcHSlWIJUoBJPrGazgNsIKvrzpi,LLRJOSbDnPnZPGbXEUhKqJHssXhUJkgd +tWvfFlWNiSzStcOtjDgtuYjmCqntzrgB,lMZXttnVUgUAuzFuYjoloaMQfarDeGqT +hrCnkZxoHQJbUYGPheivtkHwzBEIxjzd,oJIzoDebwvEylByLJSCerJTeVPjrBshI +hRHIFRTHhBhEtVKseCqbuZBSWdqJbDca,KaIyHmBUPkDmIrjZyKyRMaqSEadLkrIY +zITyKmuKCoTFvfuOETvvMrqbmlyRJSeV,cpXSWsCMZOfMJsmXrxEPDKVUSGzBARSZ +lQrNknBDQCgTxWYIzphxxhQPgvxbIVJG,wKyPxZqlCOzTtBNmUabZXuGFhzeAFOsg +lqHFJfiRSwQVbirSBvcUaowLpfAoPTya,nQjpxFKTaEohIbbAFcFMdLZmbLzRVMMI +FuijzkibELfefqMezACrYdMPGEbOcKpK,mMTTvBnifTYcHtvjvBXyGxvqccyTKyPS +xEuHGLYKPEjkIXTcPgZKQGHsQfyIDAZF,xeCrhucKXkCefPszFFetCPAESRKIcftT +kYNtFHRLVqbTiFEkQqxIaoEegxiFtaRB,igCJgqQZrJWiOzhNBztoyItNubHGyhbt +orcEwJFKiDjbMpqCxPNCUOOXZFWsajpX,bkneQzxqrHgBKSZnzSLXEkgJaAGoGKau +YJnwxoYmfTNmHDjiGgcJAQiRSjJYZGEO,xUjicJXFskTpODmhivzuuNkajZamTshf +pRGsrfrfAvzULHXodLNLJXZOINEJmfKS,BhaqieHIpyQLBeKZveEUTIzoQiVgjAqJ +LdPNhFmCCLaImqwLuRcmplLgopefaxav,VOozsiNwhFYMzzuizIrNPNnAXzrguOBB +AUYNjDxWrArBvDVQJczJbhaJkGzmLEoJ,vKpakqAudkXQOASgunnrYeODnfuUOveR +bYPElZSVGREdcxPRmPorRzPSzVdZjFLf,bVBmJptZiIlvjTPPcRmDErFYedmVAAzE +sCNzOHzAClZAUIEhHjxJDptpgfVqbbpQ,FDZUxdFgCAzkkYVPezItJMhEnRPXEXGK +cTSptGeFdtKzmSmsKgPXUpbcrVytXLkD,MKNNbDrDQsorlhyWCjRxaipvGcPFWEYq +WTNMrNWzZREzKhJUCYfPyrGCfbzKJkqk,tWTrYjiNhOPccylSJTVCXnHIfItPcFJG +ojiaxbEEdVMAzzzzNGjpuKcZJbeFDzrl,DlZtZXXHoaDLyRSOuBqqeLTTmxrPGzgc +DgrLTbuOhTjLCRxLvYeUdZTjaKQtLZII,NbwPAPjTsUBskenGqPbYhuMeQFlWmWYq +HDIuTybMORueByTMPXcjumpHXuTSjbKI,JItVYsKWmJxhWOUogbjcmsVwKknvZcPr +kipZqEDWZMOPtBBfUfJGqzIHuEbUqDEy,EddrTBqpGpHkzUydvKFqSRFACGdbpdui +oQgoDfgRkgypkCaMPAVAULMzfhpfjatu,WJOJNPckVJYcGCzFkARIxWPxEpByAAZX +NQDNeNqnIRVCqDKsGjeUjEsxmlrlTcCb,AnjxeCSTcSGmoUkkkBbPlenPpLmBahXo +RuPtifsTgLwBHwLgwOerVvFlbsOBXgEF,mtHSYQNpcyjjetNPAjnfeOWJYWZOZuFE +bdrqHtZaJUflCNaTHyQVMqtiVZOTivgG,CatHeksLqEVwaQemRAikQPjOabIqPyos +rqWlbAGQOwyIbORUdKUfLmhcJlEwNbxh,JIDBZgwNeYABfjZELpeqFLChkCLCtTst +GnJTvROWpVNHbNjaCGWDsCKQNGHaqSEN,SgZPPYqSbrcRcdGAjnUTNykyedmBKmex +OZVdaHIVcXihFYRtEvarikrNsteBSfuq,myulYwTxAJRsSJydnyphVsSjVywZHoZS +brlrGFJjVzxrjOjqrFACUJIIsXZqRWQK,qoHXqyNnMuvhwpjVSLEjfLWAEbOkSfNB +trnrxYEaZzmmkFpsYJGyasKDFVdXjmKU,lTCSDtCWgBagKGXxiXWismyEEsWGcDbs +gqPJfNmdGUZXPwXjKxnNHclXxchciVJc,XvPbQERsvlzHwnsGzLOjYFybOfqTtzhn +mqJAMfFqaDVGzsZUhFguFKoGWSdhXCXU,qvsTjDunhEqAuxWCdAzOurdpXLfJukFA +zDpNIslYrDMMknTpxouHqsNFOgtTRzzg,psusCsDDsEqbuzWMxsHUHxRpYDSwzRzL +PKMbfVsFsySAcgZhOVPfxWmaBzegcugy,CYlmTnUPVBjEBwcAimzjJWFAYsCSosci +bmTlajnfbfhLMCGlPefahmbtcgMHtxVh,bUpZDXsebmGAahdoAGFrdIyFKVScQHVJ +bZkAKDCgMDzNSQqnhTcTpDeNdNZuOHRb,StQjyzXHrRNQWiZpwmiXoLVKVYnCTxwi +dxtnUtPXgRveUYbLYTTkpsctcrBtsdMg,dLSlphgGDCCrIlJgIGKPSNaklnQKNQCi +oMAslyTewMMYcLMXfceMZALYPHtMHmDe,DFAPMNogiVyWYGUHEfwwKNmOnsgsgUjQ +RybMJvUlHGTjxkQjWWTiHRIbJBHkAWjj,fGIyvqCIcGhXYKMIMGugBeJtygdVdvqJ +XiWuOPpwVNleHmWXfolVtJXGDwFqTSWF,OpPXIVWCtPSIbeihlcKAdhKwkvGbLjPf +WrLZXLtDVzrGbKWffvXYXfunbpEtVLpc,AmkLfYAYMnimLHyVidhsDKINZeeSjWzL +FvkowLAMDIfUBAvfmhxFzqyyvuVlmFlD,OGCfllQHcUYwXxLvoFWCBfgMFkhmYxdn +SnPRRKpEyOaWcLidbqIvDTklzNjwDCJt,OajeUTWXhlaVSuuFLRltDzJbAtkHSzmG +HIoFktxCajjDVpUyWTpZzeDboLBldfPz,hqTOKWJPvWZvHeJvHaCtzfNvnTgJVmVa +jGXbJnDjASinyJaURsFORjvytLHNUYER,KDeFagqGYwGOXvjtKuhoVwgYVrjUDMZr +oGIbREQyrELreRXUTwcaJrJnlENiIjeZ,uaJKVofFSENlHWqSBLkVtPqRuCfontMW +yuheVZuxuduezEgvSHjwAWHuCZtlbZGl,LvQKTfoLkwKVPiAVOfyhHAKzbJgHaYlz +hZHbXTPWMaHXOAMCHEOaGpqcoEkVVcLO,GJuiMSshwLMhCsryhfRZWrOmuqDYIMHa +JOlHZtrjapqFySgKCQMXnQzRBVVjCCyL,fZBpCbExvgXhhEOJoGYFmmBntVTrNvBq +zcsJZwjpUlDzoxDSrEttVHyRizBTErbj,fhLLPIbRGpkMBzKGTTxKIjHOYPpUQoyT +hYddaNvHVbLiOvYAMsAPGtjzRoVRfbhw,PifWXQLrmZQKaQLxZAKRLfWqWJYcqTgA +pRrySHRyMpmeDBdUZLdacIMnDjKJlWjQ,kaXJxJxfNfDqxeAgxzGsVGzZfLFouVSL +kFFdprazveYhXKkCmlWGDPyesHdfxUVR,bYlCxRBxwPMxcOykvRlCiguaSASpvwPs +YEbfBbnVXtVMHIezegupKcFGRiqYPokc,vetHFJIxuQkBRBIwsZKZWIhDSfDmfBDN +axZfrOTKYJyIGKvmrvXMpSrdXIeYNWHY,bBLjfwcifDzzGXkVBEcRjyadrBDIKUHe +LliUVnxHhQemwHrHROMOcCGiIXGfsMTp,RDASRJFQlvbcaiebZOEmXoUGSsbJGijU +PQrtUyowjqrCrEmXibjoRUZdiCdJKsGr,XnErGQxoxfguOuFBKQpzpzISenbWCHsP +knItMwIkehJOdyfyTDfdVbLQBzigWlce,PXTvfgVLmkRkHTjcuQjZaiaVCgIMohXX +XhfclaMlJXZMJJYAABfAEsdcZFrrUyCz,oCViEnLVshIMvwAQNWZXqZcrQPRRKUVw +qGQYEYHymauzrXYZAlktFSFUsIgZIFMg,GoOGNQRLxLZGhSweuygSLdWXrNmhWLPA +SLXnOfQpILDJrFhcqjrnLjZdJUxpFkYs,PfyoGOBnePuqAoBiuMmuSnePKZaKRkcZ +vDBSFyjobdxMoMuutWYlDyMvxKDyJazV,PZVNXrBdRoZyABhHojHlzpjgkniweiFP +WhzMFyFQhyDvcmWjfoyVHXByfuvyzUfX,zhdYyQtJVbfdbTJyPIOPaUwVCrnPsanm +vVbgVzZNfOpkxzLjHpxleJJkIddDhUga,TkhUySZeBrPlOvvZUubENDIPZrrxACYE +XhZlBcMAbxkjuDkGQpTeElJfirplGyvi,bAMBVBrUkVaWsgRZxeoCYlpLqpVOIsEo +kLxxFsrMcXVHHoFmQleHxjhCwVLsGLRL,oUtqDzfqWLWRXphIhhVVGNDTsCWwNexc +WmhBThtkptZcEuZwKFSuexrAcCzFiJmn,XzsjEVqqUaFAoPWuDTAPRQzNydKdFhbh +TIzKSdeimquKoXwtAvFwcRynoopqTBIG,QqGvBNIDWrNddpvjKEkuGRalLIqMWzQI +kDFmIwrMLKzHLKnWxnatgNLpsdiOfKKP,dmbglxyksVrNbWsbKOwzMlpEiyPtdyCZ +ItENmyhHKtewTbDxOUXPoTxrIwgHYFRg,ceWGHrDMFtRJphIyvVRIZkccAxDLyQjQ +uTzuXJfgBYdaEwwWPQNOfXXTVEaLHYjk,TLOWQenvbzEoJRkLNIVmJsZTfLtDTbXc +BIPcBTFpKfIJFLrfKLyCqlwzppbeqnPa,ZhaRtZTKpghfyWCBUvDUMveHsIOQsLQc +ebqUmVKbFJyUVDMtqLGYuBllsGPUrviF,tnBmNtioAgYKZkiRhoJKkFEANiXvWYtk +xmnmcmHiatfqnezfRDpJnWtrvTRwgkDg,qezZohcptJiTZMUFRfIbeVlKQAUBeBjU +LmcQeEVSlFaazQmhIwpVgqefrXiviqWd,LTNMXWwzWgiSwYbwpzKNpnbwCsDvZzUq +DySwnUmcOFdpbUXWJSdPONtwOBLUyYLY,sboPiymQfPCGdVpiNdhrGemPZkKDhsKa +PwTpCQkzNcerFNdnhnuaxOpTQChbxvLK,QUOecAVYsvYXnYHQDFXASTMHxskPZUJU +frDqfeAgrSrYMiqKiDjxoLMwBaGRZKaC,yltdCVhgrTPwMaUOVyVHsxWrdBhqBzzu +laWiqsEhpcxmufQewMfZsywiqBNbWHvq,sZYDYsWhwZrGlVqBWVXFmQENHLLFbKTV +vKZDMLPBdtrPYpGhQkltdyMKIjNshElg,QMXxaqgbmTBilvfNZkuSVjbgOjkEzfTv +oLJHVpcVhQxSEGYSIWiemdUGqVjIZCri,FlHekWzGaoHbIDOyUIDsJmQPaRBCmgWZ +IVJheOemlncdfUXKvxWYUTzqZtYKpzbV,gqRTUCAWrtOiMetADZFQUEmOuFUZTrpA +HHQFZvmFAByVyFwdyHEEeWoOqNwRNGPI,VFulTvRMBtXDjuWZUyBDLCYmLpUeFJEA +CeAMPHqlEYWLxMBemqvxbXDPdhQWetYo,hsOHIKvIstOTUcGHNzbshCOjlekzflmj +kPctDyQeSWiGZVJsugYjaWazsDYSPJpl,RtncfhLeoEhmqomkbMZRgklswOFBvHSa +SfjnUwQwonoMjDrTUgFBxOzHtYXkApgR,QAKhLpRPCKjyxinivrbgfdLPdMHzmiHF +yOhqEAaIFAdfnvQgtzZtfFtBhrIjyzMU,ewNQGxQTAEYWmQpLWENCNFjkEDjuCoaQ +HaHwsqxCnTJuQQJrLVogfpujNgumWigG,jyppjDEzdRwRjPhOkbghEkDXpliAYhfv +DVuPQHWXTdmLvxdhlzUTNYugrYEAYwXt,FodDgITMECrURJEWknOfJZBxlForJODS +iXmyfkjaSYtsQAfHeJRfyglGwtllyAXA,kQuSDEEtNeoKvzqYrWaIOJdsoJQipYms +cHWvWaGjQCqOXVYGWGunrajuElZJtniO,cUfCKuIJcnZXqeyWAHtNRBNxzehQDTJk +sIaFuhlYogcqrZwfMeQvCwFuyBmnbsJC,QNnzVdkecHIQskBSmvvCFJwAibtadYMu +zpweJqEwPdCWNMCRDogWNqOYYaVkOTuv,RcVvXqaWKnKeXqKhvAKtRInSlceQZlUb +IGnHWSWetrWkpTBqiYMVAzmCudpqIUTT,bHmDKJMYsegrNvpLrSJVZYNUfKXobzsV +lmLfTSRZlTCTMAKTJYsrqcaPXahZHany,qyMtWLcnpdRHjUqdZTnhxSMIegzyXeMJ +OyZIhBZXovGNxMrTQEdUEGppYwcbhvxi,EvFaQMcLnHjyqlmSrAaQauAWWaxSEzlx +BfkJMiubfUlwdzcChFTEBRmARUsuOhSC,XxMsiYHTqWxKYgIpncVLVljwrjVZCNVJ +OENOFrFHCSgLWsGSLkSLOTqFFoSkhYuO,qVhhUEkmNALtKrSquEteUyUXmBSFNAmg +ZeQWNrboaJMDEWlCSXcutLvAHgshBiOT,WDXQTRqLytccqgIvfincLkdzClzmvBuj +tedCylnjwRoFgdTbEjMNLTplyhNOvqbM,GKWlxlkjksliNrqLTCMVnJBpZWfTYKXA +oHyQFRmIcsHhrjafHjJMoRqEsjZOZlgK,ElTBibhGSkpPLFLlkVIBZCVpQxmhrQyn +eWLcKFughwUfvRMmTnCBGfYvzAmUGWGP,tshokCfctsBYwwFWgVDytaRzXPgbGufX +FEmYvNbokfnltCPlZKBbCrlhZKVUkztR,prSOPwHnYoGWzPfbiqDkjxTuroKFKAYU +wOoFtTgtgUvXnoGaiKpRSAeHSiLyJbRW,OSgZVbwpuXdEhOOHZnrHEfrHtWLzihpX +yTRRbjplJOKyrZPQDEYljlYvFfgPnVmx,gpUNNxjCunBGQbheRGcyPNUTblNmXeCf +ddeZqxUUYBepphJzcgQwBPFXWYMgAiZe,UJDhSeOYtByvkeSAMaBBCShqkSbkAvHH +goNOCFeJlkWfCylPMTvvRROueoyDibrO,xprKPPJUkJPbdYjcbAWdrWokEXnCCoyy +YdhKVtPfzaZvMvZeyArLkUpYdsTBkRcU,AcuwzLvCNQeAPFOubrSIEUgytiwHkteQ +QKWOUUySCTbexWguJxBIuNTShTmyiWJU,IhIQRTJoUAmbKHEuriisDxSzPqxEVmHA +bYenYbveAelEiXWjfllVOdzbkwIwnDks,KKAATYRNDDAaapmDjmqOxabLQSMBjGPI +blOUyjwOhnkQCFnFRJuMEAoNFNvuBxdN,AHebxaGwzbbMDCjQxjuYhqsktZRtbnIN +hUEHCeDPuvYFvEGLSGgjmQrYTQsXLyWb,lAQEDgFMPMaybJqHfIanNfNiByeGWJxW +KfYNKtLrBeCGMAXWtAgnpUfvkanRUcQO,VZluydoTbkcOxTcolwDysTZxwgsPTxaz +lIgYpDwXjxkdXIQJKqoOzIwmjBTgJsKd,WGdFsIWKIDbgsAtwcAymgulZICpYViyW +dyQFhaQtZNvrzuyqgEwAAronVoGZBJFV,zstdjRaUkHJpfENnOKIKKLEBoFJWcCKJ +QJVdkshgTZxVdNHzxYDIXSUYueKueeoz,XJSgNYVDiNkJWEABGfRzFMTwbclvCpxE +sEtvQDdpvLFRCxxPPbvEHYenDkNMbjiC,ajWgrmIFsAwTIYubMWnYoaAJYLXJKxYF +UmiClrxxsRKncEpKvoYXKsyozYxRPjVS,vbyxPAsyMUJYZzVVnsCyxAlZDeNVvTmr +AmYgSQpgWlgZGPscexCHChppRqOkbvyq,FrbJCobaJMOADyePhHYHJEOwOQIerCig +uSuodHNclTpNOPffbBADxSAIrrWisayB,GfFnHhlXEUlFDaJacylIocGqMpYyNeoZ +ynqZelorxYPEbpaGvFYWlMNLNJjweHKp,rcjNfimBblVigUTCrgIZlBtQRQUqAcBf +gImQZPwujfxtLebPcasuPPSsqVZCelNC,gVOAYpZTZgFQnRHXkSsonEpOJGcpnVvO +cIJUgbfEosDWqriTgMyvJtKxhHHOEbFX,gOCSSvkUmbwXkTscKZNAdqWCUaaEyNlm +hQYkXkHiRjeHGNrMLbIlHmZUAMTtgJLG,gaRrAVBcsEIghlrqSncvZOBGuZbCVkfi +YTEcfedqEJetutBchyVccuLHhZBuikLF,cHiaQwAfoazjSyoYsLEijibEFYlEvoQP +HHzqEmFcHrRApEGtbGXXDFYYfrZKYWlT,gbMzwoCdQvJkHTyYAfEBpnDiWqjPqAFg +MjlVaNrfADTsYrgcQECgcKhSruTiaFcP,VhlPYatswNQUKjlWARBIzZnvIOmiXSVT +oQiRLCKlNlCPGjyUpjiYIhOzQFnsoFpf,UIRmEZDNSGkBnysMKsYcbgwtmOXtPwqN +gRiblrEKUeQTOyMiiOswtJfjUgyEUgdn,ksshKhIlJVCAqAonWiQekbjLeCXEqtyy +uNKZubLrHYYOtdewfxDafXGXcyiohFaG,zvdXqTSgmtjpJDStxxrexUKchFCfvIYt +wtvvSiXtuqkXnKYTwsbCNWREfVwttBwl,aJbcRuGKACTRGDTaQKQryahxoLgyipXt +OVsmBMxoaDGKIGOCNgnDPZCvmQxKHkal,IgxSwTqxdFiAqNaDwWQbwMuDxqLSZUNY +oaTEWMMuKJcmGnzPeUvrTJbxSbNCbjmf,dxSkzWJipMQImvvKzNGHQTgSwNHOkwLo +sVuiyeYvDCIokAEhPWfqJaCapphQfZiW,CSMoigmiEgxmoIOPJlJoSKdQVqVhAZAC +qQOEoiTnoGAGbwjNOKoYkvPppQotaZzx,MAlwWvqRdYBwKJdARzwZkmEeGKrpFulH +EQMSeXeiAPqIcGgBTuZYfsiGYBxTmrpz,FBkVlZvnlQICPDbrcRrCbiwjtZaEtwEY +NsQVCWYvMunOKFBUFmvEFvljZjeZoZid,vWdLevorczrQUUqNgvyyMtPpzybirvVy +itFfEJrKIDGmMrbKVteWMEEGHFBDcHos,xbuWGyCeXPwjnGfulFuUYNbVpmiCMhXn +hgRArloGEcysHqUcGWxWFmlqLoEwyJXE,OjdkkdonzfZOxOlPllWzveRhiglGZWWI +mUIMeNcDsVBulCNiniaIohkytfGXYgWz,AqZljaVwJFxUBDubqrUKxkUqgQmZmWfj +xRAtJzWubpgJPdITCxbVTyuVuJzPAniZ,CnQswkBWyrHCNdCaudtTgNbUKziCdYeE +pfqzVPSzOQvRafGFzyxnDzEqZdSpCPEJ,RWHWvyTgloIRISIUxyOtGkYfLAwwKHpV +ydkfvakbOPfuJbeLVoYOQTdbTcERjlxh,lXFSTqXDbNAJFdtsGhZhqKhndUFhZiPk +lSEfJGcIgymHVBLHAzLdqYwrDlcOtNJu,hzcpBQenNuvDWQMaWzSWZHHIcKONsaIF +QbZhDBMPFnFlXIqnYvASJVaCMfSlRjtm,eUOyJjSZFzzLYaGleLOfuvlOqZZHRmwG +hfsLMaXXPyXKgbhJXRQdhxLTzuyWbQQl,ojGvSBEzdmpUuUlYOMHWvgjTjZeXGuQs +rtzzyCeOdNtkDyYsDEWqSYKEYeqmInnJ,cShXuVyaGzHGiQJonEzNMpcCNQhNEJjj +MONwKxHEGtHSINisEcCOddmUEPmnnUPB,YoiNDCOhriUXlHToBAlLEvLNhsGCLQaW +GrDKGUBpwkYUdFTIzrgZQQVPMRUVLnEb,aYITlGfGawEHCjIknQVjxLPwMjXcWJGt +IxBluuEwdMuigvNsVLGoVqPaFfeeJgfi,roTSINUYGLHnEDcjunuyNEetJRkyMnXH +zPwYoRbMfmeJKwMIAhBMyRhHiuESbPBQ,kGIMUNFyYXjKdqdELeZZweSRpLOXgkqX +lwODnqFgPPjIVPTMJasPoBlDlIIKduvm,QMytUHDlQSLXJVEMAOeWVlobZOvubXfg +FDcxVDOnclzyUhYmssKkpVnNEZSCIMqD,ilpZnVNiFIiaNvBRxshvaiozYwqHcrmA +VJBsttBlfqnsKoxPzDwhNzASGQmtwOcq,tmUjdJNSUwfzaDcuowHXWMXDvTQSfUgq +PHwBShTiXPSTHufVXdplylrCpIiiKkQf,dReVoGoruStzzyqwAmBVVqIgjtEXElPk +mpWrYsLwxOZcasOjTUttRJaDhXUEDXrx,xXHCPrVMAVEmTJgpqkKkIeIyOYPmzjfg +lbIXKHwtIvgKNleZSwynveXgaFwPIzbc,jrEpWrwZqZVYTDKIuNCoRisclNAkiLGl +uXDUGhgjRUanuhCVwcrprWdDjGwSdagb,RORUdqtcItxdWdHCjSuBJxFjfsSEvbaB +hIHjfEAFJyeauHmLOVhmGijnVnWnbuCq,VHfnGdcLsVzBjMXshJZfSMVRLuIqDczQ +QUTHtLlGFPkxArDiUcRGftHNPGWziBac,RVxQbiEhSfwweWILgLQbuzubBBYnuxpK +rruBxxRCfvtwIUOdzKQkCobZzXVXQiKV,FmUNwGfOAYwSPNjXuMLWxFTHjyabSVra +ZJyPzVwyMdYJSszjQZMZUftRFxqJkXlw,rzSGzWoTrqlrRXZQEDOHUptlHZjQVaXv +OcqPDwGcePrJogVHAAVsfbMYpWQyRbrH,wlzOQpZjJdFfIuoMwJusgPCNYxxfEPLN +mvvJxCMwqwbThamqXQvULtlGDZCOAUlS,iMhczhMwMGAWKphFqqtcMWoKNyXmWdpw +XTQHpLupTMkTpJomvHOxSPilgpJtQmNN,QKjXFBCDDkaRRUyQlxiycAvlqgTbGhzi +dJZcBArWhvbyqHPEmQeyoHtNiWqpdWpE,WYvlSdNAEXUhBkPyKNWecknnkPdvexNP +HbOuSyNirKtAKbbIVjnkAWqUbUgljtFL,kGPgSsAqEsxeRZbkekhYuuLaQDwATfir +avVzKmdfiNuzephXKaqDEzGzWshJnqVh,PmPhFnGrzkfhMXtdjSYLOpYtlXHlVTNN +vjzbBOuwNKwMiJlfEqsmMRMzupoHNYvs,KlwdQMrwCkrMbHoDmLgwQOgIVXettqMy +mxCDTMxzukZrdcsugCPCjYnRwOSRbIfv,XAVGwXqhVumflFiPYauUaFoGilBRfBqk +klTdIHQyLnofpkKjTSEKQUvGKArZeiRK,bVGaEMkqtoTyRDZLsfeLYKERhaQPtkkR +CXWaTsxBFRTrZTbvhztPFXgAfWrZYHxv,xBEbYHFSbNqGVevaoHQcvkpvuLKVGkDV +gLfrPqiPAbXNYxqsysDBMZukOSfWlImw,QuJIEdEoXHmbICbBNgUpSIZEqOIoWAxO +FEwfclzsEHqrRWVpckdxWzvKKzmVWPMY,iPfINyusDshJxqYGtjAPkHUavXYLEixk +AABZKpQaerspSqlKFhsivfabmVzJivrV,vcgYBvMjhtKoxCBXZttSMeXkoaWEaMAg +duTNtEDIzaGxqxeMLRpuoEoWJmkscbwr,mFgJVRjomCktmYWslBbBLKmPrzcUfgjO +niGoXkbDzqybovBQjRDBRiiftSWQwGZd,cSzTVkqnXoxGiDiFrTqvCLfWvSYVangN +vOzhgrfSVJDgVcTFQAuWdjgyjuIzYMyb,ZPcyXEQBAPgriBzzaumucOWcVroEMdZf +odmHEwBVNlQNYWqOYnHZGpnygXHElHVM,xgwkzierjcxetoJywdubXrcuZglBtuIU +ulVpuLqiqdDJJsOJDiEjMGiaReOmCNvr,kOYZTWYPrisHTfALihsqpMTJNqzmrbaQ +eqUPPdMYsfNOSVXSooNsdFbKhHYMrFcK,SUlIjWuKNbOYXqKXhHDPWbKhVNuUsLvp +eGjwKXGPeiRYeXATNEAsMfgTFOcvgRXD,kAiLKrMkhiOwRlxbeoOFNkzjomhQtqvh +PZxRQfQQfeZLRysHOLPMbyqPWtrdovLj,oIYneXVeEXbrBAkUQlDmqFKPaPouICdE +IsxwRhQIiRCCymYKSsDSfUlzsNNteMIs,UtBRAdQoUSCcJYmiwLUPYqsibCsxYhWs +MqvsBuJRyRoRjBGOmcsVCIEEeTVVkdmZ,YpvbRCaWBESYJtQFiRMHPowAqndmMLQJ +XuOutfkpASLvswDWpyNGTcpptswbpxCy,EQYtAZSXhHezUbRJAdNPwumrZQQoFALI +QgfaVCIpiADdYZYxgfHvCZlEXDviFZLc,WKCQUPFqKfXfEIdrjkKpCRCNTtGpcktZ +RfLjFTbutRiAQBvPRlHjrnglWQENWuqL,YxtuhnWPqKlhStpmWvndIzxiwmKPLZzp +exDrhHYDOlvSvDcNnPtoeUhfqzhZoHQr,MhkYJgYNMmMMbQKZmLIoJIxbXUKjegXu +NoijfvLGAvJBFwzPBjQJlyookpzwseaU,aGlHZxpwgWcOEBHJeHTMRyHsgyciDEBv +ISsUQLhcrSFemURRRQxnkgEGMKQjISet,tTVINqQcVyGhaVwABmKFLsLBdfOsAAAh +FoJaBvLOnvepfnyatsakqOjqfpknPIAQ,ogRrWxlWPVGFrPoipswTELovjNpXtSZP +qlLPexPTyhEEruLmBwjETtdZImKetADF,yyaYxkqhgEeXhVspunhXtZaVrNWCLZKO +AWbqYpldwwqOpiCTEMYeCHytNgKoeTMY,BbswWSnvYLEoRFvwaKsjBjZBjxeHXXmY +JiVnXibDFiQJIiwKSBeNQRfdpaIMYpnN,nxTsynkBndDGSnVfUcMubAwxXViXBuSA +XLMEYcHDzIkEZHUSVXpbAaQTEIGhLkqU,jSkCziLLnncRrCisExeSeXutEwWTSIBo +ePgqBjNOJYcrKrmmNcFUcokNqPplrKrT,dBmuNUzyvkMBrbhdjCHFIFuckkECZfve +fEqbukhxdoxOskOJOXoqwuxaLVCEyHFu,UTaweeOfDspBkDOMUsZAXcsIblYFVEpL +KkshhKkqCXpuibXzFrcEFcIIJauMBbMf,AJNEXFsWSRCBRcOFAJMHKYboaPerUokg +bBrTJKXYkDUJAUouXTssGnJvxqisVmJm,XgmIyHyYaICjRyXuYECfbxrTgKZqIVHB +FcPOgKBaADajQzPvFWiAbrYMrtfPYtno,TZofNULghNNRZpdDlpCBiWckcxIcNGnw +LHCYbxdwGsYihqLlHKufCVeQMTFajUKh,VBMCApAbSLHunRpFhnGvaYjxUSgkhQkV +DQRvnYPHdxMdTKkCKTVfXmvnZkfDxgEC,ybWISTmtgZEnHBrlRQgeitgfJZQpDJJN +dclkKqkadbkITQAMDmXgqRxGvqKHQdyb,wJEQDeoTBTLnFJgrlOgOSiOAASSoIsqF +JQawOEYNnoafPmEbIrNhTpOJHOqxQnOP,PHlftPSQWBnVEnAQTceeInQdrXUrPeDo +pcLbTOezhPZbgkygZcEwzEzxojSCMlbP,SOrUpQjnaJeCeItgrJqdEUhNsxIlSzhX +nbsautROEopjmyErzRoqazVtkxuVHVgQ,EouDIWXeCporerfBwWSeDHGyNQDnmUgr +otmIOELGEYhspyDHIsHxfcJOkrTOxQRW,sRjqwceyoNPtrPRohRTnXzYYIStbGcaq +ugaBIFHtnSeyAdADeCdiMpbdzKqWGbLs,CqnKdGJdXAaursiBsDPmJIVMTDAmmjKr +qvvSsQrrccAUKTxUFETsQxTmjCEqaOaH,kvYavUIuVfLRvdVCCLtElNXEQuvfNmDh +dwsUWVolnujNinAEzqVkYNOXPZcTvYkn,tjZkjLbbZFBSjPMGOZsbqhNtvdlMhVOb +eStJntRCbsdnrOviZlprKXpKUBRxoROs,ZWNmvMAKbYTJYBqnHAILSOcdZiDOzzta +gSGJedfirkzTPWuFklcyszKxuvlMqAaQ,NkxzUNiUqIVPLzRRtpPAKFagVmGTswua +UkyGiPCtQOFSfyZeSvCFBQnshZFzzIRC,CqXArmRaHSzNqWDprLlqreGTOxetJvSN +iTvxXiDFNJTDxOiLbNVhxEnLdhCLopmF,tsHajsmqbAMUVTvymuQWTuhwkUSQTtSi +QdtuiFTuXQzWEbEROmeqCsHzIZOzijyb,VlVedPwbvcHVdzxOWZEKCBkeHbJJaovR +dMsePPTnRjyxHnwtiFvgRLOwemvEsSuO,mVNBxABQRsIzhMajveQQIycgNBYhnqca +jtVVrwuEAKqjcuhbmYWwWFkgXDbYPQbj,LjBoiQEKGOvRQMBLOrtDPKNJLwlEBELe +zcAmErKtNIRWemdXaGgvhPMJcVFPzRMM,edFyngjauUIyXSgjkWwxDzrExqvobuYw +nDWReDhVIhexWinMAZHdBUBFcihBEwXo,bIPgVNKEeaLPFRngrPakTmvuByGoCiqn +GKpnJtLeskWYMPbXdetlwKCkWFZDeale,CLNRrBmWAwvMPrsLjVRZYwIGYjWbsIqn +zRDSsBsBTYrcaNxwcKQirklyViZnrkQc,ROHrzzhWOlavtULGSiWQfYKMRhqLvSzu +PtySmpReAfYqqCGJvdudQAgdWGAooEVW,XLYgMBMBTnMyGVnfHlROBNIjEDtRYxAH +ImZMKsQcWfUzQEEArlwgxZueUElBBOeS,tvcfLDQMYyWDAFbmbdmmqjkOpMrFPYYu +XGndCWILxiYStsWWivtVSKKGGfhRSjMn,CfecYYuCLFooiQYBeTiGWXiEroCvOAAb +GjroJKGizkHfTXHfqueZRCFDgFrdeJEi,IzRPVvmwcbHoaSdAmDHSACHGbLpmdtyg +KsCMZQPkzrOjGBawCEsAtqEnceVTiBJi,MvplBrKjGehZIEMGfDKnNxfBRUXayohm +bNeGSDWmrbnhjRCsZpBMbaODmOsgPQTC,STOxVPitCAonBTWCqysTgprvKBaCaTQs +bAmMlSlWXXdRemXcCrvwaDSPCEdLnzrK,dhukdVKtCutfbeKghILMmfHZDINsTomK +FddIgopHVNaJxWnZCKrBsqZJnIqXCrpb,zvXtVptSGTBCkVqSlhpGnMhcylObIzQI +ZtGoTQWSmdYkMDYsWxBONCUUipgrwPuk,AvNkbgtetcNsfTKMjMzfMAjEiOATcpKO +AxebfLNrXtOhcCHljjOCSIbEAnZemxMp,JvzQaTrzhpRJUEGcsCujwhqtViLnttPb +SfzWygbzevImsQPjADDNuUoieLldpAqm,HwiVzcWGjdZCTXPaExaVZKJdMNvUSQDA +DUcWhSCMKJBoMiuSkgTpNnHPONqjLzrl,JONKcUxzKIHWlJKmuLewkFGezVUsopTn +gSsqinNPRqibjIwuOpTLQYhWDicxakBM,TlOEfxAIZJjFYqdNcNicGUoCNNhWVDDk +pUnElGznGCGnYodPWpDcwoWQDkRhFHOJ,AkMngBTwMTkIkoyJMyiCeLgBcOJomLjm +ezsDxjaJybdyDlObgBJnWeCHyArkndMt,XFbRzfStxyhPWpKPLNzmkNLjqoALGowe +raFQfgTZDxUlbuPMVqOJvjHMSwQmRyCP,PNCfqjbNJjQjBAjilOMqteRzGqdLZRba +JrjsafOLVSWDYZGaToOuPwqFlSIcFMwP,PLmyOwIzuISXggvHDFaYHEaiIYnJtMfF +nidlVjUIbIUgyQyrXEDwtrzYbFehRazo,dYntqwKbLruFtEnWTXYAmgRPUxNGvDrn +RlcOhycEfMuXAGWuFAbmtvUzOIBCohmw,djltDROTxPlRVQzkvTteDaWxNHxSqItx +MnJHdESlCkZGnYmcoUJRVIQfqDibKMYF,pnsvqIarXfTwhOxfqthGzzcvkHyQmGkx +YprNXUkNTZdhdBOCXDkHpsLhUYSKOJtY,aPqknwYKTlGoVMSblijKuKHPEraojWpK +uNnxSVLUDgWjnZsvRGeSuLRxJfDJUmCD,RiuFxexGmSgFacxWAnLaYaEmoliHdnJH +QzyeWnQefCWjgvkOsUJZNAWMOnfSbIfI,mcCgIStVvFaQkrYfnFLsVHhKWCpXoqlg +pZXLgNQajrusJYWskUVkKcZRofiwDHIB,xXNfRcnmqALPfPTFkyajxhTpyTdgJAsp +hInRmINswsQnmGlWtYKczcxKKyhsgDEo,dizIeLczMUBpQwpFjoOdkwwSOAYUKOHk +oIuEydgoJEnXlNktiXmHnVJTcQBRzZGd,GWXeiPLWFEGbRsdYPvvrNFVrAwlpzOyo +dDznUDQOajLhuiVnQIRfNsitoeNqosHX,OmAvBSfihfOsdngOuGXskkaFnviPqOpi +JqhplJepvgDdAcIrosjIdtoDXpbGFTYA,VtWzmezMNfSsugFSQIOHkDmYTmeAEDvi +ZRGkuuBgMevOPsQRNKDYkaOaxyglSaCt,xcJOQnOnRommsAarQCgXyoHstfetpiJQ +kAxvkCXcAxdchzOlhgghAzCcGWaJdoAQ,HgmNdBFleTUjnQLfktoWikUCbHiQuIkA +ARHEWCVEofRNvYJjSjgVJvWaiVdAOBoG,ECqnpnGNNQKfOCkOFPdJWihbMYQigTiA +FuuNYXQSHpbuZwJEGVIPLEqynjQXaSPg,dlOYbvfnaZKzGJSuwJDwtVbWhtyIBaGg +AVWoQLSUVlEWTmFVLHHwOmXRbqFVQbOP,aZULTeOQiCFzHVSLoicfvxRicrifWIFz +DwnDIOmeLPdYEvSGREXrITATlmeTwIji,pCgFSuZoIeKFzxHoyXUYYRYQrpPfIngX +sTVATiFePOgmlVayrGbOlCPEYLhUTQfj,HvgglOdLcRtIMhcxuCFwxKyNwCBiZokA +LTgLwNmRopXCdeMdxMSOGpAAEPOnFdKH,VGnOwxUvhwnUUVZUhRrkquJzItPMUuEt +uxjzUVnfHQxtFinsfbtLGknltwljLrhJ,IHSdUdQnwNnYXQDmapQrHvBnsXXCxsrT +YmWWqfUpOCeqvNzBogpEzHVzKeoxROhW,TlKwFXOpUOzhpqMzFOJXTjmjPZeHkinW +kKyYWFheMWsYfREYmlzxGWSbEcWQwBGz,vAoqVzPPctdrobpSiYOsPXKbJbHpNIMB +GYDSssDJgbdvVFSzzIuoTAzzbNKxJVIM,smLrMHykgTItGOSUrVHZdqhnZHNXppKw +pJJkvqJCANSozoZRfhVLAxbNOnqpczOs,GxkpFDusQSRqLQDKdDjbcqAtylpttJeX +EAkibBmuTxMPkaucGgONpUryHEqPgrBi,UqFdGbNwKbUklQOZWDfxFZdfqHFumVID +gHgscPnggmzaqexdQmLydfVzNDQIseGK,tmeiLdrUiCJVbUgrVjnjeBAvFFGJzVfg +xKBQJrXNLmqHjNdvuvmLvvsGbacnhYTn,fXPfVAilbQwVEGjAnFmBCBrFJHmUBILO +cHMxJbbfWcNJseIxUiGEEPKbIhrlaNMs,IVdbJtjzgPrenDQKDnHTtOKMlDcXYnCl +OgucdCqyqJSsMVOmAfgfITTqgnpFPVmG,OgrjyzvnHuqHyVasmCWAyPjYYEmaHMWE +lOfOdCrTdQgyJkJITTtsIvPKhaLZqHSB,YfTNbmyDYjnlUaCPeIAVHPwdZbsqHnvB +IncXzcTiJrMsnknNOYPaasTKFOdVCRfZ,HNppsXODcVfoyLrMKEJIViBrpZPshALB +hMcYLwbfzHoyFiTpgBkteqEcOMWdFOka,rcznKQyNBQDviivQGyiovRUwGMANEqYd +MkIxVbiUpPcuGOJBiyVlYJIHjTCZLQbQ,FrjNBizkPDQHKgsHwRqEhwBYZFuZCPZG +oHbkZykyZsKuGJRdkEDsNWkepXeFBTCM,EokrtGSxtXUpturneIFhuufoJpfOHQjt +YqUcIcOLSHQUhNTmhsPVRAKdEQJnJKgh,CihMfpubchmmDptFnhGfzlqSsYXUbgEc +PjHwlweOTtvpesPJoIHqfXSfHoXjlVVp,kWwmnvNYGyszZkJvLWGwnCWuHIpuGQuD +lumWWlxMvNhGqqgQGHBhvCVvThZfUHtA,AwXSXbilZtnDnwGbiKxkpsApozzvuiFv +nlEVaohiABKmzoSgHfOKVRCllevVGjYk,RIltVdJqdNMaQCKvzqwUHWRpRyNDLLbJ +iCJvPtCUXyuiyieWKVYzoCHeiocILHpY,DBaXekOImBmFlwRzltikJPCoBMnOITAb +SRBEbJHYmvVgOzxZQilXSIwICSkFPDBX,LyFIZjVsTUmTuauAKYLJAYZttCjxgSkh +iKOIBqTLWBNgHVgNWwkvzRFmjlfiSECd,ZhbYuiPQTASggPdgkwLCWrYRplglIAcI +rUsPJACcQctamVGRaCOZjiTxgEzgzghB,dEwboTnWlCWgVQBKjEEhwbKkRfGzfXtO +dqlOImzrfuispOMxWhVqsqIzBQLvNqNK,wrwAvtRkILZkewGOmykGKvptAHGcqzIl +JHCNXcLmqGbjkBHJGgDofxxQGBtLiUTC,IDjrJTxtAfQMWPcYTBhYqKaUfOefMemF +FLCUmCTKpFaiKFAbklImlBjkBOaPyJvx,vTrcnONrbKkurdeTisYwDOChyAUlWhyM +KVhThmcwVfLkTUXWwVGbdzvlOsprKHZJ,QVDcEOpwUzHCexxXspdjXjOSUrtHcAlf +eyBqxVtsqqeXdeJhSyXcsIpLVYyPnasd,UHwworOtERKiEnQRXTxFYvxsylmmqUCY +FAeCgbCeSShIWXyrMufmDjtNpHZBvUzv,FeVYLOupKdtJoUmePamAntheikfSXTTB +qaAoVuNpILMYtTMPwGPlAvocVbCGteac,sOIsiGMOgTYlYdFyMlFeEOcgsgikBQDU +wzwkNRXXbinjKyPNNkCXeBgygPfisnMm,VYtprGugVNKwpRHQScfhFcxIjxcnaHiH +PkFMNYtLcAaQdnnPEfOWwfnnODZGHHiW,aMPkFxQpUVMHxywNkTQLYwsuHntRPXpb +DyTCpRtYJHlLsKSexTnQImNncBqHUvMr,wntbMZubrReXMuGIzkBTQTwlTHuYLAZO +xmBJnAjlNzMBxEfgnElAJWzSrQMeHawE,pnnmzRRlYQhjTeyHhqRRYetawCPCYqkq +qIsSquelOsYgxJxTtFvIHlXOMfvggPOK,lHtmZsgcCOuznOKzXUSjWJGOtLEmWTPZ +RoJKibPXkogfnPKgqGliHJCvaofWiVle,yoBqfYtScrxecfYhoSxAbUYdSXCHdWjC +fSohcOoXHHMChAemGpkSkMwdIqsxIOWQ,uMDmepgpgxeDXjcUdZPcLAGeRavXJCDg +jjiZaWpWjLoUoykhTZCznvzSWOnvUykv,UETkkrhnGnwYqOOdsJbneseYhDjZQhNi +gpwrFGJCrSwVsJUpmlxnzFSmMBFbCQTc,kWUlOtpecjUZyvYhsZhQWEmgruPLroVk +zWmpEzJSolcZoGobVzKjmtgFOsmBZORK,pooAPvcbSenInTRvcfelEOyWHfDratxR +BLhFyeKepRxvlZxrMzWXfbovtgFukfOM,IuWxKpRgCTeWXpvdapOXsxoxQwvawMix +bvLLtkCKztZJZdDZjrtSZSGTirLbQdEg,wQJIyPtrUbtKrXwKxFwSgrRfDYcSEjsr +UQrELgqcoGrzRbnEhYQsBEpmlwMbIOQD,cIQunGljQUcTOTPItWrWGkiLTAlgWQLU +eEBTvzIUPGrCRxIegpLFgnUBTXCsJcuK,CLBJVRFJRgiadJmGEocELMrAlaxwkiug +uZEMIhLGPxDCjhuYykitHIjUWnPVxOWI,ZQwQZvBBWXnoLfnNJgdDlhLtMJKfEuBP +dxHChyZsgnpzCSxqZHMRXLQQQyJkDPUm,AzMaSEjniNghQieesVzBvlCszfYinsUC +aCNIVAAdtzDrASfOeKbMBjcfAYHZKcKA,wZDyeGRSiXyXSeyNibDdUDDzCHeoQigv +OJPurrwbIaGRIHFuHEyalFcrqJRafBKs,dAmYYsjnujQeWwiJlwhvSdmAIJZAoaXX +XSFCNPmZxNQAdjmwAlYVGdqBexLFDwCC,BBcLNBHhrMufnHaHfxDiecVGxmiTCWjk +gkNsQAgegSoGRglBKAUzsGNyyxLyhQiw,lECOHnOfHxBskKXnMKmVWwtHLltFmMtu +VDaxkkCxpYaTZCutHyJduyTNDRXYkUgy,QXrUsdIIwCiWMrriwjghyxEPDzaQWLaI +EfTpwPtRsdNImXjXFWtNUOTXKlAGChTL,rohURcZhgDJkEXTagBeftleXJESGLibg +zSQDyxztEWavXsgvkXsFMeKIfGkZFsfI,zxujZgECJNihGffqnWDEaYFGwoaBgoUl +yHkRTWTkzcjujEBboXPzlwxjdCwekGjP,tGWUUQRIffOMqwCFuLGdzYcFQDECoEuW +JWtVdTIoBXzNADMEFLSAqHruOBJbCPxT,KJnXLttPOGOAjzUXYQXwtnjJXGuPdkCN +oNuMOvHGtDHvoXgwgzsXSKTYhpwlSolj,rVdoZedLOzWMKTrraAVRVhAerOEYYapE +aGArSMBOPPmRsZqquLZVdOTWyUHHeleM,KFILOaLzfBEJQDjDCuBKPLJJLKPgmwvc +GEyaIMQsadITdNaXRwGDoViUTWVjPlVg,KfBKeHhEbxzzVjjUbznDPnQCQZLGIPxC +LTLaBPZcTvVsuCGiRtNOeVRZhcVawdRb,XErcvRUboPjpotrbAtVuyaoQMliHyJxb +UGGWVmFnxbYLneExLRHlWHQyXsxuhfVX,AxEZqauQHaYEfWJRrQELqDklUGmqZgCl +SYjnauWUgjIpTUWgjPHhDARNgnEgkNjD,RVzUHuAdIJQYdEWHLxqJmjrzOSCSxfeu +VZvXvqSlpFQvahuxhAkpsuGHKVcAvmGl,rtpzyKkIkCasigGwNKPlgUPwnQZMJqQR +TeQPuSaevXDVDXUYfcXmFDkJlBHMYUAO,aPRCJDxHGziZdyGUecNxRVznJPnSsRMg +sgNIRheWYBMTKFgLUCGizxfVTAIGebac,XeXZUkrIKgSfkwDNTzzzkXXqWkVzOjMe +GOfCDNXWkhPKHJubFSMZNDbfeXeRcWXG,LfeaYDyhpmnfEjUhSyFvMJfzVfEaPRVc +UjgteozIYpgduszcymqeUwxvKCYOeyDI,AgVtOBAXVxIzwtMZygJrWHKmCdzWNtfZ +BerjojhjHtxFyBZotuSQxPywDabXGeoW,fZFOGLBuYXZlrUaOuHIEqbKAUCpMwXle +jUWqKMDyzwjfPNiolxQTeGbgDKMSVyiL,wbCTtrkcwJxnEpVdDqojFxwbBoIkrXAG +usJThWLHjntxRJzfGFdxrrBhROwjwrgJ,rmHxpKstokXsldKsUPERPqxRQXrRWwtT +UXfetkAjPpCSfOXQCRHlIUVEVRMUgMgf,cZRiUvxUMaGVPKZiyKMtxrtFLVpceHYt +mHviJlYUxmlNeEcMLEGsMtLBSFAQoouk,WOucFZPorZbggnVipGxhpkiPFnaQfXmv +WJcDmURCAQMgcqVRTceUdEcJmkDrxEIv,VMrSTDUxOrvEvqxRXLiUViFrTYkiLyTs +ejreStUzvHJVdeKdRKxcEsUQSfVRfift,yTtPowhNcPjpkGLkkrFmqOxzZsClQSUh +ORKTzrpMPIWvUFIqTrsXVHzmvmXlFgZj,GltZLrOFpVgQRFikowVEOGxpVnsZLQnI +tbihEwKrfCVfOQBKKEpNFvLDghSRcNIe,ZGjQCKkzzahMeVTqFSKNpIfDNNTBuhgp +xYauvGcBDOfdwOcYGOkblEntrmbnBYFf,ILMhlUZCRMhmJqOuUocinFamnSnsCAQG +ngdIzWWOGHqhaDitvzDjQLcGJbnShDLi,ajONmzKbiqOsMmEdXCZiwZvsuRwHdGGy +UnzAcEeuDxeWCGaIqhzqNugmKDQNmqjt,VhYFiFfEQTxQtfJtQeGhqxBELJjjQrUy +NtCtOZddVDRBNjVJdQrbMPBtmVuMGwmc,aBIVtxerPEMpiOdtOqZelZhXRsCddfOo +TcdDzjciDhGQuMvQruLMTOuZziCGOjma,luaZhfofqxVQKbjeaZVCBwvDoNYFfmwx +VnTSVANNGXIydwpjMmksssVqrTDbapKa,oMysjHCxfxxmvrvBnCIpjdQxqvAqpagX +OWzPmWkOQraJFclzhsRtbtTEtWAZZMVI,aQStazWsdZsIPIowQxqODhXDWqxAlKFV +MKeaVpbDerqEvkvbzrrEgCJyYVBzLwWs,JjRbBVAfgYofxbpJTIUNilyOEQGuQTbo +RafhIJHlouMPPcMitVFupNXmoCURYvVs,oPwdrEdppSRfsiDQqzobykEUqEYxKdjb +zAFvqQRzqiXRTfBTWRERqjasdYwhhqjk,qtFbYWijuFsahBRxkKeHOXLtZxOnktGC +tomgiITrxVeFbaOpPpUkKxAiVTLYTWOf,dHaHkxYkmZJGibTkJlvVxpwuXGCoiGKn +DJkNMRxbEUmwpOiPAnIrGMDnOTvgvHry,eWpSUCAuAMwZvAXXkrezKEaxHLDZPwNn +EirDfmuJwaWvuqQrFbHRyEgbsWQkqqDi,oLSGvkiyOdAfDpgmKXKCIMWOSulTOpOF +qeYIjUtftxbsbackLchIPbVwqpgvWtEL,WjGkySKBnoPuYkfcsFZxEqgtfrxRrkAm +vJLHXfvdoBOpmfsiaMpCOMlCiKJrkOxp,QWAJlHdeKMpOXuuJGWuhOUjMJwwjeTwR +YGdpGyjGOOfUGRxnHBCtcVzbCMkIdTlK,RqnUlrBrdPsKSTiSZwseQfZnmLGsnCqG +dVzgwjOFrAJrKAkdheYrBuHNpOClFxUp,LPLBRGAPQlxgIAtILDmdadHuXoZXCiou +GNTiGQpwujZRJsQruYfdlyUeRgObbkJf,PeQWrBSonIMBqthjpLTunvAiFMjcEMOO +KRLqQCSKIsNQJdgEUBZRRtPsNXGxEKHy,zOVfjYijjAQunrroXaTcLCEYnROpLrfc +FvspkDEcqMbMBcmxngLbvzSRAuCcdxGb,ofnVwjBIXtHSHNUvRJPaaYrnjzVsNCfx +TdtcnLzjRApNlrGjFtBMnNDDMmKtsIpm,dnVlrjYfsQdPkuvILNNMNhXrysJROusF +jCDLQZMoktvKHKEnZqWBFKClYuaOsVMQ,ADWGKIobgniYAOeFwrdOLPLxCmHnobQh +qFTRHDWwmGbRQQivZogqnOzVzMhpjaTD,RQxPDnpXvYSpvCGjHjjeRJnpFVAELxnv +MoHjTkWAHZJGKjSsBNxpHHfUVEDXWmZw,GxmNliBlwqPMudixKnQcQOWDwilNbpWU +RglodgVfmbdWoZECfvUKSuPsZyXleoNw,lMTPHBRyWFUkWVGNnYAgAkQvngMAiuJq +thXAkpSUxyeOvySpyXisUZWpDdKzuFeg,NdVIQKDSBCDtNlKvcWXjNUfzVoMagkGa +fHJLmsjvWMaQpjNGROjxGxOUPZgCEjFZ,CnGagvSrkDrqRTQtDwMfLciczicCrTRA +NLuNnynlkmwMFUgtytiJWRahivtMHeXQ,sOCoxlkLKMYNmLbvbASOdoCyghggshsB +nDcMvcMpjtNBSeQWpqBewEKDhnTjJNzl,xhNnhRJsLdmkIVjviGCsowWOuDIZiyjY +aloutJsQssnRptwIDxxYhrnHmkhcieBe,jYSNwSCPCjGqbfQzfafkqzFFYTBbCoav +xFRwGUWiJmDEsaiTPpFHjGihOaWdZhlW,WKtqAfDWpJzdBBVYKsXNDTxYXFhxvECF +aJmGdDEboWFfENaiYYmkqaANAckkLpMI,PnPmpSjRGjtzxvKETYNLsrtmVOhpFEqm +AuTCHreylfGidSStekBqkogjgVPieQWs,dnAaXdpyvpjtFOcgXYFdPkXaCRQmKqEe +HrSLkyNqNqDuCFxDSMscshSkPZYeSiIo,gaLfnNiwPxMIlwYWPPNUlueoxLzBozOq +QXkhbLgLQHUBqsChIsWjFmCvEhCWfFKL,QcKQbNOMttjOWRKzsLWAhOkTeEXpePCn +HGzyprxOgOswVzuAdRucShfbzlQwvmyJ,qlVUEBpSlidkMtPqEqqBRWQJyQuODafJ +FGuSKaWCECnMbWWuRBuBhAtMyaLnEegF,DPVCUVMBjNUUPfzHaiGXWcIdBYEhJzzv +SuiQWApMMUgbDNGJYAvEsZScQwWCChXB,oPlLxxMIBlqAfBGDOTDaHdNKNWeVuZiC +suQPVvjZaKyiRuxKrpZZpYylvfzuLtRq,CDbYruIwrtLsUiXXqBOwqdoorclIjrvH +JmjxVKZVSrSuFSfqXbkEsWRBxZCaTHeA,PufufrGjNvngwqBvjZdGkNncftCwCvzb +poiaYcdwqHjchkQWvpLAPCDaNNziVPzT,wFwjgQmcewvuWLJGUAkGfeKswDIhcgJd +JFXFNFAYfliBpmpEEVEOGkOkXJXBTbkv,QwqxJGaEiedJLvjaPCijGarpDLOpwrvl +fCubNpdzgWXVysoRgBxrgRTkynFjNzKI,UZRkeAKOncPFztRuXcHDqCmaxeGItPbT +JQiTeRYMwGTagcWaUQOFHUxwSvdloeXV,RZfLdPgddTNwlYrSvPzUfWJeteONkbCB +RBlqDrSbavxYLhogzYTAHyaePCxEVlYz,UQkrHhuncracGFHbmMjxlGkLWgTNQkzf +qEywtBzSHbUqGfdcPIIVkQqjSDOXdbNt,xGuuSFhsrnxjofWznJzfiyQrXrzLvUKT +ycarXplgvMAxDdurBFtNBskwHQySpUVY,roAgxtjzKoFSgOjMJyTtsoqRcFkUMzoh +GpQcKdYaJUFogcLMUFkMZHlPrJwHScCq,RWtzoqkCaTVAszhnMoZuHIgvlTMjiGsS +NhDuleWpXRGEjQsSpRzZpldqiptLGiFw,aJyPNINvPnTszOsaGZVPGXSGlPRFVXxr +veLPCTYcuZTJpKSZrofwIjExazFLpolU,feZibGIztgQaFUGxmuTaAPuvZEeTrXSJ +BjwYbfDUfyaOySoxnaIoXOKZzkVdZHwV,ZVijukocLhaPQLvoqoCyBEMiSNPpGkwv +YRTsxObsiCXARulwqdPODiYnGbHIEyfy,rFGorKImzTdMiFTERygmbeBEiNDUXcAU +acaJNRQUunIAymnOIjZBbOKoHaMGrGgz,DYmyNUEkQitCDlEUJVDMvsXIvwjXIZre +bhmlLgcuYZQtPwpwURxUaBYsWAInWkaV,LDpsuGvdMOcJfpJabWVejFFtWBjoHJrO +bvWawEWESGmOHnoDgLeacXCUosXoxsnV,pPpOBfJHUqVzysQIULFfKnFYimWVOfws +VZPIBIcvXPdrEecBLgsRaLhWhEigyspe,eTorelefalswiRRKuFvitcjxuHkffCry +EIfYbEllvsYaKutcWUFaLXhMaMwgcZJO,TaYqTpkOfuJfxloIDgFjXbeFRfiZlbbT +bWlPHXsdeXPMWGHLqsWDnYdrynmlVdRs,VXORcuuqSeEFIFgeNrcrwwmLfdWhdvGF +QxojhzoCPxBZnMFtFLqjLJUDfroLOOWz,XuqUACQTMOAPrqdmHSTVsYoUEjqYIGXp +bAtLDlCkECLvEwAfMUbTkJqInyArySmK,QaVovljrHzgrkwqfZtABuClXOhDWjETL +aAWnFWVpxKleVpZIlQRuJAJxxnbwGqll,YgOisTyOCwPlIkFmXpzUCnRJzjQaHWRR +MEONmSeXSsfAJeKQsNvCncXTajouiRqY,EZQyCKpASqKBtVgmUDUONHHoiKywDozI +PJNEXtMyEGxljScgcivMFJPfCnuoJVSf,BHcTDAzcwShVBsZKQKJLubxhXJReLUBB +BAheQbXmngNosEqMumgEelFNLATIkQyQ,hJfuTOCpjZSlfdaluoLpGKppTqpjflDU +hIzxJkrXzmxIdNeYtcfnxTtupfTOfrWK,oeMglwGxKRFcYVgUdbKKtdNBtGWeKKCp +guDTCcNxUpYtoKDJHHceAbuxKGTLpSLv,zmFDwiwjGeQbQFzbyzMPvOcLPPkBpFCL +FwprMeKrmXvYeopjRZHoWcOrYMneNWvz,UXiGVqvdeyaudURdhPcXeEKgUCzzuLTz +kQlWTamMLUXZtoVgDTHaLDMLmjVflZAZ,AjayhmMHnsyzWXdlMvnynVyBzYrwPmuu +IvCWxowfVAQLPJIXvAsJBPASsiplMhuC,JJglhsRWjSQttHiyYXagIDGhXNdUevKT +HdrGLUCMZAoGXzENKtuWAcikIDHYxhmr,eZqArJGLhlOPcHcfFXwZRKmyImIrDAcH +XXhdCOQZLZHGgDjMjOBvggINNsWhzylP,GdrKpyIncwVsirxdObAsxgNBUatRuSwt +pLvbWWdJsaGPxGGefTrEpVDgIoZHyDbu,aPCRucOcLEvpZdSPVbZBkomBJcdevQQm +uxTHSrKgYrWwtYHdXBgAQrSiWLiokQFn,VDibqjAQjOJYEJGmmUYgYXfjmPzyNqIU +IaafoYyMPrWuxfReujRJNVtspQlvaffN,vvulzEssjkmEIDQzEMMyWvBJJybvYwpK +veUAawwXHqzmyEKUJiLjRhjAFzdSVnBN,duATgUsxxOlqrvszeviYOqPMwtEkeMXy +lUiTzlvKvewimuZeyMEMjtXMTdUkLiAc,FUSpOSLbMUqigIfeswcdaChdtiKiNxrL +ovURTtMgBAkxNLwOMRBMuLuVsmfbdlsh,vAasuTfRrXJWQgbvmoRNLmRJbvETbyvG +FOPsTPVlmnmMDWclshfqUywGBGOreIjw,nZGSpvFiLCpitYdwoXhBSbJFzVnqDTwk +XvGzngJKytDEWAtKYuIxmLbfpWsjobRm,hzwsGJjsUtYUtHfLsOBfDzhDYrhFuSGS +pDUpaqzXIiUvdfnKeaDAmhzPhewAjtjB,TydQksHwqDvepiCEkTIUcNHdBGJmXzMJ +WdFwmcrGBOibNGDzCUIoqjIWJvCuuPhm,kGTYRLENBzNrDheDLLQpFSnvecjSvIjY +gZmJarTbiGjzchcCkxyOgsMWlGnHSrPi,VRsJeJxJehefOrwuzJWkLzEnLOTazBeU +zJfGLnwEsdWMManJGefUIbVvSrbWRCbn,PYaIJGfiHSrnWcpBEdWUIyRGWiRbCNHl +mDynYnQruHldTboUNPCvHSOQzNOzthvE,vHeyPKOIyXmiPOfrCWJzQAOvbVPZiahJ +aXQFlBeATBiDJsZVmdTjRBIyOiCjgQrX,AzbKIopVtcODUcUGBFvBlehPpWicyyLK +FLTraesBAnDqpsbqeaLhDIRjrwaEiqfb,JufuiSarMWVgAELPiTzOGNVdWCAkMxqO +ZLhRaeEfxKSrXTCKkjAIDQvdTYZkYJOf,NcjyBgEMhGyaadWwMOEHwmibymuqjrys +WbKyZxVXLwiEDYDOKFVSpXnYIUoEvGzK,PpUORqgltNTUYfCwxAyFEggZDLjDMSVW +msnFyYKxGYNhnmqVrtPhoRPzdgSLICTM,DRnyGpwZcDliCNWEclAefarclxGxospU +KliIjQXiEqyBawRLSRpNkksXzWDxJBlx,ckMBzRnTcgxiGtTGqdJCOPVlXgDqvkbK +megXHbqWEMBNcZogiGxZjMpYyzqCgCCJ,ZgafdlxNALmuNCylWCFIjTFmAAAfpPMN +ZRzmgfpGTMFgkQPIXWQkMFHPApMIMDoh,QRJmMxWRqcMunyiDWnyFomBOyndkroSp +xRuKGBxCuftwDyyLmRplXyZEYCjuiwmK,rtmAHVHYDbxSCAhNBqPkdNGGlLxKFVvo +GdDzrSaEioTSlLnZZSDGWVFwiiIYUZDm,vDSVBtrKPvTvxjQKAyOTRqHcAWOFTWBl +AMTGuvnKZxGNAVeJLXzMeaPNCkiYATti,SuvYYpYDvaizbDKxNNkRpGGWbWOfalll +WUuTwvStAvDpzgODqnYxFgQYsSeRSKwI,psPMamfHOCozBEXtLuQYaxnMJGxLylXC +ragDHVKjynNCyELpPTTKwojhZATodJiX,ZNRBOkpCSUQtbYRKavWUkvMxVodmfPac +DRipiIDLMRwxxrjGYspyglKEDLDrsNar,nTTTYKJtzalqkFEyNaEBfnxGiadZjQNp +RhslVFwSDzBEkigWTgfajXfJzWnxuXxd,RuEtUOfrugArDXcyKKvGCuBbXVgHWPqs +zZiqgIOeyktfUnwOmMjPVRnRLhWFgcKs,GpdbmuMsNPazhxcDkFjicoOWYblbisOi +avZtjnErjLPsizUgmChWHPaAtxFvYchq,mmzsuAxQouTisNJLSKlzKmweMucFcgaF +QremJtjlNheTCcapqssxFSGbNTRMabtk,xsAmbtnfGgLwtVFANRqSqdlXclLTygte +FzwbuxecuPlofWHMOPDIoAlWdOiElpnP,DXhoRkzCepomfctqHyjKJWxdlvGEsWTJ +DxyeccsrgnMrjWkVnMDtwhKdvpctyyec,hwwuBcepGsbvPhePpEqmHrjbeaaPoAXc +CfIhWzzbPECdiZBgQRnqssGtfDkymksk,LcuThMNjtYJmplMYThbgLpVOiJvGjvtM +lIPeYJXiMeAopFUduZtHQHEynnqIOnjK,fbbyOxxzwucqgmiJJLvBGpsPaeFVxHML +NLMuynSYWsQrqbBjcrlrnzerhyCeIpOE,nWPnEvUiLIKMExXMOviqBGpYriPnEbpK +CnwpJhnKmmlKGhcDpLHuPlYQofkiiGrN,NNgfmPnsNPcrhCxrNoMwefEQxSrGsZkG +WJNoEcoMeCQQDdVQGDexHTdOqfPfzKpe,PxtonjzaQCaZxEvBdTmidguhZEYcPjoj +KzTRCHwfbsRSvorgCfbMgKTqyvQWopje,qCUKXMQrtwfcOjAAHlRoiXUmjVxsQcSf +KIRmTWJAsLZhMCuVouelWVuLpKvBNCRi,mjbkXJixDmCcRxCzrtsprzqbuOaqONVq +AZHQjEAbRlMIDrXUPqJPJrNTLODXWrQP,PTLoDnfzSewbaEeqkOIikCuBUoLzasOs +OltQwezraaGJXotDeezguXgZVhZUsroZ,suvBWOtWPlbzaQDtyhMgYZdMnmyuJLXM +jrprBNtoNxGHgyFhCCfdvdMhgWoUtDAd,VVhMjuPZEVXdndxvadjEHrgZvMPeHINZ +uUISEKnEunYbMjEQSCAxcoIUwggcueDz,bONllpBIpTMXOdMQltxKIgsjpsYwslrS +hpDjScrNWTCdaYjGbOXTffiNKsqRInCK,pdIMbujhkvkjLtaxAluoqFyRHpVDNVTb +bsuiqQKhbMPdNcYsEmfKXuQFBtyeNWlc,zJuUhMRfNfnQCmiZnxhKzZwjRWYGlslj +mRbnrlpNOCsvmEtLjjrFBeHKdZhZNuPr,RdpZVkbelLbVdGXOATLDbarwgRMxHHPP +qNvKXrEAARpUsyIzgtnMkVYGMjJMJqlq,euCwAfSeFABCqpjhQurmNkWYCjRkbqYI +buZfVESpOPSEuabGSBoaCLFJxIwJKEIY,KyerRIVQmPhzmZNnyZjUGGWnwaErUsLq +pdEVPFhRttCnjmuFyMGQSOjfjywpqwYW,KyAvoVBvDPVxFGAlYvetQCgdfxfBBGdk +LoWOYDclYUBSeYcTqlHUWLVaWBleHQfi,aHulADHCMrmyrfzGeQQlLqZoFrfsOWez +rusHOHKWFOtCRWPcGHmYsgPTOAJfTRvy,TpMJASVZqsCVXNXQLRcDYWyiKluqGzwi +dLIJpegHyJswJQGJBOgOVKeUKskdKAhp,aSXYnuUJRgTaZiCSdZMmpnvjDFMSxYJh +hDHxlsPZNjlcCQJXHURxPDiLaeQeshvF,jwnmnczkRbIAwPuQdzAAiuDRsSvPlghZ +usERYJyQTHqUaaqMcXLqxkyaXUtdJBbt,bNTmIcZmVRQBHLgNqfuVKbQALiHWAMbY +HHkalmwHnyjnMjhABRWfCLwLFtFFBZDB,HFfBvbeTkJGckQDSYzigjgNXfBUmIijW +EXikcUgXHFXmocBXmxoXwZrAKoWCyagS,UxhQqmXaHDXgrSTWsERDCtMsrIsCrqqV +HwPCumFdBmVpvguqYqWVyRQPrPDMPYGy,LYQLykRPrvqLtRiGGJJokVBoulQNKWMO +AiscMfTjZneCfLESfzyapbmGCGpEZWgZ,GClhQWryxulPMhyAvSMxxcMPpVusvBLL +cvhdwrDdCypoSthNFfsYIKFTSzrcPksW,bAwIKBFdhuCkrdETdjxeWFykckchXywJ +eSRoPoWStcqBjgXZlmLltrQlTwweeHrH,SpNXCRrWQwxrNYECmHAEUuphdsXvEZug +OFZKozJiqeFrRsUuXyMMQEcoCKSIJGPo,TrrxYXrINhoFviILtLcvoDXewoWjdnCN +OLtxjeoyugRqUGdKQjtIPacSNImOqAZO,VzpgZGctPFAZiWiTAHFUCLytOqByIlEo +uAxUMPICUtroLocVgoQMYacNjTVbSPLQ,zMPsXBRyCbWuakRdlPCoXqFOMIjgJYar +xYdSqURqbzGZdjSrZCtdzYnzetfmZszs,taFLjWJeWGdRsWygTRWLDHUTGrMlcpLA diff --git a/examples/pir/apsi/data/query.csv b/examples/pir/apsi/data/query.csv index 8a9fe68..96416e0 100644 --- a/examples/pir/apsi/data/query.csv +++ b/examples/pir/apsi/data/query.csv @@ -1,10 +1,11 @@ -xJ -aE -LJ -uk -PR -tR -iJ -xo -ID -vA +key +fewPzlVQbdGzWcpIQNoFTHHGmyHfhtZL +cCDIklNjCAHiQVWjTWnewRWgCNnncqjr +DMKrrLBYyyeghykbDdhbMLHvIIyanEbI +mFlYBTHAmZvIAiJFTXWxsLjvKoAODWfF +hABRoHuJtFdYVnWoQuMAwrebLsXOOViZ +pxlSukFAQDecskXCDnVaykHqmfXTGMTG +FMmyaTPrinFxGOUxZykwDTyILrddUeoX +tyPmyhPawVfmigpEoQQoppQKwYgxrtAj +JrHuJQrxyOfZmxwoHmKLwdskfWXrnjKj +jLxnDiASAEJClKTNlbanWVufVCbeKXSB diff --git a/examples/pir/apsi/data/query_1_100_300byte.csv b/examples/pir/apsi/data/query_1_100_300byte.csv index 114dc4f..c46e16a 100644 --- a/examples/pir/apsi/data/query_1_100_300byte.csv +++ b/examples/pir/apsi/data/query_1_100_300byte.csv @@ -1 +1,2 @@ +key IkKLBDzORQvehwiFVDZhfVWiZhJnNBvjGdzxrBIdlNvkQnJKBZCtBHmXUZwETcbx diff --git a/examples/pir/apsi/data/query_to_labeled_db.csv b/examples/pir/apsi/data/query_to_labeled_db.csv index 74dfe2e..8c81b0d 100644 --- a/examples/pir/apsi/data/query_to_labeled_db.csv +++ b/examples/pir/apsi/data/query_to_labeled_db.csv @@ -1,10 +1,11 @@ -WT -FA -mI -gF -XF -Pq -nU -Ap -Vu -DV +key +PSSAEqtkeNBxGsdgLbekOPlesVzFDdzW +WbJAtEXMQdiRUPQmLaVHOXaSSalWCivB +IUYMRlEYVYjCvSofmILMOJDuuCHoiOkb +PsxQKxvhGcfrRIeviMIzmGzHkeqWcLNJ +uvRfIYpzyenCvyTwGRibBsCIcgbYxMIq +BAwGmPuWeuCRjcevHXgcUQpqzxkbYeGD +kGBivxOLSyakNKUbEVXJRfVfdqEuNlGF +MIJXyWSUPawcLQlbeuUfMcQGhQtYmaAu +vWnpnBQEZwkAbsLfjikRnAEUvOpCiuBu +NWSFtaMmilRGquCfJJePYcYLxuNrDCEr diff --git a/examples/pir/apsi/test_data_creator.py b/examples/pir/apsi/test_data_creator.py index 4aecfeb..fd6efc6 100644 --- a/examples/pir/apsi/test_data_creator.py +++ b/examples/pir/apsi/test_data_creator.py @@ -6,20 +6,46 @@ import string ap = argparse.ArgumentParser() -ap.add_argument("sender_size", help="The size of the sender's set", type=int) -ap.add_argument("receiver_size", help="The size of the receiver's set", type=int) ap.add_argument( - "intersection_size", help="The desired size of the intersection", type=int + "-s", "--sender_size", help="The size of the sender's set", type=int, default=100 ) ap.add_argument( - "label_byte_count", + "-r", "--receiver_size", help="The size of the receiver's set", type=int, default=1 +) +ap.add_argument( + "-i", + "--intersection_size", + help="The desired size of the intersection", + type=int, + default=1, +) +ap.add_argument( + "--db_file", + nargs="?", + help="The file to write the sender's set to", + default="db.csv", +) +ap.add_argument( + "--query_file", + nargs="?", + help="The file to write the sender's set to", + default="query.csv", +) +ap.add_argument( + "--intersection_file", + nargs="?", + help="The file to write the sender's set to", + default="ground_truth.csv", +) +ap.add_argument( + "--label_byte_count", nargs="?", help="The number of bytes used for the labels", type=int, default=0, ) ap.add_argument( - "item_byte_count", + "--item_byte_count", nargs="?", help="The number of bytes used for the items", type=int, @@ -35,37 +61,54 @@ sender_list = [] letters = string.ascii_lowercase + string.ascii_uppercase -while len(sender_list) < sender_sz: - item = "".join(random.choice(letters) for i in range(item_bc)) - label = "".join(random.choice(letters) for i in range(label_bc)) - sender_list.append((item, label)) -print("Done creating sender's set") -recv_set = set() -ground_truth_set = set() -while len(recv_set) < min(int_sz, recv_sz): - # item = random.choice(sender_list)[0] - kv_pair = random.choice(sender_list) - ground_truth_set.add(kv_pair) - item = kv_pair[0] - recv_set.add(item) +send_size = 0 +recv_size = 0 -while len(recv_set) < recv_sz: - item = "".join(random.choice(letters) for i in range(item_bc)) - recv_set.add(item) -print("Done creating receiver's set") +import time -with open("db.csv", "w") as sender_file: - for item, label in sender_list: - sender_file.write(item + (("," + label) if label_bc != 0 else "") + "\n") -print("Wrote sender's set") +start = time.time() -with open("query.csv", "w") as recv_file: - for item in recv_set: - recv_file.write(item + "\n") -print("Wrote receiver's set") +with open(args.db_file, "w") as sender_file: + sender_file.write("key" + (("," + "value") if label_bc != 0 else "") + "\n") + with open(args.query_file, "w") as query_file: + query_file.write("key\n") + with open(args.intersection_file, "w") as truth_file: + truth_file.write("key" + (("," + "value") if label_bc != 0 else "") + "\n") + while send_size < sender_sz: + item = "".join(random.choice(letters) for i in range(item_bc)) + label = "".join(random.choice(letters) for i in range(label_bc)) + sender_file.write( + item + (("," + label) if label_bc != 0 else "") + "\n" + ) + send_size += 1 + if send_size % 100000 == 0: + print( + time.strftime("%Y%0m%0e %0H:%0M:%0S") + + ": Wrote " + + str(send_size) + + " items : " + + f"{send_size/sender_sz*100:.2f}%, " + + f" rest: {(time.time() - start) / send_size * sender_sz * (1 - send_size / sender_sz): .2f}s", + flush=True, + ) + if recv_size < int_sz: + query_file.write(item + "\n") + truth_file.write( + item + (("," + label) if label_bc != 0 else "") + "\n" + ) + recv_size += 1 + while recv_size < recv_sz: + query_file.write( + "".join(random.choice(letters) for i in range(item_bc)) + "\n" + ) + recv_size += 1 + print(f"Wrote intersection set: {args.intersection_file}") + print(f"Wrote query's set: {args.query_file}") + print(f"Wrote sender's set: {args.db_file}") -with open("ground_truth.csv", "w") as ground_truth_file: - for item, label in list(ground_truth_set): - ground_truth_file.write(item + (("," + label) if label_bc != 0 else "") + "\n") -print("Wrote ground_truth set") \ No newline at end of file +""" +python examples/pir/apsi/test_data_creator.py -s=1000000 -r=1 -i=1 --db_file=db_1M.csv --query_file=query_1.csv --intersection_file=ground_truth_1.csv --label_byte_count=16 +python examples/pir/apsi/test_data_creator.py -s=10000000 -r=1 -i=1 --db_file=db_10M.csv --query_file=query_1.csv --intersection_file=ground_truth_1.csv --label_byte_count=16 +python examples/pir/apsi/test_data_creator.py -s=100000000 -r=1 -i=1 --db_file=db_100M.csv --query_file=query_1.csv --intersection_file=ground_truth_1.csv --label_byte_count=16 +""" diff --git a/examples/pir/config/apsi_receiver_bucket.json b/examples/pir/config/apsi_receiver_bucket.json index 40f978d..6fc944d 100644 --- a/examples/pir/config/apsi_receiver_bucket.json +++ b/examples/pir/config/apsi_receiver_bucket.json @@ -1,22 +1,22 @@ { - "apsi_receiver_config": { - "query_file": "/tmp/query.csv", - "output_file": "/tmp/result.csv", - "params_file": "/tmp/100K-1-16.json", - "experimental_enable_bucketize": true, - "experimental_bucket_cnt": 10 - }, - "link_config": { - "parties": [ - { - "id": "sender", - "host": "127.0.0.1:5300" - }, - { - "id": "receiver", - "host": "127.0.0.1:5400" - } - ] - }, - "self_link_party": "receiver" -} \ No newline at end of file + "apsi_receiver_config": { + "query_file": "/tmp/query.csv", + "output_file": "/tmp/result.csv", + "params_file": "/tmp/100K-1-16.json", + "experimental_enable_bucketize": true, + "experimental_bucket_cnt": 10000 + }, + "link_config": { + "parties": [ + { + "id": "sender", + "host": "127.0.0.1:5300" + }, + { + "id": "receiver", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "receiver" +} diff --git a/examples/pir/config/apsi_sender_full.json b/examples/pir/config/apsi_sender_full.json index 58f49d3..4c71759 100644 --- a/examples/pir/config/apsi_sender_full.json +++ b/examples/pir/config/apsi_sender_full.json @@ -1,19 +1,19 @@ { - "apsi_sender_config": { - "db_file": "/tmp/db.csv", - "params_file": "/tmp/100K-1-16.json" - }, - "link_config": { - "parties": [ - { - "id": "sender", - "host": "127.0.0.1:5300" - }, - { - "id": "receiver", - "host": "127.0.0.1:5400" - } - ] - }, - "self_link_party": "sender" -} \ No newline at end of file + "apsi_sender_config": { + "source_file": "/tmp/db.csv", + "params_file": "/tmp/100K-1-16.json" + }, + "link_config": { + "parties": [ + { + "id": "sender", + "host": "127.0.0.1:5300" + }, + { + "id": "receiver", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "sender" +} diff --git a/examples/pir/config/apsi_sender_online_bucket.json b/examples/pir/config/apsi_sender_online_bucket.json index 7f2bc08..202ae9a 100644 --- a/examples/pir/config/apsi_sender_online_bucket.json +++ b/examples/pir/config/apsi_sender_online_bucket.json @@ -1,20 +1,23 @@ { - "apsi_sender_config": { - "experimental_enable_bucketize": true, - "experimental_bucket_cnt": 10, - "experimental_bucket_folder": "/tmp/apsi_sender_bucket/" - }, - "link_config": { - "parties": [ - { - "id": "sender", - "host": "127.0.0.1:5300" - }, - { - "id": "receiver", - "host": "127.0.0.1:5400" - } - ] - }, - "self_link_party": "sender" -} \ No newline at end of file + "apsi_sender_config": { + "source_file": "/tmp/db.csv", + "params_file": "/tmp/100K-1-16.json", + "experimental_enable_bucketize": true, + "experimental_bucket_cnt": 10000, + "experimental_bucket_folder": "/tmp/apsi_sender_bucket/", + "experimental_db_generating_process_num": 16 + }, + "link_config": { + "parties": [ + { + "id": "sender", + "host": "127.0.0.1:5300" + }, + { + "id": "receiver", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "sender" +} diff --git a/examples/pir/config/apsi_sender_setup.json b/examples/pir/config/apsi_sender_setup.json index 6c002fb..e3e6991 100644 --- a/examples/pir/config/apsi_sender_setup.json +++ b/examples/pir/config/apsi_sender_setup.json @@ -1,8 +1,8 @@ { - "apsi_sender_config": { - "db_file": "/tmp/db.csv", - "params_file": "/tmp/100K-1-16.json", - "sdb_out_file": "/tmp/sdb", - "save_db_only": true - } -} \ No newline at end of file + "apsi_sender_config": { + "source_file": "/tmp/db.csv", + "params_file": "/tmp/100K-1-16.json", + "sdb_out_file": "/tmp/sdb", + "save_db_only": true + } +} diff --git a/examples/pir/config/apsi_sender_setup_bucket.json b/examples/pir/config/apsi_sender_setup_bucket.json index 2f8d9b3..ace3d9d 100644 --- a/examples/pir/config/apsi_sender_setup_bucket.json +++ b/examples/pir/config/apsi_sender_setup_bucket.json @@ -1,11 +1,13 @@ { - "apsi_sender_config": { - "db_file": "/tmp/db.csv", - "params_file": "/tmp/100K-1-16.json", - "sdb_out_file": "/tmp/sdb", - "save_db_only": true, - "experimental_enable_bucketize": true, - "experimental_bucket_cnt": 10, - "experimental_bucket_folder": "/tmp/apsi_sender_bucket/" - } -} \ No newline at end of file + "apsi_sender_config": { + "threads": 1, + "source_file": "/tmp/db.csv", + "params_file": "/tmp/100K-1-16.json", + "sdb_out_file": "/tmp/sdb", + "save_db_only": true, + "experimental_enable_bucketize": true, + "experimental_bucket_cnt": 10000, + "experimental_bucket_folder": "/tmp/apsi_sender_bucket/", + "experimental_db_generating_process_num": 16 + } +} diff --git a/psi/apsi_wrapper/BUILD.bazel b/psi/apsi_wrapper/BUILD.bazel index 3996685..0a091c8 100644 --- a/psi/apsi_wrapper/BUILD.bazel +++ b/psi/apsi_wrapper/BUILD.bazel @@ -45,21 +45,6 @@ psi_cc_library( ], ) -psi_cc_test( - name = "integration_test", - timeout = "long", - srcs = [ - "integration_test.cc", - ], - tags = ["manual"], - deps = [ - ":receiver", - ":sender", - ":test_utils", - "@com_github_microsoft_apsi//:apsi", - ], -) - psi_cc_library( name = "yacl_channel", srcs = ["yacl_channel.cc"], diff --git a/psi/apsi_wrapper/api/api_test.cc b/psi/apsi_wrapper/api/api_test.cc index bac6549..f4173fb 100644 --- a/psi/apsi_wrapper/api/api_test.cc +++ b/psi/apsi_wrapper/api/api_test.cc @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include #include #include @@ -23,6 +21,7 @@ #include "boost/uuid/uuid.hpp" #include "boost/uuid/uuid_generators.hpp" #include "boost/uuid/uuid_io.hpp" +#include "fmt/format.h" #include "gtest/gtest.h" #include "spdlog/spdlog.h" @@ -34,7 +33,7 @@ namespace psi::apsi_wrapper::api { std::vector ReadItems(const std::string& file_path) { - psi::apsi_wrapper::CSVReader reader(file_path); + psi::apsi_wrapper::ApsiCsvReader reader(file_path); psi::apsi_wrapper::DBData db_data; std::vector items; @@ -68,6 +67,7 @@ TEST(ApiTest, Works) { EXPECT_TRUE(std::filesystem::create_directory(bucket_senderdb_folder)); { + SPDLOG_INFO("test sender db setup"); psi::apsi_wrapper::api::Sender sender; sender.LoadCsv(sender_csv_file, params_file, nonce_byte_count, compress); @@ -87,6 +87,8 @@ TEST(ApiTest, Works) { } { + SPDLOG_INFO("test sender db load"); + psi::apsi_wrapper::api::Sender sender; ASSERT_TRUE(sender.LoadSenderDb(sdb_out_file)); std::string params_str = sender.GenerateParams(); @@ -105,6 +107,8 @@ TEST(ApiTest, Works) { } { + SPDLOG_INFO("test bucketized sender db"); + psi::apsi_wrapper::api::Sender::SaveBucketizedSenderDb( sender_csv_file, params_file, nonce_byte_count, compress, bucket_senderdb_folder, bucket_cnt); diff --git a/psi/apsi_wrapper/api/api_test_label.cc b/psi/apsi_wrapper/api/api_test_label.cc index e07f15f..bb03e44 100644 --- a/psi/apsi_wrapper/api/api_test_label.cc +++ b/psi/apsi_wrapper/api/api_test_label.cc @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include #include #include @@ -22,6 +20,7 @@ #include "boost/uuid/uuid.hpp" #include "boost/uuid/uuid_generators.hpp" #include "boost/uuid/uuid_io.hpp" +#include "fmt/format.h" #include "gtest/gtest.h" #include "spdlog/spdlog.h" @@ -33,7 +32,7 @@ namespace psi::apsi_wrapper::api { std::vector ReadItems(const std::string& file_path) { - psi::apsi_wrapper::CSVReader reader(file_path); + psi::apsi_wrapper::ApsiCsvReader reader(file_path); psi::apsi_wrapper::DBData db_data; std::vector items; @@ -42,7 +41,7 @@ std::vector ReadItems(const std::string& file_path) { } psi::apsi_wrapper::LabeledData ReadLabeledDB(const std::string& file_path) { - psi::apsi_wrapper::CSVReader reader(file_path); + psi::apsi_wrapper::ApsiCsvReader reader(file_path); psi::apsi_wrapper::DBData db_data; std::vector items; tie(db_data, items) = reader.read(); diff --git a/psi/apsi_wrapper/api/receiver.cc b/psi/apsi_wrapper/api/receiver.cc index cc2c54a..63e92a2 100644 --- a/psi/apsi_wrapper/api/receiver.cc +++ b/psi/apsi_wrapper/api/receiver.cc @@ -37,7 +37,7 @@ void Receiver::SetThreadCount(size_t threads) { } void Receiver::LoadParamsConfig(const std::string& params_file) { - params_ = psi::apsi_wrapper::build_psi_params(params_file); + params_ = psi::apsi_wrapper::BuildPsiParams(params_file); } void Receiver::LoadSenderParams(const std::string& params_response_str) { diff --git a/psi/apsi_wrapper/api/sender.cc b/psi/apsi_wrapper/api/sender.cc index 7ef7dba..54a44e5 100644 --- a/psi/apsi_wrapper/api/sender.cc +++ b/psi/apsi_wrapper/api/sender.cc @@ -35,8 +35,8 @@ bool Sender::LoadCsv(const std::string &csv_file_path, size_t nonce_byte_count, bool compress) { throw_if_file_invalid(csv_file_path); throw_if_file_invalid(params_file_path); - sender_db_ = try_load_csv_db(csv_file_path, params_file_path, - nonce_byte_count, compress, oprf_key_); + sender_db_ = GenerateSenderDB(csv_file_path, params_file_path, + nonce_byte_count, compress, oprf_key_); return sender_db_ != nullptr; } @@ -46,13 +46,13 @@ void Sender::SetThreadCount(size_t threads) { bool Sender::LoadSenderDb(const std::string &sdb_file_path) { throw_if_file_invalid(sdb_file_path); - sender_db_ = try_load_sender_db(sdb_file_path, "", oprf_key_); + sender_db_ = TryLoadSenderDB(sdb_file_path, "", oprf_key_); return sender_db_ != nullptr; } bool Sender::SaveSenderDb(const std::string &sdb_file_path) { - return psi::apsi_wrapper::try_save_sender_db(sdb_file_path, sender_db_, - oprf_key_); + return psi::apsi_wrapper::TrySaveSenderDB(sdb_file_path, sender_db_, + oprf_key_); } std::string Sender::GenerateParams() { @@ -101,6 +101,8 @@ std::string Sender::RunOPRF(const std::string &oprf_request_str) { } std::string Sender::RunQuery(const std::string &query_str) { + YACL_ENFORCE(sender_db_ != nullptr, "sender_db is null"); + stringstream ss; ss << query_str; ::apsi::network::SenderOperationHeader sop_header; @@ -247,7 +249,7 @@ bool Sender::SaveBucketizedSenderDb(const std::string &csv_file_path, return false; } - CSVReader reader(csv_file_path); + ApsiCsvReader reader(csv_file_path); reader.bucketize(bucket_cnt, parent_path); MultiplexDiskCache disk_cache(parent_path, false); @@ -255,7 +257,7 @@ bool Sender::SaveBucketizedSenderDb(const std::string &csv_file_path, for (size_t i = 0; i < bucket_cnt; i++) { std::string db_path = GenerateDbPath(parent_path, i); ::apsi::oprf::OPRFKey oprf_key; - auto sender_db = psi::apsi_wrapper::try_load_csv_db( + auto sender_db = psi::apsi_wrapper::GenerateSenderDB( disk_cache.GetPath(i), params_file_path, nonce_byte_count, compress, oprf_key); @@ -265,7 +267,7 @@ bool Sender::SaveBucketizedSenderDb(const std::string &csv_file_path, return false; } - if (!psi::apsi_wrapper::try_save_sender_db(db_path, sender_db, oprf_key)) { + if (!psi::apsi_wrapper::TrySaveSenderDB(db_path, sender_db, oprf_key)) { APSI_LOG_ERROR("Failed to save SenderDB: " << db_path << " terminating"); return false; } diff --git a/psi/apsi_wrapper/cli/BUILD.bazel b/psi/apsi_wrapper/cli/BUILD.bazel index 8bf9ad5..ab350a1 100644 --- a/psi/apsi_wrapper/cli/BUILD.bazel +++ b/psi/apsi_wrapper/cli/BUILD.bazel @@ -35,6 +35,7 @@ psi_cc_library( "//psi/apsi_wrapper:sender", "//psi/apsi_wrapper:yacl_channel", "//psi/apsi_wrapper/utils:bucket", + "//psi/apsi_wrapper/utils:group_db", "@com_github_microsoft_apsi//:apsi", ], ) @@ -52,8 +53,10 @@ psi_cc_library( "//psi/apsi_wrapper/utils:bucket", "//psi/apsi_wrapper/utils:common", "//psi/apsi_wrapper/utils:csv_reader", + "//psi/apsi_wrapper/utils:group_db", "//psi/apsi_wrapper/utils:sender_db", "//psi/proto:pir_cc_proto", + "@yacl//yacl/utils:parallel", ], ) diff --git a/psi/apsi_wrapper/cli/entry.cc b/psi/apsi_wrapper/cli/entry.cc index 4f2388e..7533dca 100644 --- a/psi/apsi_wrapper/cli/entry.cc +++ b/psi/apsi_wrapper/cli/entry.cc @@ -17,14 +17,23 @@ #include "psi/apsi_wrapper/cli/entry.h" +#include +#include +#include +#include // STD +#include +#include #include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -35,6 +44,7 @@ #include "apsi/log.h" #include "apsi/network/zmq/zmq_channel.h" #include "apsi/thread_pool_mgr.h" +#include "yacl/utils/parallel.h" #include "psi/apsi_wrapper/cli/common_utils.h" #include "psi/apsi_wrapper/cli/sender_dispatcher.h" @@ -42,6 +52,7 @@ #include "psi/apsi_wrapper/utils/bucket.h" #include "psi/apsi_wrapper/utils/common.h" #include "psi/apsi_wrapper/utils/csv_reader.h" +#include "psi/apsi_wrapper/utils/group_db.h" #include "psi/apsi_wrapper/utils/sender_db.h" #include "psi/apsi_wrapper/yacl_channel.h" #include "psi/utils/multiplex_disk_cache.h" @@ -49,7 +60,9 @@ using namespace std; namespace psi::apsi_wrapper::cli { + namespace { + void print_transmitted_data(::apsi::network::Channel &channel) { auto nice_byte_count = [](uint64_t bytes) -> std::string { std::stringstream ss; @@ -61,12 +74,11 @@ void print_transmitted_data(::apsi::network::Channel &channel) { return ss.str(); }; - APSI_LOG_INFO( - "Communication R->S: " << nice_byte_count(channel.bytes_sent())); - APSI_LOG_INFO( - "Communication S->R: " << nice_byte_count(channel.bytes_received())); - APSI_LOG_INFO("Communication total: " << nice_byte_count( - channel.bytes_sent() + channel.bytes_received())); + SPDLOG_INFO("Communication R->S: {} ", nice_byte_count(channel.bytes_sent())); + SPDLOG_INFO("Communication S->R: {}", + nice_byte_count(channel.bytes_received())); + SPDLOG_INFO("Communication total: {}", + nice_byte_count(channel.bytes_sent() + channel.bytes_received())); } std::string get_conn_addr(const ReceiverOptions &options) { @@ -77,7 +89,7 @@ std::string get_conn_addr(const ReceiverOptions &options) { } void sigint_handler(int param [[maybe_unused]]) { - APSI_LOG_WARNING("Sender interrupted"); + SPDLOG_WARN("Sender interrupted"); print_timing_report(::apsi::util::sender_stopwatch); exit(0); } @@ -98,14 +110,14 @@ int RunReceiver(const ReceiverOptions &options, channel = std::make_unique<::apsi::network::ZMQReceiverChannel>(); std::string conn_addr = get_conn_addr(options); - APSI_LOG_INFO("Connecting to " << conn_addr); + SPDLOG_INFO("Connecting to {}", conn_addr); static_cast<::apsi::network::ZMQReceiverChannel *>(channel.get()) ->connect(conn_addr); if (static_cast<::apsi::network::ZMQReceiverChannel *>(channel.get()) ->is_connected()) { - APSI_LOG_INFO("Successfully connected to " << conn_addr); + SPDLOG_INFO("Successfully connected to {}", conn_addr); } else { - APSI_LOG_WARNING("Failed to connect to " << conn_addr); + SPDLOG_WARN("Failed to connect to {}", conn_addr); return -1; } } else { @@ -133,23 +145,23 @@ int RunReceiver(const ReceiverOptions &options, // reciver must own the same params_file as sender. unique_ptr<::apsi::PSIParams> params = - psi::apsi_wrapper::build_psi_params(options.params_file); + psi::apsi_wrapper::BuildPsiParams(options.params_file); if (!params) { try { - APSI_LOG_INFO("Sending parameter request"); + SPDLOG_INFO("Sending parameter request"); params = make_unique<::apsi::PSIParams>( psi::apsi_wrapper::Receiver::RequestParams(*channel)); - APSI_LOG_INFO("Received valid parameters"); + SPDLOG_INFO("Received valid parameters"); } catch (const exception &ex) { - APSI_LOG_WARNING("Failed to receive valid parameters: " << ex.what()); + SPDLOG_WARN("Failed to receive valid parameters: {}", ex.what()); return -1; } } ::apsi::ThreadPoolMgr::SetThreadCount(options.threads); - APSI_LOG_INFO("Setting thread count to " - << ::apsi::ThreadPoolMgr::GetThreadCount()); + SPDLOG_INFO("Setting thread count to {}", + ::apsi::ThreadPoolMgr::GetThreadCount()); psi::apsi_wrapper::Receiver receiver(*params); @@ -159,7 +171,7 @@ int RunReceiver(const ReceiverOptions &options, if (!query_data || !holds_alternative(*query_data)) { // Failed to read query file - APSI_LOG_ERROR("Failed to read query file: terminating"); + SPDLOG_ERROR("Failed to read query file: terminating"); return -1; } @@ -193,26 +205,25 @@ int RunReceiver(const ReceiverOptions &options, vector<::apsi::HashedItem> oprf_items; vector<::apsi::LabelKey> label_keys; try { - APSI_LOG_INFO("Sending OPRF request for " << items_vec.size() - << " items "); + SPDLOG_INFO("Sending OPRF request for {} {}", items_vec.size(), + " items "); tie(oprf_items, label_keys) = psi::apsi_wrapper::Receiver::RequestOPRF( items_vec, *channel, bucket_idx); - APSI_LOG_INFO("Received OPRF response for " << items_vec.size() - << " items"); + SPDLOG_INFO("Received OPRF response for {} items", items_vec.size()); } catch (const exception &ex) { - APSI_LOG_WARNING("OPRF request failed: " << ex.what()); + SPDLOG_WARN("OPRF request failed: {}", ex.what()); return -1; } vector<::apsi::receiver::MatchRecord> query_result; try { - APSI_LOG_INFO("Sending APSI query"); + SPDLOG_INFO("Sending APSI query"); query_result = receiver.request_query(oprf_items, label_keys, *channel, options.streaming_result, bucket_idx); - APSI_LOG_INFO("Received APSI query response"); + SPDLOG_INFO("Received APSI query response"); } catch (const exception &ex) { - APSI_LOG_WARNING("Failed sending APSI query: " << ex.what()); + SPDLOG_WARN("Failed sending APSI query: {}", ex.what()); return -1; } @@ -231,7 +242,7 @@ int RunReceiver(const ReceiverOptions &options, *match_cnt = total_matches; } - APSI_LOG_INFO("Total matches " << total_matches << " items."); + SPDLOG_INFO("Total matches {} items.", total_matches); print_transmitted_data(*channel); print_timing_report(::apsi::util::recv_stopwatch); @@ -241,25 +252,23 @@ int RunReceiver(const ReceiverOptions &options, vector<::apsi::HashedItem> oprf_items; vector<::apsi::LabelKey> label_keys; try { - APSI_LOG_INFO("Sending OPRF request for " << items_vec.size() - << " items "); + SPDLOG_INFO("Sending OPRF request for {} items ", items_vec.size()); tie(oprf_items, label_keys) = psi::apsi_wrapper::Receiver::RequestOPRF(items_vec, *channel); - APSI_LOG_INFO("Received OPRF response for " << items_vec.size() - << " items"); + SPDLOG_INFO("Received OPRF response for {} items", items_vec.size()); } catch (const exception &ex) { - APSI_LOG_WARNING("OPRF request failed: " << ex.what()); + SPDLOG_WARN("OPRF request failed: {}", ex.what()); return -1; } vector<::apsi::receiver::MatchRecord> query_result; try { - APSI_LOG_INFO("Sending APSI query"); + SPDLOG_INFO("Sending APSI query"); query_result = receiver.request_query(oprf_items, label_keys, *channel, options.streaming_result); - APSI_LOG_INFO("Received APSI query response"); + SPDLOG_INFO("Received APSI query response"); } catch (const exception &ex) { - APSI_LOG_WARNING("Failed sending APSI query: " << ex.what()); + SPDLOG_WARN("Failed sending APSI query: {}", ex.what()); return -1; } @@ -284,177 +293,239 @@ int RunReceiver(const ReceiverOptions &options, return 0; } -int RunSender(const SenderOptions &options, - const std::shared_ptr &lctx) { - apsi::Log::SetConsoleDisabled(options.silent); - apsi::Log::SetLogFile(options.log_file); - apsi::Log::SetLogLevel(options.log_level); +template +pid_t StartProcess(F &&f, Args &&...args) { + auto pid = fork(); + switch (pid) { + case -1: + SPDLOG_ERROR("fork failed"); + exit(1); + case 0: + try { + if (f(std::forward(args)...) == 0) { + exit(0); + } + exit(1); + } catch (const std::exception &ex) { + SPDLOG_ERROR("process failed: {}", ex.what()); + exit(1); + } + default: + return pid; + } +} - ::apsi::ThreadPoolMgr::SetThreadCount(options.threads); - APSI_LOG_INFO("Setting thread count to " - << ::apsi::ThreadPoolMgr::GetThreadCount()); - signal(SIGINT, sigint_handler); +template +void RunDispatcher(const SenderOptions &options, + const std::shared_ptr &lctx, + Args &&...args) { + atomic stop = false; + SenderDispatcher dispatcher(std::forward(args)...); - if (!options.experimental_enable_bucketize || - options.experimental_bucket_cnt == 0 || - options.experimental_bucket_folder.empty()) { - // Check that the database file is valid - psi::apsi_wrapper::throw_if_file_invalid(options.db_file); - - // Try loading first as a SenderDB, then as a CSV file - shared_ptr<::apsi::sender::SenderDB> sender_db; - ::apsi::oprf::OPRFKey oprf_key; - if (!(sender_db = psi::apsi_wrapper::try_load_sender_db( - options.db_file, options.params_file, oprf_key)) && - !(sender_db = psi::apsi_wrapper::try_load_csv_db( - options.db_file, options.params_file, options.nonce_byte_count, - options.compress, oprf_key))) { - APSI_LOG_ERROR("Failed to create SenderDB: terminating"); - return -1; - } + if (options.channel == "zmq") { + // The dispatcher will run until stopped. + dispatcher.run(stop, options.zmq_port, options.streaming_result); + } else { + if (lctx) { + lctx->ConnectToMesh(); - // Print the total number of bin bundles and the largest number of bin - // bundles for any bundle index - uint32_t max_bin_bundles_per_bundle_idx = 0; - for (uint32_t bundle_idx = 0; - bundle_idx < sender_db->get_params().bundle_idx_count(); - bundle_idx++) { - max_bin_bundles_per_bundle_idx = max( - max_bin_bundles_per_bundle_idx, - static_cast(sender_db->get_bin_bundle_count(bundle_idx))); - } - APSI_LOG_INFO("SenderDB holds a total of " - << sender_db->get_bin_bundle_count() << " bin bundles across" - << sender_db->get_params().bundle_idx_count() - << " bundle indices"); - APSI_LOG_INFO("The largest bundle index holds " - << max_bin_bundles_per_bundle_idx << " bin bundles"); - - // Try to save the SenderDB if a save file was given - if (!options.sdb_out_file.empty() && - !psi::apsi_wrapper::try_save_sender_db(options.sdb_out_file, sender_db, - oprf_key)) { - return -1; - } + dispatcher.run(stop, lctx, options.streaming_result); + } else { + yacl::link::ContextDesc ctx_desc; + ctx_desc.parties.emplace_back( + "sender", fmt::format("{}:{}", options.yacl_sender_ip_addr, + options.yacl_sender_port)); + ctx_desc.parties.emplace_back( + "receiver", fmt::format("{}:{}", options.yacl_receiver_ip_addr, + options.yacl_receiver_port)); - if (options.save_db_only) { - APSI_LOG_INFO("Save db only. Exiting..."); - return 0; - } + std::shared_ptr lctx_ = + yacl::link::FactoryBrpc().CreateContext(ctx_desc, 0); - // Run the dispatcher - atomic stop = false; - SenderDispatcher dispatcher(sender_db, oprf_key); + lctx_->ConnectToMesh(); - if (options.channel == "zmq") { - // The dispatcher will run until stopped. - dispatcher.run(stop, options.zmq_port, options.streaming_result); - } else { - if (lctx) { - lctx->ConnectToMesh(); + dispatcher.run(stop, lctx_, options.streaming_result); + } + } +} - dispatcher.run(stop, lctx, options.streaming_result); - } else { - yacl::link::ContextDesc ctx_desc; - ctx_desc.parties.push_back( - {"sender", fmt::format("{}:{}", options.yacl_sender_ip_addr, - options.yacl_sender_port)}); - ctx_desc.parties.push_back( - {"receiver", fmt::format("{}:{}", options.yacl_receiver_ip_addr, - options.yacl_receiver_port)}); +void LogSenderDBInfo(shared_ptr<::apsi::sender::SenderDB> sender_db) { + // Print the total number of bin bundles and the largest number of bin + // bundles for any bundle index + uint32_t max_bin_bundles_per_bundle_idx = 0; + for (uint32_t bundle_idx = 0; + bundle_idx < sender_db->get_params().bundle_idx_count(); bundle_idx++) { + max_bin_bundles_per_bundle_idx = + max(max_bin_bundles_per_bundle_idx, + static_cast(sender_db->get_bin_bundle_count(bundle_idx))); + } + SPDLOG_INFO( + "SenderDB holds a total of {} bin bundles across {} bundle indices", + sender_db->get_bin_bundle_count(), + sender_db->get_params().bundle_idx_count()); + SPDLOG_INFO("The largest bundle index holds {} bin bundles", + max_bin_bundles_per_bundle_idx); +} - std::shared_ptr lctx_ = - yacl::link::FactoryBrpc().CreateContext(ctx_desc, 0); +void DealSingleDB(const SenderOptions &options, + const std::shared_ptr &lctx) { + // Check that the database file is valid + YACL_ENFORCE(!(options.db_file.empty() && options.source_file.empty()), + "Both old db_file and source_file are empty."); + + // Try loading first as a SenderDB, then as a CSV file + ::apsi::oprf::OPRFKey oprf_key; + shared_ptr<::apsi::sender::SenderDB> sender_db; + if (!options.db_file.empty()) { + sender_db = psi::apsi_wrapper::TryLoadSenderDB( + options.db_file, options.params_file, oprf_key); + YACL_ENFORCE(sender_db != nullptr, "load old sender_db from {} failed", + options.db_file); + } else { + sender_db = psi::apsi_wrapper::GenerateSenderDB( + options.source_file, options.params_file, options.nonce_byte_count, + options.compress, oprf_key); + YACL_ENFORCE(sender_db != nullptr, "create sender_db from {} failed", + options.source_file); + } - lctx_->ConnectToMesh(); + LogSenderDBInfo(sender_db); - dispatcher.run(stop, lctx_, options.streaming_result); - } - } - } else { - if (options.experimental_bucket_folder.empty()) { - APSI_LOG_ERROR("experimental_bucket_folder is not provided."); - return -1; - } + // Try to save the SenderDB if a save file was given + if (!options.sdb_out_file.empty()) { + YACL_ENFORCE(psi::apsi_wrapper::TrySaveSenderDB(options.sdb_out_file, + sender_db, oprf_key), + "Save sender_db to {} failed.", options.sdb_out_file); + } - psi::apsi_wrapper::throw_if_directory_invalid( - options.experimental_bucket_folder); + if (options.save_db_only) { + SPDLOG_INFO("Save db only. Exiting..."); + return; + } - // If experimental_bucket_folder is empty, then generate bucketized csv and - // db files. + // Run the dispatcher + RunDispatcher(options, lctx, sender_db, oprf_key); +} - shared_ptr<::apsi::sender::SenderDB> sender_db; - ::apsi::oprf::OPRFKey oprf_key; +// Based on testing, we found that multi-process processing is more +// efficient +void ProcessGroupParallel(size_t process_num, GroupDB &group_db) { + auto group_cnt = group_db.GetGroupNum(); + auto group_cnt_per_process = (group_cnt + process_num - 1) / process_num; - if (std::filesystem::is_empty(options.experimental_bucket_folder)) { - psi::apsi_wrapper::throw_if_file_invalid(options.db_file); + SPDLOG_INFO("{} process will be started", process_num); - CSVReader reader(options.db_file); - reader.bucketize(options.experimental_bucket_cnt, - options.experimental_bucket_folder); + std::vector pids; - MultiplexDiskCache disk_cache(options.experimental_bucket_folder, false); + for (size_t i = 0; i < process_num; i++) { + auto beg = group_cnt_per_process * i; + if (beg >= group_cnt) { + break; + } + auto end = std::min(group_cnt_per_process * (i + 1), group_cnt); + SPDLOG_INFO("start process {} for group: {}, {}", i, beg, end); - for (size_t i = 0; i < options.experimental_bucket_cnt; i++) { - std::string db_path = - GenerateDbPath(options.experimental_bucket_folder, i); - sender_db = psi::apsi_wrapper::try_load_csv_db( - disk_cache.GetPath(i), options.params_file, - options.nonce_byte_count, options.compress, oprf_key); + auto func = [&]() -> int { + for (size_t i = beg; i != end; ++i) { + group_db.GenerateGroup(i); + } + return 0; + }; - if (!sender_db) { - APSI_LOG_ERROR("Failed to create SenderDB: " << db_path - << " terminating"); - return -1; - } + pids.push_back(StartProcess(func)); + } - if (!psi::apsi_wrapper::try_save_sender_db(db_path, sender_db, - oprf_key)) { - APSI_LOG_ERROR("Failed to save SenderDB: " << db_path - << " terminating"); - return -1; - } + int status; + bool process_error = false; + for (auto &pid : pids) { + SPDLOG_INFO("wait for process {}", pid); + if (waitpid(pid, &status, 0) != -1) { + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + SPDLOG_ERROR("Process {} Failed to save SenderDB", pid); + process_error = true; } + } else { + SPDLOG_ERROR("Wait process {} Failed", pid); + process_error = true; } + } + YACL_ENFORCE(!process_error, "multi_process failed"); +} - if (options.save_db_only) { - APSI_LOG_INFO("Save db only. Exiting..."); - return 0; - } +void GenerateGroupBucketDB(GroupDB &group_db, size_t process_num) { + SPDLOG_INFO("start Bucketize csv file"); + group_db.DivideGroup(); + SPDLOG_INFO("end Bucketize csv file"); - // Run the dispatcher - atomic stop = false; - std::shared_ptr bucket_switcher = - std::make_shared( - options.experimental_bucket_folder, - options.experimental_bucket_cnt); - SenderDispatcher dispatcher(bucket_switcher); - - if (options.channel == "zmq") { - // The dispatcher will run until stopped. - dispatcher.run(stop, options.zmq_port, options.streaming_result); - } else { - if (lctx) { - lctx->ConnectToMesh(); + ProcessGroupParallel(process_num, group_db); - dispatcher.run(stop, lctx, options.streaming_result); - } else { - yacl::link::ContextDesc ctx_desc; - ctx_desc.parties.push_back( - {"sender", fmt::format("{}:{}", options.yacl_sender_ip_addr, - options.yacl_sender_port)}); - ctx_desc.parties.push_back( - {"receiver", fmt::format("{}:{}", options.yacl_receiver_ip_addr, - options.yacl_receiver_port)}); + group_db.GenerateDone(); +} - std::shared_ptr lctx_ = - yacl::link::FactoryBrpc().CreateContext(ctx_desc, 0); +void DealGroupBucketDB(const SenderOptions &options, + const std::shared_ptr &lctx) { + YACL_ENFORCE(!options.experimental_bucket_folder.empty(), + "experimental_bucket_folder is not provided."); - lctx_->ConnectToMesh(); + if (!std::filesystem::exists(options.experimental_bucket_folder)) { + SPDLOG_INFO("Creating bucket folder {}", + options.experimental_bucket_folder); + std::filesystem::create_directories(options.experimental_bucket_folder); + } - dispatcher.run(stop, lctx_, options.streaming_result); - } - } + GroupDB group_db(options.source_file, options.experimental_bucket_folder, + options.experimental_bucket_group_cnt, + options.experimental_bucket_cnt, options.params_file, + options.compress); + + if (!group_db.IsDBGenerated()) { + YACL_ENFORCE(!options.source_file.empty() && + std::filesystem::exists(options.source_file), + "source file {} is not exist.", options.source_file); + + auto start = std::chrono::high_resolution_clock::now(); + SPDLOG_INFO("start Generate bucket DB"); + + GenerateGroupBucketDB(group_db, + options.experimental_db_generating_process_num); + + auto sum_duration = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - start) + .count(); + SPDLOG_INFO( + "end Generate bucket DB: {}ms, {}ms/bucket, {}h/10^6bucket", + sum_duration, + static_cast(sum_duration) / options.experimental_bucket_cnt, + static_cast(sum_duration) / options.experimental_bucket_cnt * + 1e6 / 1000 / 3600); + } + + if (options.save_db_only) { + SPDLOG_INFO("Save db only. Exiting..."); + return; + } + + // Run the dispatcher + RunDispatcher(options, lctx, group_db); +} + +int RunSender(const SenderOptions &options, + const std::shared_ptr &lctx) { + apsi::Log::SetConsoleDisabled(options.silent); + apsi::Log::SetLogFile(options.log_file); + apsi::Log::SetLogLevel(options.log_level); + + ::apsi::ThreadPoolMgr::SetThreadCount(options.threads); + SPDLOG_INFO("Setting thread count to {}", + ::apsi::ThreadPoolMgr::GetThreadCount()); + signal(SIGINT, sigint_handler); + + // single db + if (!options.experimental_enable_bucketize) { + DealSingleDB(options, lctx); + + } else { // bucket db + DealGroupBucketDB(options, lctx); } return 0; diff --git a/psi/apsi_wrapper/cli/entry.h b/psi/apsi_wrapper/cli/entry.h index 2c94ad9..cc0b2ff 100644 --- a/psi/apsi_wrapper/cli/entry.h +++ b/psi/apsi_wrapper/cli/entry.h @@ -76,6 +76,7 @@ struct SenderOptions { int yacl_receiver_port; std::string db_file; + std::string source_file; std::string params_file; std::string sdb_out_file; @@ -86,6 +87,8 @@ struct SenderOptions { bool experimental_enable_bucketize = false; size_t experimental_bucket_cnt; std::string experimental_bucket_folder; + int experimental_db_generating_process_num = 8; + int experimental_bucket_group_cnt = 1024; }; int RunReceiver(const ReceiverOptions& options, diff --git a/psi/apsi_wrapper/cli/sender_dispatcher.cc b/psi/apsi_wrapper/cli/sender_dispatcher.cc index dadee12..09d15c8 100644 --- a/psi/apsi_wrapper/cli/sender_dispatcher.cc +++ b/psi/apsi_wrapper/cli/sender_dispatcher.cc @@ -39,6 +39,7 @@ using namespace std::chrono_literals; namespace psi::apsi_wrapper::cli { + SenderDispatcher::SenderDispatcher( std::shared_ptr<::apsi::sender::SenderDB> sender_db, ::apsi::oprf::OPRFKey oprf_key) @@ -78,7 +79,28 @@ SenderDispatcher::SenderDispatcher( LoadBucket(); } +SenderDispatcher::SenderDispatcher(GroupDB &group_db) : group_db_(&group_db) { + auto bucket_num = group_db_->GetBucketNum(); + for (size_t i = 0; i != bucket_num; ++i) { + SetBucketIdx(i); + if (sender_db_ != nullptr) { + break; + } + } + + YACL_ENFORCE(sender_db_ != nullptr, + "Can not found a valid bucket, terminated."); +} + void SenderDispatcher::SetBucketIdx(size_t idx) { + if (group_db_ != nullptr) { + auto item = group_db_->GetBucketDB(idx); + + sender_db_ = item.sender_db; + oprf_key_ = item.oprf_key; + return; + } + if (!bucket_db_switcher_) { return; } @@ -333,6 +355,35 @@ void SenderDispatcher::dispatch_query( SetBucketIdx(query_request->bucket_idx); + auto send_func = [&sop](::apsi::network::Channel &c, + ::apsi::Response response) { + auto nsop_response = + std::make_unique<::apsi::network::ZMQSenderOperationResponse>(); + nsop_response->sop_response = std::move(response); + nsop_response->client_id = sop->client_id; + + // We know for sure that the channel is a SenderChannel so use + // static_cast + static_cast<::apsi::network::ZMQSenderChannel &>(c).send( + std::move(nsop_response)); + }; + + if (sender_db_ == nullptr) { + ::apsi::QueryResponse response_query = + std::make_unique<::apsi::QueryResponse::element_type>(); + response_query->package_count = 0; + try { + send_func(chl, std::move(response_query)); + } catch (const std::exception &ex) { + APSI_LOG_ERROR( + "Failed to send response to query request; function threw an " + "exception: " + << ex.what()); + throw; + } + return; + } + // Create the Query object apsi::sender::Query query(std::move(query_request), sender_db_); @@ -341,17 +392,7 @@ void SenderDispatcher::dispatch_query( Sender::RunQuery( query, chl, streaming_result, // Lambda function for sending the query response - [&sop](::apsi::network::Channel &c, ::apsi::Response response) { - auto nsop_response = - std::make_unique<::apsi::network::ZMQSenderOperationResponse>(); - nsop_response->sop_response = std::move(response); - nsop_response->client_id = sop->client_id; - - // We know for sure that the channel is a SenderChannel so use - // static_cast - static_cast<::apsi::network::ZMQSenderChannel &>(c).send( - std::move(nsop_response)); - }, + send_func, // Lambda function for sending the result parts [&sop](::apsi::network::Channel &c, ::apsi::ResultPart rp) { auto nrp = std::make_unique(); @@ -380,6 +421,24 @@ void SenderDispatcher::dispatch_query( SetBucketIdx(query_request->bucket_idx); + auto send_func = Sender::BasicSend<::apsi::Response::element_type>; + + if (sender_db_ == nullptr) { + ::apsi::QueryResponse response_query = + std::make_unique<::apsi::QueryResponse::element_type>(); + response_query->package_count = 0; + try { + send_func(chl, std::move(response_query)); + } catch (const std::exception &ex) { + APSI_LOG_ERROR( + "Failed to send response to query request; function threw an " + "exception: " + << ex.what()); + throw; + } + return; + } + // Create the Query object apsi::sender::Query query(std::move(query_request), sender_db_); diff --git a/psi/apsi_wrapper/cli/sender_dispatcher.h b/psi/apsi_wrapper/cli/sender_dispatcher.h index 0dc13d8..356c373 100644 --- a/psi/apsi_wrapper/cli/sender_dispatcher.h +++ b/psi/apsi_wrapper/cli/sender_dispatcher.h @@ -30,6 +30,7 @@ #include "apsi/sender_db.h" #include "psi/apsi_wrapper/utils/bucket.h" +#include "psi/apsi_wrapper/utils/group_db.h" #include "psi/apsi_wrapper/yacl_channel.h" namespace psi::apsi_wrapper::cli { @@ -61,6 +62,8 @@ class SenderDispatcher { SenderDispatcher(std::shared_ptr bucket_db_switcher); + SenderDispatcher(GroupDB &group_db); + /** Run the dispatcher on the given port. */ @@ -71,6 +74,8 @@ class SenderDispatcher { bool streaming_result = true); private: + GroupDB *group_db_ = nullptr; + std::shared_ptr<::apsi::sender::SenderDB> sender_db_; ::apsi::oprf::OPRFKey oprf_key_; diff --git a/psi/apsi_wrapper/integration_test.cc b/psi/apsi_wrapper/integration_test.cc deleted file mode 100644 index da0dab0..0000000 --- a/psi/apsi_wrapper/integration_test.cc +++ /dev/null @@ -1,797 +0,0 @@ -// Copyright 2024 Ant Group Co., Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -// STD -#include - -// APSI -#include "apsi/log.h" -#include "apsi/network/stream_channel.h" -#include "apsi/oprf/oprf_sender.h" -#include "apsi/sender_db.h" -#include "apsi/thread_pool_mgr.h" - -#include "psi/apsi_wrapper/receiver.h" -#include "psi/apsi_wrapper/sender.h" -#include "psi/apsi_wrapper/test_utils.h" - -// Google Test -#include "gtest/gtest.h" - -using namespace std; -using namespace apsi; -using namespace apsi::network; -using namespace apsi::util; -using namespace apsi::oprf; -using namespace seal; - -namespace psi::apsi_wrapper { -namespace { -void RunUnlabeledTest(size_t sender_size, - vector> client_total_and_int_sizes, - const PSIParams ¶ms, size_t num_threads, - bool use_different_compression = false) { - Log::SetConsoleDisabled(true); - Log::SetLogLevel(Log::Level::info); - - ThreadPoolMgr::SetThreadCount(num_threads); - - vector sender_items; - for (size_t i = 0; i < sender_size; i++) { - sender_items.push_back({i + 1, i + 1}); - } - - auto sender_db = make_shared<::apsi::sender::SenderDB>(params, 0); - auto oprf_key = sender_db->get_oprf_key(); - - sender_db->set_data(sender_items); - - auto seal_context = sender_db->get_seal_context(); - - stringstream ss; - StreamChannel chl(ss); - - Receiver receiver(params); - - for (auto client_total_and_int_size : client_total_and_int_sizes) { - auto client_size = client_total_and_int_size.first; - auto int_size = client_total_and_int_size.second; - ASSERT_TRUE(int_size <= client_size); - - vector recv_int_items = rand_subset(sender_items, int_size); - vector recv_items; - for (auto item : recv_int_items) { - recv_items.push_back(item); - } - for (size_t i = int_size; i < client_size; i++) { - recv_items.push_back({i + 1, ~(i + 1)}); - } - - // Create the OPRF receiver - oprf::OPRFReceiver oprf_receiver = Receiver::CreateOPRFReceiver(recv_items); - Request oprf_request = Receiver::CreateOPRFRequest(oprf_receiver); - - // Send the OPRF request - ASSERT_NO_THROW(chl.send(move(oprf_request))); - size_t bytes_sent = chl.bytes_sent(); - - // Receive the OPRF request and process response - OPRFRequest oprf_request2 = to_oprf_request( - chl.receive_operation(nullptr, SenderOperationType::sop_oprf)); - size_t bytes_received = chl.bytes_received(); - ASSERT_EQ(bytes_sent, bytes_received); - ASSERT_NO_THROW(Sender::RunOPRF(oprf_request2, oprf_key, chl)); - - // Receive OPRF response - OPRFResponse oprf_response = to_oprf_response(chl.receive_response()); - vector hashed_recv_items; - vector label_keys; - tie(hashed_recv_items, label_keys) = - Receiver::ExtractHashes(oprf_response, oprf_receiver); - ASSERT_EQ(hashed_recv_items.size(), recv_items.size()); - - // Create query and send - pair recv_query_pair = - receiver.create_query(hashed_recv_items); - - QueryRequest recv_query = to_query_request(move(recv_query_pair.first)); - compr_mode_type expected_compr_mode = recv_query->compr_mode; - - if (use_different_compression && - Serialization::IsSupportedComprMode(compr_mode_type::zlib) && - Serialization::IsSupportedComprMode(compr_mode_type::zstd)) { - if (recv_query->compr_mode == compr_mode_type::zstd) { - recv_query->compr_mode = compr_mode_type::zlib; - expected_compr_mode = compr_mode_type::zlib; - } else { - recv_query->compr_mode = compr_mode_type::zstd; - expected_compr_mode = compr_mode_type::zstd; - } - } - - ::apsi::receiver::IndexTranslationTable itt = move(recv_query_pair.second); - chl.send(move(recv_query)); - - // Receive the query and process response - QueryRequest sender_query = - to_query_request(chl.receive_operation(seal_context)); - ::apsi::sender::Query query(move(sender_query), sender_db); - ASSERT_EQ(expected_compr_mode, query.compr_mode()); - ASSERT_NO_THROW(Sender::RunQuery(query, chl)); - - // Receive query response - QueryResponse query_response = to_query_response(chl.receive_response()); - uint32_t package_count = query_response->package_count; - - // Receive all result parts and process result - vector rps; - while (package_count--) { - ASSERT_NO_THROW( - rps.push_back(chl.receive_result(receiver.get_seal_context()))); - } - auto query_result = receiver.process_result(label_keys, itt, rps); - - verify_unlabeled_results(query_result, recv_items, recv_int_items); - } -} - -void RunLabeledTest(size_t sender_size, - vector> client_total_and_int_sizes, - const PSIParams ¶ms, size_t num_threads) { - Log::SetConsoleDisabled(true); - Log::SetLogLevel(Log::Level::info); - - ThreadPoolMgr::SetThreadCount(num_threads); - - vector> sender_items; - for (size_t i = 0; i < sender_size; i++) { - sender_items.push_back(make_pair( - Item(i + 1, i + 1), - create_label(seal::util::safe_cast((i + 1) & 0xFF), - 10))); - } - - auto sender_db = make_shared<::apsi::sender::SenderDB>(params, 10, 4, true); - sender_db->set_data(sender_items); - auto oprf_key = sender_db->get_oprf_key(); - - auto seal_context = sender_db->get_seal_context(); - - stringstream ss; - StreamChannel chl(ss); - - Receiver receiver(params); - - for (auto client_total_and_int_size : client_total_and_int_sizes) { - auto client_size = client_total_and_int_size.first; - auto int_size = client_total_and_int_size.second; - ASSERT_TRUE(int_size <= client_size); - - vector recv_int_items = rand_subset(sender_items, int_size); - vector recv_items; - for (auto item : recv_int_items) { - recv_items.push_back(item); - } - for (size_t i = int_size; i < client_size; i++) { - recv_items.push_back({i + 1, ~(i + 1)}); - } - - // Create the OPRF receiver - oprf::OPRFReceiver oprf_receiver = Receiver::CreateOPRFReceiver(recv_items); - Request oprf_request = Receiver::CreateOPRFRequest(oprf_receiver); - - // Send the OPRF request - ASSERT_NO_THROW(chl.send(move(oprf_request))); - size_t bytes_sent = chl.bytes_sent(); - - // Receive the OPRF request and process response - OPRFRequest oprf_request2 = to_oprf_request( - chl.receive_operation(nullptr, SenderOperationType::sop_oprf)); - size_t bytes_received = chl.bytes_received(); - ASSERT_EQ(bytes_sent, bytes_received); - ASSERT_NO_THROW(Sender::RunOPRF(oprf_request2, oprf_key, chl)); - - // Receive OPRF response - OPRFResponse oprf_response = to_oprf_response(chl.receive_response()); - vector hashed_recv_items; - vector label_keys; - tie(hashed_recv_items, label_keys) = - Receiver::ExtractHashes(oprf_response, oprf_receiver); - ASSERT_EQ(hashed_recv_items.size(), recv_items.size()); - - // Create query and send - pair recv_query = - receiver.create_query(hashed_recv_items); - ::apsi::receiver::IndexTranslationTable itt = move(recv_query.second); - chl.send(move(recv_query.first)); - - // Receive the query and process response - QueryRequest sender_query = - to_query_request(chl.receive_operation(seal_context)); - ::apsi::sender::Query query(move(sender_query), sender_db); - ASSERT_NO_THROW(Sender::RunQuery(query, chl)); - - // Receive query response - QueryResponse query_response = to_query_response(chl.receive_response()); - uint32_t package_count = query_response->package_count; - - // Receive all result parts and process result - vector rps; - while (package_count--) { - ASSERT_NO_THROW( - rps.push_back(chl.receive_result(receiver.get_seal_context()))); - } - auto query_result = receiver.process_result(label_keys, itt, rps); - - verify_labeled_results(query_result, recv_items, recv_int_items, - sender_items); - } -} -} // namespace - -TEST(StreamSenderReceiverTests, UnlabeledEmpty1) { - size_t sender_size = 0; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledEmpty2) { - size_t sender_size = 0; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledEmptyMultiThreaded1) { - size_t sender_size = 0; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params1(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledEmptyMultiThreaded2) { - size_t sender_size = 0; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params2(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledSingle1) { - size_t sender_size = 1; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledSingle2) { - size_t sender_size = 1; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledSingleMultiThreaded1) { - size_t sender_size = 1; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params1(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledSingleMultiThreaded2) { - size_t sender_size = 1; - RunUnlabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params2(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledSmall1) { - size_t sender_size = 10; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledSmall2) { - size_t sender_size = 10; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledSmallDifferentCompression1) { - size_t sender_size = 10; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params1(), 1, true); -} - -TEST(StreamSenderReceiverTests, UnlabeledSmallDifferentCompression2) { - size_t sender_size = 10; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params2(), 1, true); -} - -TEST(StreamSenderReceiverTests, UnlabeledSmallMultiThreaded1) { - size_t sender_size = 10; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params1(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledSmallMultiThreaded2) { - size_t sender_size = 10; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params2(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledMedium1) { - size_t sender_size = 500; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledMedium2) { - size_t sender_size = 500; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledMediumMultiThreaded1) { - size_t sender_size = 500; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params1(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledMediumMultiThreaded2) { - size_t sender_size = 500; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params2(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledLarge1) { - size_t sender_size = 4000; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledLarge2) { - size_t sender_size = 4000; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, UnlabeledLargeMultiThreaded1) { - size_t sender_size = 4000; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params1(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, UnlabeledLargeMultiThreaded2) { - size_t sender_size = 4000; - RunUnlabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params2(), thread::hardware_concurrency()); -} - -// NOTE(junfeng) huge dataset is disabled to reduce ci time. -// TEST(StreamSenderReceiverTests, UnlabeledHugeMultiThreaded1) { -// size_t sender_size = 50000; -// RunUnlabeledTest(sender_size, -// {{0, 0}, -// {1, 0}, -// {5000, 100}, -// {5000, 5000}, -// {10000, 0}, -// {10000, 5000}, -// {10000, 10000}, -// {50000, 50000}}, -// create_huge_params1(), thread::hardware_concurrency()); - -// sender_size = 1'000'000; -// RunUnlabeledTest(sender_size, {{10000, 10000}}, create_huge_params1(), -// thread::hardware_concurrency()); -// } - -// TEST(StreamSenderReceiverTests, UnlabeledHugeMultiThreaded2) { -// size_t sender_size = 50000; -// RunUnlabeledTest(sender_size, -// {{0, 0}, -// {1, 0}, -// {5000, 100}, -// {5000, 5000}, -// {10000, 0}, -// {10000, 5000}, -// {10000, 10000}, -// {50000, 50000}}, -// create_huge_params2(), thread::hardware_concurrency()); - -// sender_size = 1'000'000; -// RunUnlabeledTest(sender_size, {{10000, 10000}}, create_huge_params2(), -// thread::hardware_concurrency()); -// } - -TEST(StreamSenderReceiverTests, LabeledEmpty1) { - size_t sender_size = 0; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledEmpty2) { - size_t sender_size = 0; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledEmptyMultiThreaded1) { - size_t sender_size = 0; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params1(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledEmptyMultiThreaded2) { - size_t sender_size = 0; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}}, create_params2(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledSingle1) { - size_t sender_size = 1; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledSingle2) { - size_t sender_size = 1; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledSingleMultiThreaded1) { - size_t sender_size = 1; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params1(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledSingleMultiThreaded2) { - size_t sender_size = 1; - RunLabeledTest(sender_size, {{0, 0}, {1, 0}, {1, 1}}, create_params2(), - thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledSmall1) { - size_t sender_size = 10; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledSmall2) { - size_t sender_size = 10; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledSmallMultiThreaded1) { - size_t sender_size = 10; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params1(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledSmallMultiThreaded2) { - size_t sender_size = 10; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {5, 0}, - {5, 2}, - {5, 5}, - {10, 0}, - {10, 5}, - {10, 10}}, - create_params2(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledMedium1) { - size_t sender_size = 500; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledMedium2) { - size_t sender_size = 500; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledMediumMultiThreaded1) { - size_t sender_size = 500; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params1(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledMediumMultiThreaded2) { - size_t sender_size = 500; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {1, 1}, - {50, 10}, - {50, 50}, - {100, 1}, - {100, 50}, - {100, 100}}, - create_params2(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledLarge1) { - size_t sender_size = 4000; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params1(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledLarge2) { - size_t sender_size = 4000; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params2(), 1); -} - -TEST(StreamSenderReceiverTests, LabeledLargeMultiThreaded1) { - size_t sender_size = 4000; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params1(), thread::hardware_concurrency()); -} - -TEST(StreamSenderReceiverTests, LabeledLargeMultiThreaded2) { - size_t sender_size = 4000; - RunLabeledTest(sender_size, - {{0, 0}, - {1, 0}, - {500, 10}, - {500, 50}, - {500, 500}, - {1000, 0}, - {1000, 1}, - {1000, 500}, - {1000, 999}, - {1000, 1000}}, - create_params2(), thread::hardware_concurrency()); -} - -// NOTE(junfeng) huge dataset is disabled to reduce ci time. -// TEST(StreamSenderReceiverTests, LabeledHugeMultiThreaded1) { -// size_t sender_size = 50000; -// RunLabeledTest(sender_size, -// {{0, 0}, -// {1, 0}, -// {5000, 100}, -// {5000, 5000}, -// {10000, 0}, -// {10000, 5000}, -// {10000, 10000}, -// {50000, 50000}}, -// create_huge_params1(), thread::hardware_concurrency()); - -// sender_size = 1'000'000; -// RunLabeledTest(sender_size, {{10000, 10000}}, create_huge_params1(), -// thread::hardware_concurrency()); -// } - -// TEST(StreamSenderReceiverTests, LabeledHugeMultiThreaded2) { -// size_t sender_size = 50000; -// RunLabeledTest(sender_size, -// {{0, 0}, -// {1, 0}, -// {5000, 100}, -// {5000, 5000}, -// {10000, 0}, -// {10000, 5000}, -// {10000, 10000}, -// {50000, 50000}}, -// create_huge_params2(), thread::hardware_concurrency()); - -// sender_size = 1'000'000; -// RunLabeledTest(sender_size, {{10000, 10000}}, create_huge_params2(), -// thread::hardware_concurrency()); -// } -} // namespace psi::apsi_wrapper diff --git a/psi/apsi_wrapper/sender.cc b/psi/apsi_wrapper/sender.cc index 66369c9..1a409ed 100644 --- a/psi/apsi_wrapper/sender.cc +++ b/psi/apsi_wrapper/sender.cc @@ -40,6 +40,7 @@ using namespace std; namespace psi::apsi_wrapper { + void Sender::RunParams( const ::apsi::ParamsRequest ¶ms_request, shared_ptr<::apsi::sender::SenderDB> sender_db, @@ -150,8 +151,26 @@ void Sender::RunQuery( ::apsi::ThreadPoolMgr tpm; + auto send_func = BasicSend<::apsi::Response::element_type>; + // Acquire read lock on ::apsi::sender::SenderDB auto sender_db = query.sender_db(); + if (sender_db == nullptr) { + ::apsi::QueryResponse response_query = + make_unique<::apsi::QueryResponse::element_type>(); + response_query->package_count = 0; + try { + send_func(chl, std::move(response_query)); + } catch (const exception &ex) { + APSI_LOG_ERROR( + "Failed to send response to query request; function threw an " + "exception: " + << ex.what()); + throw; + } + return; + } + auto sender_db_lock = sender_db->get_reader_lock(); STOPWATCH(::apsi::util::sender_stopwatch, "Sender::RunQuery"); diff --git a/psi/apsi_wrapper/utils/BUILD.bazel b/psi/apsi_wrapper/utils/BUILD.bazel index 24d47ff..4343b32 100644 --- a/psi/apsi_wrapper/utils/BUILD.bazel +++ b/psi/apsi_wrapper/utils/BUILD.bazel @@ -22,6 +22,7 @@ psi_cc_library( hdrs = ["common.h"], deps = [ "@com_github_microsoft_apsi//:apsi", + "@org_apache_arrow//:arrow", ], ) @@ -45,6 +46,16 @@ psi_cc_library( ], ) +psi_cc_library( + name = "group_db", + srcs = ["group_db.cc"], + hdrs = ["group_db.h"], + deps = [ + ":csv_reader", + ":sender_db", + ], +) + psi_cc_library( name = "bucket", srcs = ["bucket.cc"], diff --git a/psi/apsi_wrapper/utils/bucket.cc b/psi/apsi_wrapper/utils/bucket.cc index af0d9db..b9eb91d 100644 --- a/psi/apsi_wrapper/utils/bucket.cc +++ b/psi/apsi_wrapper/utils/bucket.cc @@ -14,12 +14,11 @@ #include "psi/apsi_wrapper/utils/bucket.h" -#include - #include #include #include "apsi/log.h" +#include "fmt/format.h" #include "psi/apsi_wrapper/utils/sender_db.h" @@ -47,7 +46,7 @@ void BucketSenderDbSwitcher::SetBucketIdx(size_t idx, bool forced_to_reload) { std::string db_path = GenerateDbPath(parent_folder_, idx); - sender_db_ = try_load_sender_db(db_path, "", oprf_key_); + sender_db_ = TryLoadSenderDB(db_path, "", oprf_key_); if (!sender_db_) { APSI_LOG_ERROR("Failed to create SenderDB in BucketSenderDbSwitcher."); diff --git a/psi/apsi_wrapper/utils/common.cc b/psi/apsi_wrapper/utils/common.cc index 163fa59..c0813b3 100644 --- a/psi/apsi_wrapper/utils/common.cc +++ b/psi/apsi_wrapper/utils/common.cc @@ -53,6 +53,62 @@ const string Colors::RedBold = "\033[1;31m"; const string Colors::GreenBold = "\033[1;32m"; const string Colors::Reset = "\033[0m"; +std::shared_ptr MakeArrowCsvReader( + const std::string &file_name, std::vector column_names) { + std::unordered_map> + column_types; + for (auto &col : column_names) { + column_types[col] = arrow::utf8(); + } + return MakeArrowCsvReader(file_name, std::move(column_types)); +} + +bool IsDuplicated(const LabeledData &labeled_data) { + std::unordered_set labeled_data_set; + for (auto &label : labeled_data) { + labeled_data_set.insert(label.first.to_string()); + } + + return labeled_data.size() != labeled_data_set.size(); +} + +bool IsDuplicated(const UnlabeledData &unlabeled_data) { + std::unordered_set data_set; + for (auto &data : unlabeled_data) { + data_set.insert(data.to_string()); + } + + return data_set.size() != unlabeled_data.size(); +} + +bool IsDuplicated(const DBData &db_data) { + if (std::holds_alternative(db_data)) { + return IsDuplicated(std::get(db_data)); + } else { + return IsDuplicated(std::get(db_data)); + } +} + +std::shared_ptr MakeArrowCsvReader( + const std::string &file_name, + std::unordered_map> + column_types) { + auto infile = + arrow::io::ReadableFile::Open(file_name, arrow::default_memory_pool()) + .ValueOrDie(); + + arrow::io::IOContext io_context = arrow::io::default_io_context(); + auto read_options = arrow::csv::ReadOptions::Defaults(); + + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + convert_options.column_types = std::move(column_types); + + return arrow::csv::StreamingReader::Make(io_context, infile, read_options, + parse_options, convert_options) + .ValueOrDie(); +} + void throw_if_file_invalid(const string &file_name) { fs::path file(file_name); @@ -79,7 +135,7 @@ void throw_if_directory_invalid(const std::string &dir_name) { } } -std::unique_ptr<::apsi::PSIParams> build_psi_params( +std::unique_ptr<::apsi::PSIParams> BuildPsiParams( const std::string ¶ms_file) { string params_json; @@ -131,6 +187,7 @@ int print_intersection_results( } std::stringstream csv_output; + std::string csv_header = "key"; int match_cnt = 0; for (size_t i = 0; i < orig_items.size(); i++) { std::stringstream msg; @@ -143,6 +200,7 @@ int print_intersection_results( msg << Colors::GreenBold << intersection[i].label.to_string() << Colors::Reset; csv_output << "," << intersection[i].label.to_string(); + csv_header += ",value"; } csv_output << endl; APSI_LOG_INFO(msg.str()); @@ -158,6 +216,7 @@ int print_intersection_results( ofs << csv_output.str(); } else { std::ofstream ofs(out_file); + ofs << csv_header << endl; ofs << csv_output.str(); } diff --git a/psi/apsi_wrapper/utils/common.h b/psi/apsi_wrapper/utils/common.h index 8018826..fd7678b 100644 --- a/psi/apsi_wrapper/utils/common.h +++ b/psi/apsi_wrapper/utils/common.h @@ -21,6 +21,9 @@ #include "apsi/match_record.h" #include "apsi/psi_params.h" +#include "arrow/csv/api.h" +#include "arrow/datum.h" +#include "arrow/io/api.h" namespace psi::apsi_wrapper { @@ -30,6 +33,8 @@ using LabeledData = std::vector>; using DBData = std::variant; +bool IsDuplicated(const DBData &db_data); + /** Throw an exception if the given file is invalid. */ @@ -37,7 +42,7 @@ void throw_if_file_invalid(const std::string &file_name); void throw_if_directory_invalid(const std::string &dir_name); -std::unique_ptr<::apsi::PSIParams> build_psi_params( +std::unique_ptr<::apsi::PSIParams> BuildPsiParams( const std::string ¶ms_file); int print_intersection_results( @@ -46,4 +51,12 @@ int print_intersection_results( const std::vector<::apsi::receiver::MatchRecord> &intersection, const std::string &out_file, bool append_to_outfile = false); +std::shared_ptr MakeArrowCsvReader( + const std::string &file_name, std::vector column_names); + +std::shared_ptr MakeArrowCsvReader( + const std::string &file_name, + std::unordered_map> + column_types); + } // namespace psi::apsi_wrapper diff --git a/psi/apsi_wrapper/utils/csv_reader.cc b/psi/apsi_wrapper/utils/csv_reader.cc index 5bc6685..c7f7402 100644 --- a/psi/apsi_wrapper/utils/csv_reader.cc +++ b/psi/apsi_wrapper/utils/csv_reader.cc @@ -17,13 +17,18 @@ #include "psi/apsi_wrapper/utils/csv_reader.h" +#include "fmt/format.h" + #include "psi/utils/multiplex_disk_cache.h" // STD #include #include +#include #include #include +#include +#include #include #include "arrow/array.h" @@ -42,97 +47,104 @@ using namespace apsi::util; namespace psi::apsi_wrapper { -CSVReader::CSVReader(const string& file_name) : file_name_(file_name) { - throw_if_file_invalid(file_name_); - - std::ifstream csv_file(file_name_); +std::vector GetCsvColumnNames(const std::string& filename) { + std::ifstream csv_file(filename); std::string line; - if (!std::getline(csv_file, line)) { - APSI_LOG_WARNING("Nothing to read in `" << file_name_ << "`"); - empty_file_ = true; - return; + YACL_ENFORCE(std::getline(csv_file, line), "Empty file."); + + static const std::vector valid_header = { + "key,value", + R"("key","value")", + "key", + R"("key")", + }; + auto iter = std::find(valid_header.begin(), valid_header.end(), line); + YACL_ENFORCE(iter != valid_header.end(), + "file {} has invalid header {} should be one of {}.", filename, + line, fmt::join(valid_header, ";")); + + std::vector column_names; + + column_names.emplace_back("key"); + if (iter - valid_header.begin() < 2) { + column_names.emplace_back("value"); } + SPDLOG_INFO("read file {} with header {}, column_names: {}", filename, line, + fmt::join(column_names, ",")); + return column_names; +} - infile_ = - arrow::io::ReadableFile::Open(file_name_, arrow::default_memory_pool()) - .ValueOrDie(); +ApsiCsvReader::ApsiCsvReader(const string& file_name) : file_name_(file_name) { + throw_if_file_invalid(file_name_); + + std::vector column_names = GetCsvColumnNames(file_name_); - arrow::io::IOContext io_context = arrow::io::default_io_context(); - auto read_options = arrow::csv::ReadOptions::Defaults(); - read_options.autogenerate_column_names = true; + for (auto& col : column_names) { + column_types_[col] = arrow::utf8(); + } - auto parse_options = arrow::csv::ParseOptions::Defaults(); - auto convert_options = arrow::csv::ConvertOptions::Defaults(); + reader_ = MakeArrowCsvReader(file_name_, column_names); +} - reader_ = arrow::csv::StreamingReader::Make(io_context, infile_, read_options, - parse_options, convert_options) - .ValueOrDie(); +std::shared_ptr ApsiCsvReader::schema() const { + return reader_->schema(); } -auto CSVReader::read() -> pair> { +auto ApsiCsvReader::read() -> pair> { int row_cnt = 0; DBData result; vector orig_items; + std::shared_ptr batch; bool result_type_decided = false; - if (empty_file_) { - APSI_LOG_WARNING("Nothing to read in `" << file_name_ << "`"); - return {UnlabeledData{}, {}}; - } - while (true) { // Attempt to read the first RecordBatch - arrow::Status status = reader_->ReadNext(&batch_); + arrow::Status status = reader_->ReadNext(&batch); if (!status.ok()) { APSI_LOG_ERROR("Read csv error."); } - if (batch_ == nullptr) { + if (batch == nullptr) { // Handle end of file break; } arrays_.clear(); - if (batch_->num_columns() > 2) { - SPDLOG_WARN( - "col cnt of csv file {} is greater than 2, so extra cols are " - "ignored.", - file_name_); - } - for (int i = 0; i < min(2, batch_->num_columns()); i++) { + for (int i = 0; i < min(2, batch->num_columns()); i++) { arrays_.emplace_back( - std::dynamic_pointer_cast(batch_->column(i))); + std::dynamic_pointer_cast(batch->column(i))); } - row_cnt += batch_->num_rows(); + row_cnt += batch->num_rows(); if (!result_type_decided) { result_type_decided = true; - if (batch_->num_columns() >= 2) { + if (batch->num_columns() >= 2) { result = LabeledData{}; } else { result = UnlabeledData{}; } } - for (int i = 0; i < batch_->num_rows(); i++) { - orig_items.push_back(std::string(arrays_[0]->Value(i))); + for (int i = 0; i < batch->num_rows(); i++) { + orig_items.emplace_back(arrays_[0]->Value(i)); if (holds_alternative(result)) { - get(result).push_back(std::string(arrays_[0]->Value(i))); + get(result).emplace_back( + std::string(arrays_[0]->Value(i))); } else if (holds_alternative(result)) { Label label; label.reserve(arrays_[1]->Value(i).size()); copy(arrays_[1]->Value(i).begin(), arrays_[1]->Value(i).end(), back_inserter(label)); - get(result).push_back( - make_pair(std::string(arrays_[0]->Value(i)), label)); + get(result).emplace_back(std::string(arrays_[0]->Value(i)), + label); } else { // Something is terribly wrong APSI_LOG_ERROR("Critical error reading data"); @@ -141,69 +153,70 @@ auto CSVReader::read() -> pair> { } } - if (row_cnt == 0) { - APSI_LOG_WARNING("Nothing to read in `" << file_name_ << "`"); - return {UnlabeledData{}, {}}; - } else { - SPDLOG_INFO("Read csv file {}, row cnt is {}", file_name_, row_cnt); - return {std::move(result), std::move(orig_items)}; - } + YACL_ENFORCE(row_cnt != 0, "empty file : {}", file_name_); + YACL_ENFORCE(orig_items.size() == std::unordered_set( + orig_items.begin(), orig_items.end()) + .size(), + "source file {} has duplicated keys", file_name_); + SPDLOG_INFO("Read csv file {}, row cnt is {}", file_name_, row_cnt); + + return {std::move(result), std::move(orig_items)}; } -void CSVReader::bucketize(size_t bucket_cnt, const std::string& bucket_folder) { - throw_if_directory_invalid(bucket_folder); +void ApsiCsvReader::bucketize(size_t bucket_cnt, + const std::string& bucket_folder) { + if (!std::filesystem::exists(bucket_folder)) { + SPDLOG_INFO("create bucket folder {}", bucket_folder); + std::filesystem::create_directories(bucket_folder); + } MultiplexDiskCache disk_cache(bucket_folder, false); std::vector> bucket_os_vec; disk_cache.CreateOutputStreams(bucket_cnt, &bucket_os_vec); + for (auto& out : bucket_os_vec) { + if (reader_->schema()->num_fields() == 1) { + out->Write("key\n"); - if (empty_file_) { - for (const auto& out : bucket_os_vec) { - out->Flush(); + } else { + out->Write("key,value\n"); } - - bucket_os_vec.clear(); - - return; } - int row_cnt = 0; + std::shared_ptr batch; while (true) { // Attempt to read the first RecordBatch - arrow::Status status = reader_->ReadNext(&batch_); + arrow::Status status = reader_->ReadNext(&batch); if (!status.ok()) { APSI_LOG_ERROR("Read csv error."); } - if (batch_ == nullptr) { + if (batch == nullptr) { // Handle end of file break; } arrays_.clear(); - if (batch_->num_columns() > 2) { + if (batch->num_columns() > 2) { SPDLOG_WARN( "col cnt of csv file {} is greater than 2, so extra cols are " "ignored.", file_name_); } - for (int i = 0; i < min(2, batch_->num_columns()); i++) { + for (int i = 0; i < min(2, batch->num_columns()); i++) { arrays_.emplace_back( - std::dynamic_pointer_cast(batch_->column(i))); + std::dynamic_pointer_cast(batch->column(i))); } - row_cnt += batch_->num_rows(); - - for (int i = 0; i < batch_->num_rows(); i++) { + for (int i = 0; i < batch->num_rows(); i++) { std::string item(arrays_[0]->Value(i)); int bucket_idx = std::hash()(item) % bucket_cnt; auto& out = bucket_os_vec[bucket_idx]; - if (batch_->num_columns() == 1) { + if (batch->num_columns() == 1) { out->Write(fmt::format("\"{}\"\n", item)); } else { out->Write(fmt::format("\"{}\",\"{}\"\n", item, arrays_[1]->Value(i))); @@ -211,8 +224,6 @@ void CSVReader::bucketize(size_t bucket_cnt, const std::string& bucket_folder) { } } - (void)row_cnt; - for (const auto& out : bucket_os_vec) { out->Flush(); } @@ -220,4 +231,86 @@ void CSVReader::bucketize(size_t bucket_cnt, const std::string& bucket_folder) { bucket_os_vec.clear(); } +void ApsiCsvReader::GroupBucketize(size_t bucket_cnt, + const std::string& bucket_folder, + size_t group_cnt, + MultiplexDiskCache& disk_cache) { + if (!std::filesystem::exists(bucket_folder)) { + SPDLOG_INFO("create bucket folder {}", bucket_folder); + std::filesystem::create_directories(bucket_folder); + } + + std::vector> bucket_group_vec; + disk_cache.CreateOutputStreams(group_cnt, &bucket_group_vec); + for (auto& out : bucket_group_vec) { + if (reader_->schema()->num_fields() == 1) { + out->Write("bucket_id,key\n"); + + } else if (reader_->schema()->num_fields() == 2) { + out->Write("bucket_id,key,value\n"); + } else { + YACL_THROW( + "col cnt {} of csv file {} is not 1 or 2, so extra cols are " + "ignored.", + reader_->schema()->num_fields(), file_name_); + } + } + + bool empty = true; + std::shared_ptr batch; + auto per_group_bucket = (bucket_cnt + group_cnt - 1) / group_cnt; + SPDLOG_INFO("{} group, {} bucket, per_group{}", group_cnt, bucket_cnt, + per_group_bucket); + + while (true) { + // Attempt to read the first RecordBatch + arrow::Status status = reader_->ReadNext(&batch); + + YACL_ENFORCE(status.ok(), "Read csv error."); + + if (batch == nullptr) { + // Handle end of file + break; + } + + SPDLOG_INFO("process {} lines", batch->num_rows()); + + arrays_.clear(); + if (batch->num_columns() > 2) { + SPDLOG_WARN( + "col cnt of csv file {} is greater than 2, so extra cols are " + "ignored.", + file_name_); + } + + for (int i = 0; i < min(2, batch->num_columns()); i++) { + arrays_.emplace_back( + std::dynamic_pointer_cast(batch->column(i))); + } + + for (int i = 0; i < batch->num_rows(); i++) { + empty = false; + std::string item(arrays_[0]->Value(i)); + int bucket_idx = std::hash()(item) % bucket_cnt; + int group_idx = bucket_idx / per_group_bucket; + auto& out = bucket_group_vec[group_idx]; + + if (batch->num_columns() == 1) { + out->Write(fmt::format("{},\"{}\"\n", bucket_idx, item)); + } else { + out->Write(fmt::format("{},\"{}\",\"{}\"\n", bucket_idx, item, + arrays_[1]->Value(i))); + } + } + } + + YACL_ENFORCE(!empty, "empty file : {}", file_name_); + + for (const auto& out : bucket_group_vec) { + out->Flush(); + } + + bucket_group_vec.clear(); +} + } // namespace psi::apsi_wrapper diff --git a/psi/apsi_wrapper/utils/csv_reader.h b/psi/apsi_wrapper/utils/csv_reader.h index 2e999ab..89ab1c2 100644 --- a/psi/apsi_wrapper/utils/csv_reader.h +++ b/psi/apsi_wrapper/utils/csv_reader.h @@ -35,32 +35,35 @@ #include "arrow/io/api.h" #include "psi/apsi_wrapper/utils/common.h" +#include "psi/utils/multiplex_disk_cache.h" namespace psi::apsi_wrapper { /** Simple CSV file parser */ -class CSVReader { +class ApsiCsvReader { public: - explicit CSVReader(const std::string& file_name); + explicit ApsiCsvReader(const std::string& file_name); std::pair> read(); void bucketize(size_t bucket_cnt, const std::string& bucket_folder); + void GroupBucketize(size_t bucket_cnt, const std::string& bucket_folder, + size_t group_cnt, MultiplexDiskCache& disk_cache); + + std::shared_ptr schema() const; + private: std::string file_name_; - std::shared_ptr infile_; - std::shared_ptr reader_; - std::shared_ptr batch_; - std::vector> arrays_; - bool empty_file_ = false; -}; // class CSVReader + std::unordered_map> + column_types_; +}; // class ApsiCsvReader } // namespace psi::apsi_wrapper diff --git a/psi/apsi_wrapper/utils/group_db.cc b/psi/apsi_wrapper/utils/group_db.cc new file mode 100644 index 0000000..dfb4d40 --- /dev/null +++ b/psi/apsi_wrapper/utils/group_db.cc @@ -0,0 +1,375 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/apsi_wrapper/utils/group_db.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arrow/array.h" +#include "fmt/format.h" +#include "sender_db.h" +#include "yacl/base/exception.h" + +#include "psi/apsi_wrapper/utils/common.h" +#include "psi/apsi_wrapper/utils/csv_reader.h" + +namespace psi::apsi_wrapper { + +namespace { + +constexpr const char* kGroupLabel = "value"; +constexpr const char* kGroupKey = "key"; +constexpr const char* kGroupBucketId = "bucket_id"; + +} // namespace + +GroupDBItem::GroupDBItem(const std::string& source_file, + const std::string& db_path, size_t group_idx, + std::shared_ptr<::apsi::PSIParams> psi_params, + bool compress, size_t max_bucket_cnt) + : source_file_(source_file), + filename_(fmt::format("{}/{}_group.db", db_path, group_idx)), + meta_filename_(filename_ + ".meta"), + psi_params_(psi_params), + compress_(compress), + max_bucket_cnt_(max_bucket_cnt) {} + +void GroupDBItem::LoadMeta() { + if (complete_) { + return; + } + + YACL_ENFORCE(std::filesystem::exists(filename_), "db file {} not exists.", + filename_); + YACL_ENFORCE(std::filesystem::exists(meta_filename_), + "db file {} not exists.", meta_filename_); + + std::ifstream ifs = std::ifstream(filename_, std::ios::binary); + std::ifstream mete_ifs = std::ifstream(meta_filename_); + + size_t bucket_num; + mete_ifs >> bucket_num; + + YACL_ENFORCE_LE(bucket_num, max_bucket_cnt_, + "bucket_num {} is too large(more than {})", bucket_num, + max_bucket_cnt_); + + for (size_t i = 0; i < bucket_num; ++i) { + size_t bucket_id; + size_t offset; + + mete_ifs >> bucket_id >> offset; + bucket_offset_map_[bucket_id] = offset; + offset_bucket_map_[offset] = bucket_id; + } + + complete_ = true; +} + +GroupDBItem::BucketDBItem GroupDBItem::LoadBucket(size_t bucket_id) { + if (bucket_offset_map_.empty()) { + LoadMeta(); + } + if (bucket_offset_map_.find(bucket_id) == bucket_offset_map_.end()) { + return {0, nullptr, {}}; + } + size_t offset = bucket_offset_map_[bucket_id]; + std::ifstream ifs = std::ifstream(filename_, std::ios::binary); + ifs.seekg(offset); + + BucketDBItem bucket_db; + bucket_db.bucket_id = bucket_id; + bucket_db.sender_db = TryLoadSenderDB(ifs, bucket_db.oprf_key); + + return bucket_db; +} + +bool IsGrouopLabeled(const std::string& source_file) { + std::ifstream ifs(source_file); + std::string line; + YACL_ENFORCE(std::getline(ifs, line), "Failed to read file {}", source_file); + return line.find(kGroupLabel) != std::string::npos; +} + +void GroupDBItem::Generate() { + if (complete_) { + return; + } + + if (std::filesystem::exists(filename_) && + std::filesystem::exists(meta_filename_)) { + SPDLOG_INFO("DB file {} already exists, load_meta {} directly", filename_, + meta_filename_); + LoadMeta(); + return; + } + + std::unordered_map db_data; + + DBData result = UnlabeledData{}; + + auto is_labeled = IsGrouopLabeled(source_file_); + + std::unordered_map> schema; + schema[kGroupBucketId] = arrow::int64(); + schema[kGroupKey] = arrow::utf8(); + if (is_labeled) { + schema[kGroupLabel] = arrow::utf8(); + } + auto reader = MakeArrowCsvReader(source_file_, schema); + + std::shared_ptr batch; + + while (true) { + auto status = reader->ReadNext(&batch); + YACL_ENFORCE(status.ok(), "Read csv: {} error.", source_file_); + + if (batch == nullptr) { + // Handle end of file + break; + } + + auto bucket_id_array = + std::static_pointer_cast(batch->column(0)); + auto key_array = + std::static_pointer_cast(batch->column(1)); + std::shared_ptr label_array; + if (is_labeled) { + label_array = + std::static_pointer_cast(batch->column(2)); + } + + auto row_cnt = batch->num_rows(); + for (int64_t i = 0; i < row_cnt; ++i) { + auto bucket_id = bucket_id_array->Value(i); + auto key = key_array->Value(i); + auto value = is_labeled ? label_array->Value(i) : ""; + + if (db_data.find(bucket_id) == db_data.end()) { + if (is_labeled) { + db_data[bucket_id] = LabeledData{}; + } else { + db_data[bucket_id] = UnlabeledData{}; + } + } + + if (is_labeled) { + apsi::Label label(value.begin(), value.end()); + std::get(db_data[bucket_id]) + .emplace_back(std::string(key), label); + } else { + std::get(db_data[bucket_id]) + .emplace_back(std::string(key)); + } + } + } + + YACL_ENFORCE_LE(db_data.size(), max_bucket_cnt_, + "bucket_cnt {} is too large, more than {}", db_data.size(), + max_bucket_cnt_); + + std::vector bucket_dbs_; + + std::ofstream ofs(filename_, std::ios::binary); + ofs.exceptions(std::ios_base::badbit | std::ios_base::failbit); + + auto flush_proc = [&]() { + size_t processed = 0; + BucketDBItem* bucket_db; + while (processed < db_data.size()) { + { + bucket_db = &bucket_dbs_[processed]; + ++processed; + } + bucket_offset_map_[bucket_db->bucket_id] = ofs.tellp(); + offset_bucket_map_[ofs.tellp()] = bucket_db->bucket_id; + + YACL_ENFORCE( + TrySaveSenderDB(ofs, bucket_db->sender_db, bucket_db->oprf_key), + "save sender db {} to {} failed.", bucket_db->bucket_id, filename_); + } + }; + + for (auto& [bucket_id, data] : db_data) { + BucketDBItem bucket_db; + bucket_db.bucket_id = bucket_id; + + YACL_ENFORCE(!IsDuplicated(data), + "duplicated data in bucket {}, source_file: {}", bucket_id, + source_file_); + + if (is_labeled) { + auto& labeled_db_data = std::get(data); + + // Find the longest label and use that as label size + size_t label_byte_count = + max_element(labeled_db_data.begin(), labeled_db_data.end(), + [](auto& a, auto& b) { + return a.second.size() < b.second.size(); + }) + ->second.size(); + + bucket_db.sender_db = std::make_shared<::apsi::sender::SenderDB>( + *psi_params_, label_byte_count, nonce_byte_count_, compress_); + bucket_db.sender_db->set_data(labeled_db_data); + } else { + bucket_db.sender_db = std::make_shared<::apsi::sender::SenderDB>( + *psi_params_, 0, 0, compress_); + bucket_db.sender_db->set_data(std::get(data)); + } + bucket_db.oprf_key = bucket_db.sender_db->strip(); + + { bucket_dbs_.push_back(bucket_db); } + } + + // future.get(); + flush_proc(); + + std::ofstream meta_ofs(meta_filename_); + meta_ofs << bucket_offset_map_.size() << '\n'; + for (auto& [bucket_id, offset] : bucket_offset_map_) { + meta_ofs << bucket_id << " " << offset << '\n'; + } + + complete_ = true; +} + +void LoadStatus(const std::string& status_file, + GroupDB::GroupDBStatus& status) { + std::ifstream ifs(status_file); + ifs >> status; +} + +void SaveStatus(const std::string& status_file, + const GroupDB::GroupDBStatus& status) { + std::ofstream ofs(status_file); + ofs << status; +} + +GroupDB::GroupDB(const std::string& source_file, const std::string& db_path, + std::size_t group_cnt, size_t num_buckets, + const std::string& params_file, bool compress) + : source_file_(source_file), + db_path_(db_path), + group_cnt_(group_cnt), + num_buckets_(num_buckets), + status_file_path_(std::filesystem::path(db_path_) / status_file_name), + disk_cache_(db_path_, false, "group_"), + params_(BuildPsiParams(params_file)), + compress_(compress) { + if (std::filesystem::exists(status_file_path_)) { + LoadStatus(status_file_path_, status_); + YACL_ENFORCE(status_.version == KGroupDBVersion, + "status version {} not match {}, this dir may have a " + "different version of db, please choose a different dir", + status_.version, KGroupDBVersion); + YACL_ENFORCE(status_.group_cnt == group_cnt_, + "group cnt {} not match {}, this dir may have a " + "different version of db, please choose a different dir", + status_.group_cnt, group_cnt_); + YACL_ENFORCE(status_.num_buckets == num_buckets_, + "bucket num {} not match {}, this dir may have a " + "different version of db, please choose a different dir", + status_.num_buckets, num_buckets_); + YACL_ENFORCE(status_.params_file_content == params_->to_string(), + "params {} not match {}, this dir may have a " + "different version of db, please choose a different dir", + status_.params_file_content, params_->to_string()); + + } else { + status_ = GroupDBStatus{.version = KGroupDBVersion, + .group_cnt = group_cnt_, + .num_buckets = num_buckets_, + .params_file_content = params_->to_string(), + .status = DBState::KNotExist}; + SaveStatus(status_file_path_, status_); + } +} + +bool GroupDB::IsDivided() { return status_.status >= DBState::KBucketed; } + +bool GroupDB::IsDBGenerated() { return status_.status >= DBState::KGenerated; } + +void GroupDB::DivideGroup() { + if (IsDivided()) { + SPDLOG_INFO("It seems like the file has been divided, skip."); + return; + } + + if (!std::filesystem::exists(db_path_)) { + SPDLOG_INFO("create bucket folder {}", db_path_); + std::filesystem::create_directories(db_path_); + } + + ApsiCsvReader reader(source_file_); + reader.GroupBucketize(num_buckets_, db_path_, group_cnt_, disk_cache_); + + status_.status = DBState::KBucketed; + SaveStatus(status_file_path_, status_); +} + +size_t GroupDB::GetGroupNum() { return group_cnt_; } + +GroupDB::BucketIndex GroupDB::GetBucketIndexOfGroup(size_t group_idx) { + auto per_group_bucket_num = (num_buckets_ + group_cnt_ - 1) / group_cnt_; + auto beg = group_idx * per_group_bucket_num; + beg = std::min(num_buckets_, beg); + auto end = std::min(num_buckets_, (group_idx + 1) * per_group_bucket_num); + + return BucketIndex{beg, end - beg}; +} + +void GroupDB::GenerateGroup(size_t group_idx) { + auto per_group_bucket_num = (num_buckets_ + group_cnt_ - 1) / group_cnt_; + + auto group_item_db = std::make_shared( + disk_cache_.GetPath(group_idx), db_path_, group_idx, params_, compress_, + per_group_bucket_num); + group_item_db->Generate(); + group_map_[group_idx] = group_item_db; +} + +void GroupDB::GenerateDone() { + status_.status = DBState::KGenerated; + SaveStatus(status_file_path_, status_); +} + +size_t GroupDB::GetBucketGroupIdx(size_t bucket_idx) { + YACL_ENFORCE(bucket_idx < num_buckets_, + "bucket_idx {} is out of range: [0, {})", bucket_idx, + num_buckets_); + return bucket_idx / ((num_buckets_ + group_cnt_ - 1) / group_cnt_); +} + +GroupDBItem::BucketDBItem GroupDB::GetBucketDB(size_t bucket_idx) { + auto group_idx = GetBucketGroupIdx(bucket_idx); + if (group_map_.find(group_idx) == group_map_.end()) { + GenerateGroup(group_idx); + } + return group_map_[group_idx]->LoadBucket(bucket_idx); +} + +GroupDB::~GroupDB() {} + +} // namespace psi::apsi_wrapper diff --git a/psi/apsi_wrapper/utils/group_db.h b/psi/apsi_wrapper/utils/group_db.h new file mode 100644 index 0000000..a2a9b12 --- /dev/null +++ b/psi/apsi_wrapper/utils/group_db.h @@ -0,0 +1,155 @@ +// Copyright 2024 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "psi/apsi_wrapper/utils/sender_db.h" + +namespace psi::apsi_wrapper { + +class GroupDBItem { + public: + struct BucketDBItem { + size_t bucket_id; + std::shared_ptr<::apsi::sender::SenderDB> sender_db; + ::apsi::oprf::OPRFKey oprf_key; + }; + + GroupDBItem(const std::string& source_file, const std::string& db_path, + size_t group_idx, std::shared_ptr<::apsi::PSIParams> psi_params, + bool compress, size_t max_bucket_cnt); + + GroupDBItem(const GroupDBItem&) = delete; + GroupDBItem(GroupDBItem&&) = delete; + GroupDBItem& operator=(const GroupDBItem&) = delete; + GroupDBItem& operator=(GroupDBItem&&) = delete; + + void Generate(); + + void LoadMeta(); + + BucketDBItem LoadBucket(size_t bucket_id); + + private: + std::string source_file_; + std::string filename_; + std::string meta_filename_; + std::shared_ptr<::apsi::PSIParams> psi_params_; + bool complete_ = false; + bool compress_ = false; + int32_t nonce_byte_count_ = 16; + size_t max_bucket_cnt_ = 0; + + std::unordered_map bucket_offset_map_; + std::map offset_bucket_map_; +}; + +class GroupDB { + public: + static inline const std::string KGroupDBVersion = "sf_pir_group_db_001"; + + struct BucketIndex { + size_t start_index; + size_t cnt; + }; + + enum class DBState : int32_t { + KNotExist = 0, + KBucketed = 1, + KGenerated = 2, + }; + + struct GroupDBStatus { + std::string version = KGroupDBVersion; + size_t group_cnt = 0; + size_t num_buckets = 0; + std::string params_file_content; + DBState status = DBState::KNotExist; + + friend std::ostream& operator<<(std::ostream& os, + const GroupDBStatus& status) { + os << status.params_file_content << "\n"; + os << static_cast(status.status) << " "; + os << status.num_buckets << " "; + os << status.group_cnt << " "; + os << status.version << " "; + + return os; + } + + friend std::istream& operator>>(std::istream& is, GroupDBStatus& status) { + std::getline(is, status.params_file_content); + int32_t tmp; + is >> tmp; + status.status = static_cast(tmp); + is >> status.num_buckets; + is >> status.group_cnt; + is >> status.version; + + return is; + } + }; + + GroupDB(const std::string& source_file, const std::string& db_path, + std::size_t group_cnt, size_t num_buckets, + const std::string& params_file = "", bool compress = false); + + void DivideGroup(); + + size_t GetBucketNum() { return num_buckets_; } + + std::string GetGroupSourceFile(size_t group_idx); + + size_t GetGroupNum(); + + BucketIndex GetBucketIndexOfGroup(size_t group_idx); + + void GenerateGroup(size_t group_idx); + + size_t GetBucketGroupIdx(size_t bucket_idx); + + GroupDBItem::BucketDBItem GetBucketDB(size_t bucket_idx); + + bool IsDivided(); + + bool IsDBGenerated(); + + void GenerateDone(); + + ~GroupDB(); + + private: + static inline const std::string status_file_name = "db.status"; + + std::string source_file_; + std::string db_path_; + size_t group_cnt_; + size_t num_buckets_; + std::string status_file_path_; + MultiplexDiskCache disk_cache_; + std::shared_ptr<::apsi::PSIParams> params_; + bool compress_; + std::unordered_map> group_map_; + GroupDBStatus status_; +}; + +} // namespace psi::apsi_wrapper diff --git a/psi/apsi_wrapper/utils/sender_db.cc b/psi/apsi_wrapper/utils/sender_db.cc index 42bfd7e..3d0c943 100644 --- a/psi/apsi_wrapper/utils/sender_db.cc +++ b/psi/apsi_wrapper/utils/sender_db.cc @@ -38,7 +38,56 @@ namespace fs = std::filesystem; #endif namespace psi::apsi_wrapper { -shared_ptr<::apsi::sender::SenderDB> try_load_sender_db( + +shared_ptr<::apsi::sender::SenderDB> TryLoadSenderDB( + std::istream &in_stream, ::apsi::oprf::OPRFKey &oprf_key) { + shared_ptr<::apsi::sender::SenderDB> result = nullptr; + + in_stream.exceptions(ios_base::badbit | ios_base::failbit); + try { + auto [data, size] = ::apsi::sender::SenderDB::Load(in_stream); + APSI_LOG_INFO("Loaded SenderDB (" << size << " bytes) "); + + result = make_shared<::apsi::sender::SenderDB>(std::move(data)); + + // Load also the OPRF key + oprf_key.load(in_stream); + APSI_LOG_INFO("Loaded OPRF key (" << ::apsi::oprf::oprf_key_size + << " bytes)"); + } catch (const exception &e) { + // Failed to load SenderDB + APSI_LOG_DEBUG("Failed to load SenderDB: " << e.what()); + } + + return result; +} + +bool TrySaveSenderDB(std::ostream &os, + shared_ptr<::apsi::sender::SenderDB> sender_db, + const ::apsi::oprf::OPRFKey &oprf_key) { + if (!sender_db) { + return false; + } + + os.exceptions(ios_base::badbit | ios_base::failbit); + try { + size_t size = sender_db->save(os); + APSI_LOG_INFO("Saved SenderDB (" << size << " bytes)"); + + // Save also the OPRF key (fixed size: oprf_key_size bytes) + oprf_key.save(os); + APSI_LOG_INFO("Saved OPRF key (" << ::apsi::oprf::oprf_key_size + << " bytes)"); + + } catch (const exception &e) { + APSI_LOG_WARNING("Failed to save SenderDB: " << e.what()); + return false; + } + + return true; +} + +shared_ptr<::apsi::sender::SenderDB> TryLoadSenderDB( const std::string &db_file, const std::string ¶ms_file, ::apsi::oprf::OPRFKey &oprf_key) { shared_ptr<::apsi::sender::SenderDB> result = nullptr; @@ -67,12 +116,12 @@ shared_ptr<::apsi::sender::SenderDB> try_load_sender_db( return result; } -shared_ptr<::apsi::sender::SenderDB> try_load_csv_db( +shared_ptr<::apsi::sender::SenderDB> GenerateSenderDB( const std::string &db_file, const std::string ¶ms_file, size_t nonce_byte_count, bool compress, ::apsi::oprf::OPRFKey &oprf_key, const std::vector &keys, const std::vector &labels) { - unique_ptr<::apsi::PSIParams> params = build_psi_params(params_file); + unique_ptr<::apsi::PSIParams> params = BuildPsiParams(params_file); if (!params) { // We must have valid parameters given APSI_LOG_ERROR("Failed to set PSI parameters"); @@ -90,9 +139,9 @@ shared_ptr<::apsi::sender::SenderDB> try_load_csv_db( nonce_byte_count, compress); } -bool try_save_sender_db(const std::string &sdb_out_file, - shared_ptr<::apsi::sender::SenderDB> sender_db, - const ::apsi::oprf::OPRFKey &oprf_key) { +bool TrySaveSenderDB(const std::string &sdb_out_file, + shared_ptr<::apsi::sender::SenderDB> sender_db, + const ::apsi::oprf::OPRFKey &oprf_key) { if (!sender_db) { return false; } @@ -122,7 +171,7 @@ unique_ptr load_db( psi::apsi_wrapper::DBData db_data; try { if (keys.empty() && labels.empty()) { - psi::apsi_wrapper::CSVReader reader(db_file); + psi::apsi_wrapper::ApsiCsvReader reader(db_file); tie(db_data, ignore) = reader.read(); } } catch (const exception &ex) { @@ -139,12 +188,10 @@ load_db_with_orig_items(const std::string &db_file) { psi::apsi_wrapper::DBData db_data; std::vector orig_items; try { - psi::apsi_wrapper::CSVReader reader(db_file); + psi::apsi_wrapper::ApsiCsvReader reader(db_file); tie(db_data, orig_items) = reader.read(); } catch (const exception &ex) { - APSI_LOG_WARNING("Could not open or read file `" << db_file - << "`: " << ex.what()); - return {nullptr, orig_items}; + YACL_THROW("load file: {} failed: {}", db_file, ex.what()); } return {make_unique(std::move(db_data)), diff --git a/psi/apsi_wrapper/utils/sender_db.h b/psi/apsi_wrapper/utils/sender_db.h index 169120a..f65f6ef 100644 --- a/psi/apsi_wrapper/utils/sender_db.h +++ b/psi/apsi_wrapper/utils/sender_db.h @@ -25,11 +25,14 @@ namespace psi::apsi_wrapper { -std::shared_ptr<::apsi::sender::SenderDB> try_load_sender_db( +std::shared_ptr<::apsi::sender::SenderDB> TryLoadSenderDB( + std::istream &in_stream, ::apsi::oprf::OPRFKey &oprf_key); + +std::shared_ptr<::apsi::sender::SenderDB> TryLoadSenderDB( const std::string &db_file, const std::string ¶ms_file, ::apsi::oprf::OPRFKey &oprf_key); -std::shared_ptr<::apsi::sender::SenderDB> try_load_csv_db( +std::shared_ptr<::apsi::sender::SenderDB> GenerateSenderDB( const std::string &db_file, const std::string ¶ms_file, size_t nonce_byte_count, bool compress, ::apsi::oprf::OPRFKey &oprf_key, const std::vector &keys = {}, @@ -42,9 +45,13 @@ std::unique_ptr load_db( std::pair, std::vector> load_db_with_orig_items(const std::string &db_file); -bool try_save_sender_db(const std::string &sdb_out_file, - std::shared_ptr<::apsi::sender::SenderDB> sender_db, - const ::apsi::oprf::OPRFKey &oprf_key); +bool TrySaveSenderDB(const std::string &sdb_out_file, + std::shared_ptr<::apsi::sender::SenderDB> sender_db, + const ::apsi::oprf::OPRFKey &oprf_key); + +bool TrySaveSenderDB(std::ostream &os, + std::shared_ptr<::apsi::sender::SenderDB> sender_db, + const ::apsi::oprf::OPRFKey &oprf_key); std::shared_ptr<::apsi::sender::SenderDB> create_sender_db( const psi::apsi_wrapper::DBData &db_data, diff --git a/psi/apsi_wrapper/yacl_channel.cc b/psi/apsi_wrapper/yacl_channel.cc index ec871d0..12aeea5 100644 --- a/psi/apsi_wrapper/yacl_channel.cc +++ b/psi/apsi_wrapper/yacl_channel.cc @@ -257,13 +257,6 @@ void YaclChannel::send(unique_ptr<::apsi::network::ResultPackage> rp) { throw invalid_argument("result package data is missing"); } - APSI_LOG_DEBUG( - "Sending result package (" - << "has matching data: " << (rp->psi_result ? "yes" : "no") << "; " - << "label byte count: " << rp->label_byte_count << "; " - << "nonce byte count: " << rp->nonce_byte_count << "; " - << "has label data: " << (rp->label_result.size() ? "yes" : "no") << ")"); - stringstream ss; size_t old_bytes_sent = bytes_sent_; diff --git a/psi/cryptor/ecc_cryptor.h b/psi/cryptor/ecc_cryptor.h index 0db3547..db24717 100644 --- a/psi/cryptor/ecc_cryptor.h +++ b/psi/cryptor/ecc_cryptor.h @@ -38,15 +38,15 @@ inline constexpr int kEccKeySize = 32; class IEccCryptor { public: IEccCryptor() { - YACL_ENFORCE(RAND_bytes(&private_key_[0], kEccKeySize) == 1, + YACL_ENFORCE(RAND_bytes(private_key_.data(), kEccKeySize) == 1, "Cannot create random private key"); } - virtual ~IEccCryptor() { OPENSSL_cleanse(&private_key_[0], kEccKeySize); } + virtual ~IEccCryptor() { OPENSSL_cleanse(private_key_.data(), kEccKeySize); } virtual void SetPrivateKey(absl::Span key) { YACL_ENFORCE(key.size() == kEccKeySize); - std::memcpy(private_key_, key.data(), key.size()); + std::memcpy(private_key_.data(), key.data(), key.size()); } /// Get current curve type @@ -85,10 +85,12 @@ class IEccCryptor { std::vector DeserializeEcPoints( const std::vector& items) const; - [[nodiscard]] const uint8_t* GetPrivateKey() const { return private_key_; } + [[nodiscard]] std::array GetPrivateKey() const { + return private_key_; + } protected: - uint8_t private_key_[kEccKeySize]; + std::array private_key_ = {}; std::unique_ptr ec_group_ = nullptr; }; diff --git a/psi/cryptor/sm2_cryptor.h b/psi/cryptor/sm2_cryptor.h index 56e0e8f..0c63741 100644 --- a/psi/cryptor/sm2_cryptor.h +++ b/psi/cryptor/sm2_cryptor.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include @@ -35,7 +36,7 @@ class Sm2Cryptor : public IEccCryptor { CurveType type = CurveType::CURVE_SM2) : curve_type_(type) { YACL_ENFORCE(key.size() == kEccKeySize); - std::memcpy(private_key_, key.data(), key.size()); + std::copy(key.begin(), key.end(), private_key_.begin()); ec_group_ = yacl::crypto::EcGroupFactory::Instance().Create( "sm2", yacl::ArgLib = "openssl"); } diff --git a/psi/ecdh/ecdh_oprf.h b/psi/ecdh/ecdh_oprf.h index 1e35aff..5e73cdb 100644 --- a/psi/ecdh/ecdh_oprf.h +++ b/psi/ecdh/ecdh_oprf.h @@ -62,16 +62,16 @@ enum class OprfType { class IEcdhOprf { public: IEcdhOprf() { - YACL_ENFORCE(RAND_bytes(&private_key_[0], kEccKeySize) == 1, + YACL_ENFORCE(RAND_bytes(private_key_.data(), kEccKeySize) == 1, "Cannot create random private key"); } - virtual ~IEcdhOprf() { OPENSSL_cleanse(&private_key_[0], kEccKeySize); } + virtual ~IEcdhOprf() { OPENSSL_cleanse(private_key_.data(), kEccKeySize); } virtual OprfType GetOprfType() const = 0; virtual size_t GetCompareLength() const { - if (compare_length_) { + if (compare_length_ != 0) { return compare_length_; } return kEccKeySize; @@ -87,11 +87,11 @@ class IEcdhOprf { void SetPrivateKey(yacl::ByteContainerView private_key) { YACL_ENFORCE(private_key.size() == kEccKeySize); - std::memcpy(private_key_, private_key.data(), private_key.size()); + std::memcpy(private_key_.data(), private_key.data(), private_key.size()); } protected: - uint8_t private_key_[kEccKeySize]; + std::array private_key_ = {}; size_t compare_length_ = 0; }; @@ -139,9 +139,7 @@ class IEcdhOprfServer : public IEcdhOprf { absl::Span input) const; virtual std::array GetPrivateKey() const { - std::array key_array{}; - std::memcpy(key_array.data(), &private_key_[0], kEccKeySize); - return key_array; + return private_key_; } }; diff --git a/psi/ecdh/ecdh_psi.cc b/psi/ecdh/ecdh_psi.cc index faed2c3..8af6b67 100644 --- a/psi/ecdh/ecdh_psi.cc +++ b/psi/ecdh/ecdh_psi.cc @@ -114,7 +114,8 @@ void EcdhPsiContext::MaskSelf( if (options_.ecdh_logger) { hashed_masked_items = options_.ecc_cryptor->SerializeEcPoints(hashed_points); - options_.ecdh_logger->Log(EcdhStage::MaskSelf, options_.private_key, + options_.ecdh_logger->Log(EcdhStage::MaskSelf, + options_.ecc_cryptor->GetPrivateKey(), item_count, hashed_masked_items, masked_items); } item_count += batch_items.size(); @@ -166,12 +167,22 @@ void EcdhPsiContext::MaskPeer( } } } + // Should send out the dual masked items to peer. if (PeerCanTouchResults()) { + if (batch_count == 0) { + SPDLOG_INFO("SendDualMaskedItems to peer: {} begin...", + options_.target_rank); + } const auto tag = fmt::format("ECDHPSI:Y^B^A:{}", batch_count); // call non-block to avoid blocking each other with MaskSelf SendDualMaskedBatchNonBlock(dual_masked_peers, batch_count, tag); + if (dual_masked_peers.empty()) { + SPDLOG_INFO("SendDualMaskedItems to peer: {} finished, batch_count={}", + options_.target_rank, batch_count); + } } + if (peer_items.empty()) { SPDLOG_INFO("MaskPeer:{} --finished, batch_count={}, peer_item_count={}", Id(), batch_count, item_count); @@ -181,7 +192,8 @@ void EcdhPsiContext::MaskPeer( break; } if (options_.ecdh_logger) { - options_.ecdh_logger->Log(EcdhStage::MaskPeer, options_.private_key, + options_.ecdh_logger->Log(EcdhStage::MaskPeer, + options_.ecc_cryptor->GetPrivateKey(), item_count, peer_items, dual_masked_peers); } item_count += peer_items.size(); @@ -210,7 +222,8 @@ void EcdhPsiContext::RecvDualMaskedSelf( RecvDualMaskedBatch(&masked_items, batch_count, tag); if (options_.ecdh_logger) { options_.ecdh_logger->Log(EcdhStage::RecvDualMaskedSelf, - options_.private_key, item_count, masked_items); + options_.ecc_cryptor->GetPrivateKey(), + item_count, masked_items); } for (auto& item : masked_items) { self_ec_point_store->Save(std::move(item)); @@ -368,14 +381,17 @@ void RunEcdhPsi(const EcdhPsiOptions& options, } std::future f_mask_self = std::async([&] { + SPDLOG_INFO("ID {}: MaskSelf begin...", handler.Id()); handler.MaskSelf(batch_provider, processed_item_cnt); SPDLOG_INFO("ID {}: MaskSelf finished.", handler.Id()); }); std::future f_mask_peer = std::async([&] { + SPDLOG_INFO("ID {}: MaskPeer begin...", handler.Id()); handler.MaskPeer(peer_ec_point_store); SPDLOG_INFO("ID {}: MaskPeer finished.", handler.Id()); }); std::future f_recv_peer = std::async([&] { + SPDLOG_INFO("ID {}: RecvDualMaskedSelf begin...", handler.Id()); handler.RecvDualMaskedSelf(self_ec_point_store); SPDLOG_INFO("ID {}: RecvDualMaskedSelf finished.", handler.Id()); }); @@ -430,11 +446,6 @@ std::vector RunEcdhPsi( options.target_rank = target_rank; options.batch_size = batch_size; - std::array key_array{}; - std::memcpy(key_array.data(), &options.ecc_cryptor->GetPrivateKey()[0], - kEccKeySize); - options.private_key = key_array; - auto self_ec_point_store = std::make_shared(); auto peer_ec_point_store = std::make_shared(); auto batch_provider = diff --git a/psi/ecdh/ecdh_psi.h b/psi/ecdh/ecdh_psi.h index 6d819bc..549c4b7 100644 --- a/psi/ecdh/ecdh_psi.h +++ b/psi/ecdh/ecdh_psi.h @@ -76,7 +76,6 @@ struct EcdhPsiOptions { // Optional RecoveryManager to save checkpoints. std::shared_ptr recovery_manager = nullptr; - std::array private_key; std::shared_ptr ecdh_logger = nullptr; }; diff --git a/psi/ecdh/receiver.cc b/psi/ecdh/receiver.cc index c2a4d1f..a113f5f 100644 --- a/psi/ecdh/receiver.cc +++ b/psi/ecdh/receiver.cc @@ -57,6 +57,11 @@ void EcdhPsiReceiver::PreProcess() { return; } + if (config_.protocol_config().ecdh_config().batch_size() != 0) { + psi_options_.batch_size = + config_.protocol_config().ecdh_config().batch_size(); + } + psi_options_.ecc_cryptor = CreateEccCryptor(config_.protocol_config().ecdh_config().curve()); psi_options_.link_ctx = lctx_; @@ -70,7 +75,7 @@ void EcdhPsiReceiver::PreProcess() { psi_options_.ic_mode = false; batch_provider_ = std::make_shared( - config_.input_config().path(), selected_keys_); + config_.input_config().path(), selected_keys_, psi_options_.batch_size); if (recovery_manager_) { self_ec_point_store_ = std::make_shared( diff --git a/psi/ecdh/sender.cc b/psi/ecdh/sender.cc index 036639a..91f98e8 100644 --- a/psi/ecdh/sender.cc +++ b/psi/ecdh/sender.cc @@ -59,6 +59,11 @@ void EcdhPsiSender::PreProcess() { return; } + if (config_.protocol_config().ecdh_config().batch_size() != 0) { + psi_options_.batch_size = + config_.protocol_config().ecdh_config().batch_size(); + } + psi_options_.ecc_cryptor = CreateEccCryptor(config_.protocol_config().ecdh_config().curve()); psi_options_.link_ctx = lctx_; @@ -72,7 +77,7 @@ void EcdhPsiSender::PreProcess() { psi_options_.ic_mode = false; batch_provider_ = std::make_shared( - config_.input_config().path(), selected_keys_); + config_.input_config().path(), selected_keys_, psi_options_.batch_size); if (recovery_manager_) { self_ec_point_store_ = std::make_shared( diff --git a/psi/kuscia_adapter.cc b/psi/kuscia_adapter.cc index 659a527..86ed21e 100644 --- a/psi/kuscia_adapter.cc +++ b/psi/kuscia_adapter.cc @@ -14,13 +14,12 @@ #include "psi/kuscia_adapter.h" -#include - #include #include #include #include +#include "fmt/format.h" #include "google/protobuf/util/json_util.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" diff --git a/psi/launch.cc b/psi/launch.cc index 65790cb..2dc8137 100644 --- a/psi/launch.cc +++ b/psi/launch.cc @@ -58,25 +58,23 @@ std::unique_ptr StartTracing() { } void SetLogLevel(const std::string& level) { - std::string normalized_level = boost::algorithm::to_lower_copy(level); + static const std::map kLogLevelMap = { + {"trace", spdlog::level::trace}, {"debug", spdlog::level::debug}, + {"info", spdlog::level::info}, {"warn", spdlog::level::warn}, + {"err", spdlog::level::err}, {"critical", spdlog::level::critical}, + {"off", spdlog::level::off}}; + static const std::string kDefaultLogLevel = "info"; - if (normalized_level == "trace") { - spdlog::set_level(spdlog::level::trace); - } else if (normalized_level == "debug") { - spdlog::set_level(spdlog::level::debug); - } else if (normalized_level == "info" || normalized_level.empty()) { - spdlog::set_level(spdlog::level::info); - } else if (normalized_level == "warn") { - spdlog::set_level(spdlog::level::warn); - } else if (normalized_level == "err") { - spdlog::set_level(spdlog::level::err); - } else if (normalized_level == "critical") { - spdlog::set_level(spdlog::level::critical); - } else if (normalized_level == "off") { - spdlog::set_level(spdlog::level::off); - } else { - YACL_THROW("unsupported logging level: {}", level); + std::string normalized_level = boost::algorithm::to_lower_copy(level); + if (normalized_level.empty()) { + normalized_level = kDefaultLogLevel; } + + auto level_iter = kLogLevelMap.find(normalized_level); + YACL_ENFORCE(level_iter != kLogLevelMap.end(), + "unsupported logging level: {}", level); + spdlog::set_level(level_iter->second); + spdlog::flush_on(level_iter->second); } void StopTracing(std::unique_ptr tracing_session, @@ -218,6 +216,7 @@ PirResultReport RunPir(const ApsiSenderConfig& apsi_sender_config, options.log_file = apsi_sender_config.log_file(); options.silent = apsi_sender_config.silent(); options.db_file = apsi_sender_config.db_file(); + options.source_file = apsi_sender_config.source_file(); options.params_file = apsi_sender_config.params_file(); options.sdb_out_file = apsi_sender_config.sdb_out_file(); options.channel = "yacl"; @@ -233,7 +232,14 @@ PirResultReport RunPir(const ApsiSenderConfig& apsi_sender_config, apsi_sender_config.experimental_bucket_cnt(); options.experimental_bucket_folder = apsi_sender_config.experimental_bucket_folder(); - + if (apsi_sender_config.experimental_db_generating_process_num()) { + options.experimental_db_generating_process_num = + apsi_sender_config.experimental_db_generating_process_num(); + } + if (apsi_sender_config.experimental_bucket_group_cnt()) { + options.experimental_bucket_group_cnt = + apsi_sender_config.experimental_bucket_group_cnt(); + } YACL_ENFORCE_EQ(RunSender(options, lctx), 0); return PirResultReport(); diff --git a/psi/legacy/bucket_ub_psi.cc b/psi/legacy/bucket_ub_psi.cc index 70c790f..a9deb9e 100644 --- a/psi/legacy/bucket_ub_psi.cc +++ b/psi/legacy/bucket_ub_psi.cc @@ -433,7 +433,7 @@ std::pair, size_t> UbPsiServerOnline( lctx->NextRank(), fmt::format("EC-OPRF:PSI:INTERSECTION_SIZE"))); SPDLOG_INFO("rank:{} begin recv broadcast {} intersection results", - lctx->Rank(), results.size(), intersection_size); + lctx->Rank(), intersection_size); if (intersection_size > 0) { std::vector result_items; diff --git a/psi/legacy/kmprt17_mp_psi/BUILD.bazel b/psi/legacy/kmprt17_mp_psi/BUILD.bazel index a548394..c08ce10 100644 --- a/psi/legacy/kmprt17_mp_psi/BUILD.bazel +++ b/psi/legacy/kmprt17_mp_psi/BUILD.bazel @@ -47,5 +47,6 @@ psi_cc_library( psi_cc_test( name = "kmprt17_mp_psi_test", srcs = ["kmprt17_mp_psi_test.cc"], + tags = ["manual"], deps = [":kmprt17_mp_psi"], ) diff --git a/psi/legacy/kmprt17_mp_psi/kmprt17_mp_psi_test.cc b/psi/legacy/kmprt17_mp_psi/kmprt17_mp_psi_test.cc index f07858e..c8c865d 100644 --- a/psi/legacy/kmprt17_mp_psi/kmprt17_mp_psi_test.cc +++ b/psi/legacy/kmprt17_mp_psi/kmprt17_mp_psi_test.cc @@ -64,6 +64,7 @@ std::vector> CreateNPartyItems( class KMPRTMpPsiTest : public testing::TestWithParam {}; +// FIXME : this test is not stable in arm env TEST_P(KMPRTMpPsiTest, Works) { std::vector> items; diff --git a/psi/proto/pir.proto b/psi/proto/pir.proto index c1dffa7..fb3b887 100644 --- a/psi/proto/pir.proto +++ b/psi/proto/pir.proto @@ -79,6 +79,17 @@ message ApsiSenderConfig { // [experimental] Folder to save bucketized small csv files and db files. string experimental_bucket_folder = 15; + + // [experimental] The number of processes to use for generating db. + int32 experimental_db_generating_process_num = 16; + + // Source file used to genenerate sender db. + // Currently only support csv file. + string source_file = 17; + + // [experimental] The number of group of bucket, each group has a db_file, + // default 1024. + int32 experimental_bucket_group_cnt = 18; } message ApsiReceiverConfig { diff --git a/psi/proto/psi_v2.proto b/psi/proto/psi_v2.proto index 1b3f044..e0d20db 100644 --- a/psi/proto/psi_v2.proto +++ b/psi/proto/psi_v2.proto @@ -65,6 +65,9 @@ enum Protocol { // Configs for ECDH protocol. message EcdhConfig { .psi.CurveType curve = 1; + + // If not set, use default value: 4096. + uint64 batch_size = 2; } // Configs for KKRT protocol diff --git a/psi/psi_test.cc b/psi/psi_test.cc index c78d05b..4906ce8 100644 --- a/psi/psi_test.cc +++ b/psi/psi_test.cc @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include #include @@ -31,6 +30,7 @@ #include "boost/uuid/uuid.hpp" #include "boost/uuid/uuid_generators.hpp" #include "boost/uuid/uuid_io.hpp" +#include "fmt/format.h" #include "gtest/gtest.h" #include "spdlog/spdlog.h" #include "yacl/link/test_util.h" diff --git a/psi/rr22/okvs/baxos.cc b/psi/rr22/okvs/baxos.cc index a4281d6..c872585 100644 --- a/psi/rr22/okvs/baxos.cc +++ b/psi/rr22/okvs/baxos.cc @@ -156,7 +156,7 @@ void Baxos::ImplParSolve( YACL_ENFORCE(p_.size() == size(), "p size:{} baox size:{}", p_.size(), size()); YACL_ENFORCE(inputs_param.size() <= num_items_, "input size:{}, num_item:{}", - inputs_param.size()); + inputs_param.size(), num_items_); SPDLOG_DEBUG( "item size:{} paxos size:{} num_bins_:{}, items_per_bin_:{} idxType:{}", diff --git a/psi/rr22/okvs/paxos.cc b/psi/rr22/okvs/paxos.cc index 8823c75..a66254f 100644 --- a/psi/rr22/okvs/paxos.cc +++ b/psi/rr22/okvs/paxos.cc @@ -242,8 +242,9 @@ void Paxos::SetInput(absl::Span inputs) { "IdxType:{}", inputs.size(), num_items_, sparse_size, sizeof(IdxType)); - YACL_ENFORCE(inputs.size() <= num_items_, "inputs size must equal num_items ", - inputs.size(), num_items_); + YACL_ENFORCE(inputs.size() <= num_items_, + "inputs size {} must equal num_items {}", inputs.size(), + num_items_); std::vector col_weights(sparse_size); @@ -647,7 +648,7 @@ void Paxos::BackfillGf128( SPDLOG_DEBUG("gapRows size:{}", g); - YACL_ENFORCE(g <= dense_size, "g:{}, dense_size", g, dense_size); + YACL_ENFORCE(g <= dense_size, "g:{}, dense_size: {}", g, dense_size); if (g) { auto fcinv = GetFCInv(main_rows, main_cols, gap_rows); @@ -1056,7 +1057,8 @@ std::vector Paxos::GetGapCols( // TDOD, make the linear time. auto gap_cols = ithCombination(ci, dense_size, g); ++ci; - YACL_ENFORCE(ci <= e, "failed to find invertible matrix. {}"); + YACL_ENFORCE(ci <= e, "failed to find invertible matrix. ci:{}, e:{}", ci, + e); EE.resize(g, g); for (uint64_t i = 0; i < g; ++i) { @@ -1101,7 +1103,7 @@ PxVector Paxos::GetX2Prime(const FCInv& fcinv, // x2' = x2 - D' r - FC^-1 x1 if (randomized) { - YACL_ENFORCE(P.size() == dense_size + sparse_size); + YACL_ENFORCE(P.size() == (dense_size + sparse_size)); auto p2 = P.subspan(sparse_size); // note that D' only has a dense part because we diff --git a/psi/rr22/rr22_psi_test.cc b/psi/rr22/rr22_psi_test.cc index afcb080..d09c901 100644 --- a/psi/rr22/rr22_psi_test.cc +++ b/psi/rr22/rr22_psi_test.cc @@ -14,6 +14,8 @@ #include "psi/rr22/rr22_psi.h" +#include + #include #include @@ -49,6 +51,10 @@ GenerateTestData(size_t item_size, double p = 0.5) { } } + SPDLOG_INFO("inputs_a: {}, inputs_b: {}, indices: {}", + fmt::join(inputs_a, ", "), fmt::join(inputs_b, ", "), + fmt::join(indices, ", ")); + return std::make_tuple(inputs_a, inputs_b, indices); } @@ -97,13 +103,6 @@ TEST_P(Rr22PsiTest, CorrectTest) { SPDLOG_INFO("{}?={}", indices.size(), indices_psi.size()); EXPECT_EQ(indices.size(), indices_psi.size()); -#if 0 - for (size_t i = 0; i < indices.size(); ++i) { - SPDLOG_INFO("i:{} index:{} a:{}, b:{}", i, indices_psi[i], - inputs_a[indices_psi[i]], inputs_b[indices_psi[i]]); - } -#endif - EXPECT_EQ(indices_psi, indices); } @@ -140,13 +139,6 @@ TEST_P(Rr22PsiTest, CompressParamsFalseTest) { SPDLOG_INFO("{}?={}", indices.size(), indices_psi.size()); EXPECT_EQ(indices.size(), indices_psi.size()); -#if 0 - for (size_t i = 0; i < indices.size(); ++i) { - SPDLOG_INFO("i:{} index:{} a:{}, b:{}", i, indices_psi[i], - inputs_a[indices_psi[i]], inputs_b[indices_psi[i]]); - } -#endif - EXPECT_EQ(indices_psi, indices); } diff --git a/psi/rr22/rr22_utils.cc b/psi/rr22/rr22_utils.cc index f6f4139..45d2906 100644 --- a/psi/rr22/rr22_utils.cc +++ b/psi/rr22/rr22_utils.cc @@ -50,7 +50,7 @@ std::vector GetIntersection( num_threads = self_oprfs.size(); } - int128_t truncate_mask = yacl::MakeUint128(0, 0); + auto truncate_mask = yacl::MakeUint128(0, 0); for (size_t i = 0; i < mask_size; ++i) { truncate_mask = 0xff | (truncate_mask << 8); SPDLOG_DEBUG( diff --git a/psi/utils/communication.h b/psi/utils/communication.h index 838b102..88bd55a 100644 --- a/psi/utils/communication.h +++ b/psi/utils/communication.h @@ -24,7 +24,6 @@ namespace psi { -// I prefer 4096. inline constexpr size_t kEcdhPsiBatchSize = 4096; // Ecc256 requires 32 bytes. diff --git a/psi/utils/multiplex_disk_cache.cc b/psi/utils/multiplex_disk_cache.cc index 3e5be71..79a3b94 100644 --- a/psi/utils/multiplex_disk_cache.cc +++ b/psi/utils/multiplex_disk_cache.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include "boost/uuid/uuid.hpp" #include "boost/uuid/uuid_generators.hpp" @@ -49,7 +50,9 @@ bool ScopedTempDir::CreateUniqueTempDirUnderPath( } MultiplexDiskCache::MultiplexDiskCache(const std::filesystem::path& path, - bool use_scoped_tmp_dir) { + bool use_scoped_tmp_dir, + std::string prefix) + : prefix_(std::move(prefix)) { if (use_scoped_tmp_dir) { scoped_temp_dir_ = std::make_unique(); YACL_ENFORCE(scoped_temp_dir_->CreateUniqueTempDirUnderPath(path)); @@ -60,7 +63,7 @@ MultiplexDiskCache::MultiplexDiskCache(const std::filesystem::path& path, } std::string MultiplexDiskCache::GetPath(size_t index) const { - std::filesystem::path path = cache_dir_ / std::to_string(index); + std::filesystem::path path = cache_dir_ / (prefix_ + std::to_string(index)); return path.string(); } diff --git a/psi/utils/multiplex_disk_cache.h b/psi/utils/multiplex_disk_cache.h index f67e1a1..e3c2837 100644 --- a/psi/utils/multiplex_disk_cache.h +++ b/psi/utils/multiplex_disk_cache.h @@ -48,7 +48,8 @@ class ScopedTempDir { class MultiplexDiskCache { public: explicit MultiplexDiskCache(const std::filesystem::path& path, - bool use_scoped_tmp_dir = true); + bool use_scoped_tmp_dir = true, + std::string prefix = ""); MultiplexDiskCache(const MultiplexDiskCache&) = delete; MultiplexDiskCache& operator=(const MultiplexDiskCache&) = delete; @@ -66,6 +67,7 @@ class MultiplexDiskCache { private: std::filesystem::path cache_dir_; std::unique_ptr scoped_temp_dir_; + std::string prefix_; }; } // namespace psi diff --git a/psi/utils/recovery.cc b/psi/utils/recovery.cc index 3df9ea5..c788754 100644 --- a/psi/utils/recovery.cc +++ b/psi/utils/recovery.cc @@ -50,7 +50,10 @@ v2::RecoveryCheckpoint LoadRecoveryCheckpointFromFile( RecoveryManager::RecoveryManager(const std::string& folder_path) : folder_path_(folder_path) { - YACL_ENFORCE(std::filesystem::exists(folder_path_)); + if (!std::filesystem::exists(folder_path_)) { + SPDLOG_INFO("folder {} doesn't exist, create it.", folder_path_.string()); + std::filesystem::create_directories(folder_path_); + } checkpoint_file_path_ = folder_path_ / "checkpoint.json"; private_key_file_path_ = folder_path_ / "private_key"; @@ -122,7 +125,7 @@ void RecoveryManager::MarkPreProcessEnd( // save private key auto private_key_output = io::BuildOutputStream(io::FileIoOptions(private_key_file_path_)); - private_key_output->Write(cryptor->GetPrivateKey(), kEccKeySize); + private_key_output->Write(cryptor->GetPrivateKey().data(), kEccKeySize); private_key_output->Flush(); // save checkpoint file diff --git a/psi/version.h b/psi/version.h index e18e65f..c25882c 100644 --- a/psi/version.h +++ b/psi/version.h @@ -16,5 +16,5 @@ #define PSI_VERSION_MAJOR 0 #define PSI_VERSION_MINOR 4 -#define PSI_VERSION_PATCH 0 -#define PSI_DEV_IDENTIFIER ".dev240801" +#define PSI_VERSION_PATCH 2 +#define PSI_DEV_IDENTIFIER ".dev240822"