diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd7b4f5fcf..c5b10de7934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * Filtering notifications with backlink columns as last element could sometimes give wrong results ([#7530](https://github.com/realm/realm-core/issues/7530), since 11.1.0) ### Breaking changes -* None. +* Ability to synchronize has been removed. ### Compatibility * Fileformat: Generates files with format v24. Reads and automatically upgrade from fileformat v10. If you want to upgrade from an earlier file format version you will have to use RealmCore v13.x.y or earlier. diff --git a/CMakeLists.txt b/CMakeLists.txt index a693f1c8542..830ce575b31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,7 +259,6 @@ if(UNIX) endif() # Options (passed to CMake) -option(REALM_ENABLE_SYNC "Enable synchronized realms." ON) option(REALM_BUILD_TEST_CLIENT "Build the test client" OFF) option(REALM_ENABLE_ASSERTIONS "Enable assertions in release mode." OFF) option(REALM_ENABLE_ALLOC_SET_ZERO "Zero all allocations." OFF) @@ -268,7 +267,6 @@ if(NOT EMSCRIPTEN) endif() option(REALM_ENABLE_MEMDEBUG "Add additional memory checks" OFF) option(REALM_VALGRIND "Tell the test suite we are running with valgrind" OFF) -option(REALM_SYNC_MULTIPLEXING "Enables/disables sync session multiplexing by default" ON) set(REALM_MAX_BPNODE_SIZE "1000" CACHE STRING "Max B+ tree node size.") option(REALM_ENABLE_GEOSPATIAL "Enable geospatial types and queries." ON) option(REALM_APP_SERVICES "Enable the default app services implementation." ON) @@ -287,15 +285,7 @@ endif() find_package(Backtrace) set(REALM_HAVE_BACKTRACE ${Backtrace_FOUND}) -if(REALM_ENABLE_SYNC) - option(REALM_FORCE_OPENSSL "Always use OpenSSL for SSL needs, regardless of target platform." OFF) - if(CMAKE_SYSTEM_NAME MATCHES "^Windows|Linux|Android") - set(REALM_NEEDS_OPENSSL TRUE) - endif() - if(REALM_NEEDS_OPENSSL OR REALM_FORCE_OPENSSL) - option(REALM_INCLUDE_CERTS "Include a list of trust certificates in the build for OpenSSL certificate verification" ON) - endif() -elseif(REALM_ENABLE_ENCRYPTION AND CMAKE_SYSTEM_NAME MATCHES "Linux|Android") +if(REALM_ENABLE_ENCRYPTION AND CMAKE_SYSTEM_NAME MATCHES "Linux|Android") set(REALM_NEEDS_OPENSSL TRUE) endif() @@ -380,9 +370,6 @@ set(REALM_EXPORTED_TARGETS RealmFFI RealmFFIStatic ) -if(REALM_ENABLE_SYNC) - list(APPEND REALM_EXPORTED_TARGETS Sync) -endif() export(TARGETS ${REALM_EXPORTED_TARGETS} NAMESPACE Realm:: FILE RealmTargets.cmake) configure_file(tools/cmake/RealmConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/RealmConfig.cmake @ONLY) configure_file(tools/cmake/AcquireRealmDependency.cmake ${CMAKE_CURRENT_BINARY_DIR}/AcquireRealmDependency.cmake @ONLY) diff --git a/Package.swift b/Package.swift index efcd53cf67f..c73f74e29fa 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,6 @@ var cxxSettings: [CXXSetting] = [ .define("REALM_INSTALL_LIBEXECDIR", to: ""), .define("REALM_ENABLE_ASSERTIONS", to: "1"), .define("REALM_ENABLE_ENCRYPTION", to: "1"), - .define("REALM_ENABLE_SYNC", to: "1"), .define("REALM_ENABLE_GEOSPATIAL", to: "1"), .define("REALM_APP_SERVICES", to: "1"), diff --git a/evergreen/config.yml b/evergreen/config.yml index e14afde6c9b..4979853af95 100644 --- a/evergreen/config.yml +++ b/evergreen/config.yml @@ -88,14 +88,6 @@ functions: set_cmake_var compiler_vars CMAKE_CXX_COMPILER PATH $(./evergreen/abspath.sh "${cxx_compiler}") fi - if [[ -z "${disable_tests_against_baas|}" && -z "${no_tests|}" ]]; then - scheme="http" - set_cmake_var baas_vars REALM_ENABLE_AUTH_TESTS BOOL On - if [ -n "${baas_admin_port|}" ]; then - set_cmake_var baas_vars REALM_ADMIN_ENDPOINT STRING "$scheme://localhost:${baas_admin_port}" - fi - fi - if [ -n "${enable_asan|}" ]; then set_cmake_var realm_vars REALM_ASAN BOOL On fi @@ -117,10 +109,6 @@ functions: set_cmake_var realm_vars REALM_LIBFUZZER BOOL On fi - if [ -z "${disable_sync|}" ]; then - set_cmake_var realm_vars REALM_ENABLE_SYNC BOOL On - fi - if [ -n "${use_system_openssl|}" ]; then set_cmake_var realm_vars REALM_USE_SYSTEM_OPENSSL BOOL On fi @@ -129,10 +117,6 @@ functions: set_cmake_var realm_vars REALM_TEST_DURATION STRING "${long_running_test_duration}" fi - if [ -n "${disable_sync_multiplexing|}" ]; then - set_cmake_var realm_vars REALM_SYNC_MULTIPLEXING BOOL Off - fi - if [ -n "${enable_llvm_coverage|}" ]; then set_cmake_var realm_vars REALM_LLVM_COVERAGE BOOL On fi @@ -274,11 +258,6 @@ functions: params: working_dir: realm-core shell: bash - env: - BAASAAS_API_KEY: "${baasaas_api_key}" - # BAAS_VERSION and VERSION_TYPE are set by realm-core/dependencies.yml - BAASAAS_REF_SPEC: "${BAAS_VERSION}" - BAASAAS_START_MODE: "${BAAS_VERSION_TYPE|githash}" script: |- set -o errexit set -o verbose @@ -316,12 +295,6 @@ functions: TEST_FLAGS+="-V " fi - if [[ -n "${disable_tests_against_baas|}" ]]; then - unset BAASAAS_API_KEY - unset BAASAAS_REF_SPEC - unset BAASAAS_START_MODE - fi - if [[ -n "${c_compiler}" && "$(basename ${c_compiler})" = "clang" && -f "$(dirname ${c_compiler})/llvm-symbolizer" ]]; then LLVM_SYMBOLIZER="$(dirname ${c_compiler})/llvm-symbolizer" # we don't want to put all of the llvm bin-dir onto the path, so make a new directory, as-needed, @@ -356,12 +329,6 @@ functions: fi fi - # on later MacOS's the system curl is built against an old version of openssl that - # can't talk to baasaas over TLS. - if [[ "$(uname -s)" = "Darwin" ]]; then - export CURL_SSL_BACKEND=Secure-Transport - fi - export UNITTEST_EVERGREEN_TEST_RESULTS="$(./evergreen/abspath.sh ${task_name}_results.json)" if [[ -n "$UNITTEST_EVERGREEN_TEST_RESULTS" && -f "$UNITTEST_EVERGREEN_TEST_RESULTS" ]]; then rm "$UNITTEST_EVERGREEN_TEST_RESULTS" @@ -372,11 +339,7 @@ functions: export TSAN_OPTIONS="suppressions=$(pwd)/test/tsan.suppress history_size=4 $TSAN_OPTIONS" export UBSAN_OPTIONS="print_stacktrace=1" - if [[ -n "${enable_baas_redirector}" ]]; then - export ENABLE_BAAS_REDIRECTOR=On - fi - - cd build + cd build "$CTEST" -C ${cmake_build_type|Debug} $TEST_FLAGS if [[ -f ../test_${task_id}.log ]]; then @@ -388,105 +351,6 @@ functions: params: file_location: realm-core/${task_name}_results.json - "upload baas artifacts": - - command: shell.exec - params: - working_dir: realm-core - shell: bash - script: |- - set -o errexit - set -o pipefail - set -o verbose - - if [[ -n "${disable_tests_against_baas|}" ]]; then - exit 0 - fi - - # Copy the baas_server log from the remote baas host if it exists - if [[ ! -f baas_host.yml || ! -f .baas_ssh_key ]]; then - echo "Skipping - no remote baas host or remote baas host definitions not found" - exit 0 - fi - - BAAS_HOST_NAME=$(tr -d '"[]{}' < baas_host.yml | cut -d , -f 1 | awk -F : '{print $2}') - export BAAS_HOST_NAME - - ssh_user="$(printf "ubuntu@%s" "$BAAS_HOST_NAME")" - ssh_options="-o ForwardAgent=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o ConnectTimeout=60 -i .baas_ssh_key" - - # Copy the baas_server.log, mongod.log and (optionally) baas_proxy.log files from the remote baas host - REMOTE_PATH=/data/baas-remote - REMOTE_BAAS_PATH="$REMOTE_PATH/baas-work-dir" - REMOTE_BAAS_DB_PATH="$REMOTE_BAAS_PATH/mongodb-dbpath" - REMOTE_PROXY_PATH="$REMOTE_PATH/baas-proxy-dir" - - LOCAL_BAAS_PATH=./baas-work-dir - LOCAL_BAAS_DB_PATH="$LOCAL_BAAS_PATH/mongodb-dbpath" - LOCAL_PROXY_PATH=./baas-proxy-dir - - mkdir -p "$LOCAL_BAAS_PATH/" - mkdir -p "$LOCAL_BAAS_DB_PATH/" - mkdir -p "$LOCAL_PROXY_PATH/" - scp $ssh_options $ssh_user:"$REMOTE_BAAS_PATH/baas_server.log" "$LOCAL_BAAS_PATH/baas_server.log" || true - scp $ssh_options $ssh_user:"$REMOTE_BAAS_DB_PATH/mongod.log" "$LOCAL_BAAS_DB_PATH/mongod.log" || true - scp $ssh_options $ssh_user:"$REMOTE_PROXY_PATH/baas_proxy.log" "$LOCAL_PROXY_PATH/baas_proxy.log" || true - - - command: s3.put - params: - aws_key: '${artifacts_aws_access_key}' - aws_secret: '${artifacts_aws_secret_key}' - local_file: 'realm-core/baas-work-dir/baas_server.log' - remote_file: 'realm-core-stable/${branch_name}/${task_id}/${execution}/baas_server.log' - bucket: mciuploads - permissions: public-read - content_type: text/plain - display_name: baas server logs - optional: true - - command: s3.put - params: - aws_key: '${artifacts_aws_access_key}' - aws_secret: '${artifacts_aws_secret_key}' - local_file: 'realm-core/install_baas_output.log' - remote_file: 'realm-core-stable/${branch_name}/${task_id}/${execution}/install_baas_output.log' - bucket: mciuploads - permissions: public-read - content_type: text/plain - display_name: install baas output - optional: true - - command: s3.put - params: - aws_key: '${artifacts_aws_access_key}' - aws_secret: '${artifacts_aws_secret_key}' - local_file: 'realm-core/baas-work-dir/mongodb-dbpath/mongod.log' - remote_file: 'realm-core-stable/${branch_name}/${task_id}/${execution}/mongod.log' - bucket: mciuploads - permissions: public-read - content_type: text/plain - display_name: mongod logs - optional: true - - command: s3.put - params: - aws_key: '${artifacts_aws_access_key}' - aws_secret: '${artifacts_aws_secret_key}' - local_file: 'realm-core/baas-proxy-dir/baas_proxy.log' - remote_file: 'realm-core-stable/${branch_name}/${task_id}/${execution}/baas_proxy.log' - bucket: mciuploads - permissions: public-read - content_type: text/plain - display_name: baas proxy logs - optional: true - - command: s3.put - params: - aws_key: '${artifacts_aws_access_key}' - aws_secret: '${artifacts_aws_secret_key}' - local_file: 'realm-core/test_${task_id}.log' - remote_file: 'realm-core-stable/${branch_name}/${task_id}/${execution}/test.log' - bucket: mciuploads - permissions: public-read - content_type: text/plain - display_name: trace level test logs - optional: true - "upload fuzzer results": - command: shell.exec params: @@ -628,149 +492,6 @@ functions: python $HANG_ANALYZER_PATH - "launch remote baas": - - command: host.create - params: - provider: ec2 - distro: ubuntu2004-medium - - command: host.list - params: - num_hosts: 1 - path: realm-core/baas_host.yml - timeout_seconds: 600 - wait: true - - command: shell.exec - params: - working_dir: realm-core - shell: bash - # Do NOT use verbose for this script since it would reveal the values of secrets - script: |- - set -o errexit - set -o pipefail - - if [[ -n "${disable_tests_against_baas|}" ]]; then - echo "Should not be using remote baas if the tests are not being used" - exit 0 - fi - - # Save the remote host ssh key and details to files - echo "${__project_aws_ssh_key_value}" > .baas_ssh_key - chmod 600 .baas_ssh_key - - BAAS_HOST_NAME=$(tr -d '"[]{}' < baas_host.yml | cut -d , -f 1 | awk -F : '{print $2}') - - # Github SSH host key updated 06/26/2023 - # Use 'github_known_hosts' expansion for GITHUB_KNOWN_HOSTS below once EVG-20410 has been implemented - cat >baas_host_vars.sh <&1 | tee install_baas_output.log - - "wait for baas to start": - - command: shell.exec - params: - working_dir: realm-core - shell: bash - script: |- - set -o errexit - set -o verbose - - if [[ -n "${disable_tests_against_baas|}" ]]; then - exit 0 - fi - - # Don't print out the tail of the log file - ./evergreen/wait_for_baas.sh -s -w ./baas-work-dir -l "" - - echo "Baas is started!" - - "wait for remote baas to start": - - command: shell.exec - params: - working_dir: realm-core - shell: bash - script: |- - set -o errexit - set -o verbose - - if [[ -n "${disable_tests_against_baas|}" ]]; then - exit 0 - fi - - # Don't print out the tail of the log file - ./evergreen/wait_for_remote_baas.sh -i ./.baas_ssh_key ./baas_host_vars.sh ./.baas_ssh_key - - echo "Baas is started!" - - "setup proxy parameters": - - command: shell.exec - params: - working_dir: realm-core - shell: bash - script: |- - set -o errexit - set -o pipefail - set -o verbose - - if [[ -n "${disable_tests_against_baas|}" ]]; then - echo "Error: Baas is disabled for network tests" - exit 1 - fi - - if [[ -z "${proxy_toxics_file|}" ]]; then - echo "Error: Baas proxy toxics config file was not provided" - exit 1 - fi - - if [[ -n "${proxy_toxics_randoms|}" ]]; then - PROXY_RANDOMS="-r ${proxy_toxics_randoms}" - fi - - # Configure the toxics for the baas proxy - evergreen/configure_baas_proxy.sh $PROXY_RANDOMS "${proxy_toxics_file}" - # Display the list of configured toxics - curl --silent http://localhost:8474/proxies/baas_proxy/toxics - "check branch state": - command: shell.exec type: setup @@ -969,26 +690,6 @@ tasks: vars: benchmark_name: crud -- name: benchmark-sync - exec_timeout_secs: 1800 - tags: [ "benchmark" ] - commands: - - func: "run benchmark" - vars: - benchmark_name: sync - -- name: sync-tests - tags: [ "test_suite", "for_pull_requests" ] - exec_timeout_secs: 1800 - commands: - - func: "compile" - vars: - target_to_build: "SyncTests" - - func: "run tests" - vars: - test_filter: SyncTests - test_executable_name: "realm-sync-tests" - # These are local object store tests; baas is not started, however some use the sync server - name: object-store-tests tags: [ "for_pull_requests", "test_suite" ] @@ -1005,46 +706,6 @@ tasks: disable_tests_against_baas: true - func: "check branch state" -# These are baas object store tests that run against baas running on a remote host -- name: baas-integration-tests - tags: [ "test_suite", "for_pull_requests", "requires_baas" ] - exec_timeout_secs: 3600 - commands: - - func: "compile" - vars: - target_to_build: ObjectStoreTests - - func: "run tests" - vars: - test_label: objstore-baas - test_executable_name: "realm-object-store-tests" - verbose_test_output: true - - func: "check branch state" - -- name: baas-network-tests - # Uncomment once tests are passing - tags: [ "disabled" ] - # tags: [ "for_nightly_tests" ] - # These tests can be manually requested for patches and pull requests - allowed_requesters: [ "ad_hoc", "patch", "github_pr" ] - # The network tests can take a really long time, so we give the test up to 4 - # hours to complete - exec_timeout_secs: 14400 - commands: - - func: "launch remote baas" - vars: - baas_proxy: On - - func: "compile" - vars: - target_to_build: ObjectStoreTests - - func: "wait for baas to start" - - func: "setup proxy parameters" - - func: "run tests" - vars: - test_label: objstore-baas - test_executable_name: "realm-object-store-tests" - verbose_test_output: true - - func: "check branch state" - - name: process_coverage_data tags: [ "for_pull_requests" ] exec_timeout_secs: 1800 @@ -1237,55 +898,6 @@ tasks: 'io.realm.CombinedTests' \ $TEST_RESULTS_FILE -- name: generate-sync-corpus - tags: [ "for_nightly_tests" ] - exec_timeout_secs: 1800 - commands: - - func: "fetch source" - - func: "fetch binaries" - - func: "compile" - vars: - target_to_build: "SyncTests" - - command: shell.exec - params: - working_dir: realm-core - shell: bash - script: |- - set -o errexit - set -o verbose - - if [[ -n "${c_compiler}" && "$(basename ${c_compiler})" = "clang" && -f "$(dirname ${c_compiler})/llvm-symbolizer" ]]; then - LLVM_SYMBOLIZER="$(dirname ${c_compiler})/llvm-symbolizer" - export ASAN_SYMBOLIZER_PATH="$(./evergreen/abspath.sh $LLVM_SYMBOLIZER)" - export TSAN_OPTIONS="external_symbolizer_path=$(./evergreen/abspath.sh $LLVM_SYMBOLIZER)" - fi - - export UNITTEST_EVERGREEN_TEST_RESULTS="$(./evergreen/abspath.sh ${task_name}_results.json)" - if [[ -n "$UNITTEST_EVERGREEN_TEST_RESULTS" && -f "$UNITTEST_EVERGREEN_TEST_RESULTS" ]]; then - rm "$UNITTEST_EVERGREEN_TEST_RESULTS" - fi - export UNITTEST_PROGRESS=1 - export UNITTEST_FILTER="Array_Example Transform_* EmbeddedObjects_*" - export UNITTEST_DUMP_TRANSFORM="changeset_dump" - - export TSAN_OPTIONS="suppressions=$(pwd)/test/tsan.suppress history_size=4 $TSAN_OPTIONS" - export UBSAN_OPTIONS="print_stacktrace=1" - - ./build/test/${cmake_build_type}/realm-sync-tests - - CHANGESET_DUMP_DIR="$(find . -type d -name changeset_dump -print -quit)" - mv "$CHANGESET_DUMP_DIR" changeset_dump - tar -czvf changeset_dump.tgz changeset_dump - - command: s3.put - params: - aws_key: '${artifacts_aws_access_key}' - aws_secret: '${artifacts_aws_secret_key}' - local_file: 'realm-core/changeset_dump.tgz' - remote_file: 'realm-core-stable/${branch_name}/${task_id}/${execution}/changeset_dump.tgz' - bucket: mciuploads - permissions: public-read - content_type: application/gzip - display_name: changeset dump tarball task_groups: - name: core_tests_group @@ -1309,7 +921,6 @@ task_groups: - func: "fetch binaries" teardown_task: - func: "upload test results" - - func: "upload baas artifacts" timeout: - func: "run hang analyzer" tasks: @@ -1330,21 +941,6 @@ task_groups: - func: "run hang analyzer" tasks: - compile - - ".test_suite !.requires_baas" - -# Runs only the baas integration tests -- name: only_baas_integration_tests - max_hosts: 1 - setup_group_can_fail_task: true - setup_group: - - func: "fetch source" - - func: "fetch binaries" - teardown_task: - - func: "upload test results" - timeout: - - func: "run hang analyzer" - tasks: - - ".requires_baas" # Runs object-store-tests against baas running on remote host - name: compile_test @@ -1355,7 +951,6 @@ task_groups: - func: "fetch binaries" teardown_task: - func: "upload test results" - - func: "upload baas artifacts" timeout: - func: "run hang analyzer" tasks: @@ -1371,22 +966,6 @@ task_groups: tasks: - compile-emscripten -# Runs object-store-tests against baas running on remote host and runs -# the network simulation tests as a separate task for nightly builds -- name: network_tests - max_hosts: 1 - setup_group_can_fail_task: true - setup_group: - - func: "fetch source" - - func: "fetch binaries" - teardown_task: - - func: "upload test results" - - func: "upload baas artifacts" - timeout: - - func: "run hang analyzer" - tasks: - - baas-network-tests - # Runs object-store-tests against baas running on remote host - name: compile_test_coverage max_hosts: 1 @@ -1396,7 +975,6 @@ task_groups: - func: "fetch binaries" teardown_task: - func: "upload test results" - - func: "upload baas artifacts" timeout: - func: "run hang analyzer" tasks: @@ -1499,28 +1077,6 @@ buildvariants: - name: compile_test - name: lint -- name: ubuntu-no-session-multiplexing - display_name: "Ubuntu (Sync Multiplexing Disabled)" - run_on: ubuntu2204-arm64-large - expansions: - fetch_missing_dependencies: On - c_compiler: "/opt/clang+llvm/bin/clang" - cxx_compiler: "/opt/clang+llvm/bin/clang++" - disable_sync_multiplexing: On - tasks: - - name: compile_test - -- name: ubuntu-with-redirects - display_name: "Ubuntu (Baas Redirector Enabled)" - run_on: ubuntu2204-arm64-large - expansions: - fetch_missing_dependencies: On - c_compiler: "/opt/clang+llvm/bin/clang" - cxx_compiler: "/opt/clang+llvm/bin/clang++" - enable_baas_redirector: On - tasks: - - name: only_baas_integration_tests - - name: ubuntu-no-geospatial display_name: "Ubuntu (Geospatial Disabled)" run_on: ubuntu2204-arm64-large @@ -1595,23 +1151,6 @@ buildvariants: # TODO RCORE-2085 ubuntu2004-release/ubuntu2004-arm64 build variants are here until we've established # a new baseline for the updated ubuntu 2204/clang 18 builders and to generate artifacts for the baas # team. -- name: ubuntu2004-release - display_name: "Ubuntu 20.04 x86_64 (Clang 11 release benchmarks/baas artifacts)" - run_on: ubuntu2004-large - expansions: - clang_url: "https://s3.amazonaws.com/static.realm.io/evergreen-assets/clang%2Bllvm-11.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz" - clang_sha256_sum: "829f5fb0ebda1d8716464394f97d5475d465ddc7bea2879c0601316b611ff6db" - cmake_url: "https://s3.amazonaws.com/static.realm.io/evergreen-assets/cmake-3.26.3-linux-x86_64.tar.gz" - cmake_bindir: "./cmake_binaries/bin" - cmake_build_type: RelWithDebInfo - fetch_missing_dependencies: On - c_compiler: "./clang_binaries/bin/clang" - cxx_compiler: "./clang_binaries/bin/clang++" - python3: /opt/mongodbtoolchain/v3/bin/python3 - tasks: - - name: compile_test_and_package - - name: benchmarks - - name: generate-sync-corpus - name: ubuntu2004-arm64 display_name: "Ubuntu 20.04 ARM64 (GCC 9 release benchmarks)" diff --git a/src/realm.h b/src/realm.h index bfc8b304694..3b6d1da39db 100644 --- a/src/realm.h +++ b/src/realm.h @@ -854,21 +854,6 @@ RLM_API void realm_config_set_scheduler(realm_config_t*, const realm_scheduler_t */ RLM_API void realm_config_set_sync_config(realm_config_t*, realm_sync_config_t*); -/** - * Get whether the realm file should be forcibly initialized as a synchronized. - * - * This function cannot fail. - */ -RLM_API bool realm_config_get_force_sync_history(const realm_config_t*); - -/** - * Force the realm file to be initialized as a synchronized realm, even if no - * sync config is provided (default: false). - * - * This function cannot fail. - */ -RLM_API void realm_config_set_force_sync_history(realm_config_t*, bool); - /** * Set the audit interface for the realm (unimplemented). */ diff --git a/src/realm/CMakeLists.txt b/src/realm/CMakeLists.txt index b5aebd5d3bf..7ee984d7154 100644 --- a/src/realm/CMakeLists.txt +++ b/src/realm/CMakeLists.txt @@ -426,9 +426,6 @@ install(FILES ${PROJECT_BINARY_DIR}/src/realm/util/config.h add_subdirectory(parser) add_subdirectory(object-store) -if (REALM_ENABLE_SYNC) - add_subdirectory(sync) -endif() if(NOT REALM_BUILD_LIB_ONLY AND NOT WINDOWS_STORE) add_subdirectory(exec) set_macos_only(exec) diff --git a/src/realm/db.cpp b/src/realm/db.cpp index ba0fbb8000b..fc7e744bf54 100644 --- a/src/realm/db.cpp +++ b/src/realm/db.cpp @@ -1560,30 +1560,6 @@ void DB::open(Replication& repl, const DBOptions& options) m_alloc.set_read_only(true); } -void DB::create_new_history(Replication& repl) -{ - Replication* old_repl = get_replication(); - try { - repl.initialize(*this); - set_replication(&repl); - - auto tr = start_write(); - tr->clear_history(); - tr->replicate(tr.get(), repl); - tr->commit(); - } - catch (...) { - set_replication(old_repl); - throw; - } -} - -void DB::create_new_history(std::unique_ptr repl) -{ - create_new_history(*repl); - m_history = std::move(repl); -} - // WARNING / FIXME: compact() should NOT be exposed publicly on Windows because it's not crash safe! It may // corrupt your database if something fails. // Tracked by https://github.com/realm/realm-core/issues/4111 diff --git a/src/realm/db.hpp b/src/realm/db.hpp index 39e5f3452d4..40f3aee9139 100644 --- a/src/realm/db.hpp +++ b/src/realm/db.hpp @@ -190,9 +190,6 @@ class DB : public std::enable_shared_from_this { return m_logger.get(); } - void create_new_history(Replication& repl) REQUIRES(!m_mutex); - void create_new_history(std::unique_ptr repl) REQUIRES(!m_mutex); - const std::string& get_path() const noexcept { return m_db_path; diff --git a/src/realm/exec/CMakeLists.txt b/src/realm/exec/CMakeLists.txt index 4897f62a52a..4a6cf4f350b 100644 --- a/src/realm/exec/CMakeLists.txt +++ b/src/realm/exec/CMakeLists.txt @@ -59,15 +59,13 @@ set_target_properties(RealmBrowser PROPERTIES ) target_link_libraries(RealmBrowser Storage) -if(REALM_ENABLE_SYNC) add_executable(Realm2JSON realm2json.cpp ) set_target_properties(Realm2JSON PROPERTIES OUTPUT_NAME "realm2json" DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX} ) -target_link_libraries(Realm2JSON Sync QueryParser) +target_link_libraries(Realm2JSON QueryParser) list(APPEND ExecTargetsToInstall Realm2JSON) -endif() add_executable(RealmDump realm_dump.c) set_target_properties(RealmDump PROPERTIES diff --git a/src/realm/exec/realm2json.cpp b/src/realm/exec/realm2json.cpp index 0edfe78fa90..7f7cb65b8cb 100644 --- a/src/realm/exec/realm2json.cpp +++ b/src/realm/exec/realm2json.cpp @@ -1,5 +1,4 @@ #include -#include #include const char* legend = @@ -118,7 +117,7 @@ int main(int argc, char const* argv[]) return 0; } catch (const realm::IncompatibleHistories&) { - hist = realm::sync::make_client_replication(); + // TODO hist = realm::sync::make_client_replication(); options.allow_file_format_upgrade = false; options.is_immutable = true; } diff --git a/src/realm/object-store/CMakeLists.txt b/src/realm/object-store/CMakeLists.txt index f4f5e68b90d..16e4ba26078 100644 --- a/src/realm/object-store/CMakeLists.txt +++ b/src/realm/object-store/CMakeLists.txt @@ -89,75 +89,6 @@ set(HEADERS c_api/conversion.hpp ) -if(REALM_ENABLE_SYNC) - list(APPEND HEADERS - sync/async_open_task.hpp - sync/generic_network_transport.hpp - sync/jwt.hpp - sync/sync_manager.hpp - sync/sync_session.hpp - sync/sync_user.hpp - - sync/impl/network_reachability.hpp - sync/impl/sync_client.hpp - sync/impl/sync_file.hpp) - - list(APPEND SOURCES - sync/async_open_task.cpp - sync/generic_network_transport.cpp - sync/impl/sync_file.cpp - sync/jwt.cpp - sync/sync_manager.cpp - sync/sync_session.cpp) - - if (REALM_APP_SERVICES) - list(APPEND HEADERS - sync/app.hpp - sync/app_config.hpp - sync/app_credentials.hpp - sync/app_service_client.hpp - sync/app_user.hpp - sync/app_utils.hpp - sync/auth_request_client.hpp - sync/impl/app_metadata.hpp - sync/mongo_client.hpp - sync/mongo_collection.hpp - sync/mongo_database.hpp - sync/push_client.hpp - sync/subscribable.hpp - ) - list(APPEND SOURCES - sync/app.cpp - sync/app_credentials.cpp - sync/app_user.cpp - sync/app_utils.cpp - sync/impl/app_metadata.cpp - sync/mongo_client.cpp - sync/mongo_collection.cpp - sync/mongo_database.cpp - sync/push_client.cpp - ) - endif() - - if(APPLE) - list(APPEND HEADERS - sync/impl/apple/network_reachability_observer.hpp - sync/impl/apple/system_configuration.hpp) - - list(APPEND SOURCES - audit.mm - sync/impl/apple/network_reachability_observer.cpp - sync/impl/apple/system_configuration.cpp) - elseif(EMSCRIPTEN) - list(APPEND HEADERS - sync/impl/emscripten/network_transport.hpp - sync/impl/emscripten/socket_provider.hpp) - list(APPEND SOURCES - sync/impl/emscripten/network_transport.cpp - sync/impl/emscripten/socket_provider.cpp) - endif() -endif() - add_library(ObjectStore STATIC ${SOURCES} ${HEADERS}) add_library(Realm::ObjectStore ALIAS ObjectStore) @@ -181,33 +112,7 @@ else() target_sources(ObjectStore PRIVATE impl/generic/external_commit_helper.cpp) endif() -if(REALM_ENABLE_SYNC AND NOT REALM_SYNC_MULTIPLEXING) - target_compile_definitions(ObjectStore PUBLIC REALM_DISABLE_SYNC_MULTIPLEXING=1) -endif() - -if(REALM_ENABLE_SYNC OR REALM_ENABLE_SERVER) - # needed to disable assertions in external/json - target_compile_definitions(ObjectStore PUBLIC - $<$:NDEBUG> - $<$:NDEBUG> - ) - target_include_directories(ObjectStore PRIVATE ${JSON_INCLUDE_DIR}) -endif() - target_link_libraries(ObjectStore PUBLIC Storage QueryParser) - -if(REALM_ENABLE_SYNC) - target_link_libraries(ObjectStore PUBLIC Sync) - target_compile_definitions(ObjectStore PUBLIC REALM_ENABLE_SYNC=1) - if(EMSCRIPTEN) - target_link_options(ObjectStore PUBLIC -sFETCH=1 -sFETCH_SUPPORT_INDEXEDDB=0 -lwebsocket.js) - endif() -endif() - -if (REALM_APP_SERVICES) - target_compile_definitions(ObjectStore PUBLIC REALM_APP_SERVICES=1) -endif() - set_target_properties(ObjectStore PROPERTIES OUTPUT_NAME "realm-object-store") foreach(FILE ${HEADERS}) diff --git a/src/realm/object-store/c_api/CMakeLists.txt b/src/realm/object-store/c_api/CMakeLists.txt index 2b017927bba..7ca83e212ca 100644 --- a/src/realm/object-store/c_api/CMakeLists.txt +++ b/src/realm/object-store/c_api/CMakeLists.txt @@ -21,15 +21,6 @@ set(REALM_FFI_SOURCES # realm/object-store/c_api/realm.c ) -if(REALM_ENABLE_SYNC) - list(APPEND REALM_FFI_SOURCES - app.cpp - http.cpp - sync.cpp - socket_provider.cpp - ) -endif() - if(NOT EMSCRIPTEN) add_library(RealmFFI SHARED ${REALM_FFI_SOURCES}) else() @@ -43,11 +34,6 @@ target_compile_definitions(RealmFFIStatic PUBLIC -DRLM_NO_DLLIMPORT) target_link_libraries(RealmFFI PRIVATE Storage ObjectStore QueryParser) target_link_libraries(RealmFFIStatic PRIVATE Storage ObjectStore QueryParser) -if (${REALM_ENABLE_SYNC}) - target_link_libraries(RealmFFI PRIVATE Sync) - target_link_libraries(RealmFFIStatic PRIVATE Sync) -endif() - if (ANDROID) target_link_libraries(RealmFFI PUBLIC android log) endif() diff --git a/src/realm/object-store/c_api/app.cpp b/src/realm/object-store/c_api/app.cpp deleted file mode 100644 index 759a6a381ab..00000000000 --- a/src/realm/object-store/c_api/app.cpp +++ /dev/null @@ -1,1288 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include "types.hpp" -#include "util.hpp" -#include "conversion.hpp" - -#include -#include -#include - -namespace realm::c_api { -using namespace realm::app; - -static_assert(realm_user_state_e(SyncUser::State::LoggedOut) == RLM_USER_STATE_LOGGED_OUT); -static_assert(realm_user_state_e(SyncUser::State::LoggedIn) == RLM_USER_STATE_LOGGED_IN); -static_assert(realm_user_state_e(SyncUser::State::Removed) == RLM_USER_STATE_REMOVED); - -#if REALM_APP_SERVICES - -static_assert(realm_auth_provider_e(AuthProvider::ANONYMOUS) == RLM_AUTH_PROVIDER_ANONYMOUS); -static_assert(realm_auth_provider_e(AuthProvider::ANONYMOUS_NO_REUSE) == RLM_AUTH_PROVIDER_ANONYMOUS_NO_REUSE); -static_assert(realm_auth_provider_e(AuthProvider::FACEBOOK) == RLM_AUTH_PROVIDER_FACEBOOK); -static_assert(realm_auth_provider_e(AuthProvider::GOOGLE) == RLM_AUTH_PROVIDER_GOOGLE); -static_assert(realm_auth_provider_e(AuthProvider::APPLE) == RLM_AUTH_PROVIDER_APPLE); -static_assert(realm_auth_provider_e(AuthProvider::CUSTOM) == RLM_AUTH_PROVIDER_CUSTOM); -static_assert(realm_auth_provider_e(AuthProvider::USERNAME_PASSWORD) == RLM_AUTH_PROVIDER_EMAIL_PASSWORD); -static_assert(realm_auth_provider_e(AuthProvider::FUNCTION) == RLM_AUTH_PROVIDER_FUNCTION); -static_assert(realm_auth_provider_e(AuthProvider::API_KEY) == RLM_AUTH_PROVIDER_API_KEY); - -static_assert(realm_sync_client_metadata_mode_e(app::AppConfig::MetadataMode::NoEncryption) == - RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT); -static_assert(realm_sync_client_metadata_mode_e(app::AppConfig::MetadataMode::Encryption) == - RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED); -static_assert(realm_sync_client_metadata_mode_e(app::AppConfig::MetadataMode::InMemory) == - RLM_SYNC_CLIENT_METADATA_MODE_DISABLED); - -static inline bson::BsonArray parse_ejson_array(const char* serialized) -{ - if (!serialized) { - return {}; - } - else { - return bson::BsonArray(bson::parse({serialized, strlen(serialized)})); - } -} - -static realm_app_error_t to_capi(const AppError& error) -{ - auto ret = realm_app_error_t(); - - ret.error = realm_errno_e(error.code()); - ret.categories = ErrorCodes::error_categories(error.code()).value(); - - if (error.additional_status_code) { - ret.http_status_code = *error.additional_status_code; - } - - ret.message = error.what(); - - if (error.link_to_server_logs.size() > 0) { - ret.link_to_server_logs = error.link_to_server_logs.c_str(); - } - - return ret; -} - -static inline realm_app_user_apikey_t to_capi(const App::UserAPIKey& apikey) -{ - return {to_capi(apikey.id), apikey.key ? apikey.key->c_str() : nullptr, apikey.name.c_str(), apikey.disabled}; -} - -static inline auto make_callback(realm_app_void_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return - [callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](util::Optional error) { - if (error) { - realm_app_error_t c_err{to_capi(*error)}; - callback(userdata.get(), &c_err); - } - else { - callback(userdata.get(), nullptr); - } - }; -} - -static inline auto make_callback(realm_app_user_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return [callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - std::shared_ptr user, util::Optional error) { - if (error) { - realm_app_error_t c_err{to_capi(*error)}; - callback(userdata.get(), nullptr, &c_err); - } - else { - auto c_user = realm_user_t(std::move(user)); - callback(userdata.get(), &c_user, nullptr); - } - }; -} - -static inline auto make_callback(void (*callback)(realm_userdata_t userdata, realm_app_user_apikey_t*, - const realm_app_error_t*), - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return [callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - App::UserAPIKey apikey, util::Optional error) { - if (error) { - realm_app_error_t c_error(to_capi(*error)); - callback(userdata.get(), nullptr, &c_error); - } - else { - realm_app_user_apikey_t c_apikey(to_capi(apikey)); - callback(userdata.get(), &c_apikey, nullptr); - } - }; -} - -RLM_API const char* realm_app_get_default_base_url(void) noexcept -{ - return app::App::default_base_url().data(); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_anonymous(bool reuse_credentials) noexcept -{ - return new realm_app_credentials_t(AppCredentials::anonymous(reuse_credentials)); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_facebook(const char* access_token) noexcept -{ - return new realm_app_credentials_t(AppCredentials::facebook(access_token)); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_google_id_token(const char* id_token) noexcept -{ - return new realm_app_credentials_t(AppCredentials::google(IdToken(id_token))); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_google_auth_code(const char* auth_code) noexcept -{ - return new realm_app_credentials_t(AppCredentials::google(AuthCode(auth_code))); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_apple(const char* id_token) noexcept -{ - return new realm_app_credentials_t(AppCredentials::apple(id_token)); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_jwt(const char* jwt_token) noexcept -{ - return new realm_app_credentials_t(AppCredentials::custom(jwt_token)); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_email_password(const char* email, - realm_string_t password) noexcept -{ - return new realm_app_credentials_t(AppCredentials::username_password(email, from_capi(password))); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_function(const char* serialized_ejson_payload) -{ - return wrap_err([&] { - return new realm_app_credentials_t(AppCredentials::function(serialized_ejson_payload)); - }); -} - -RLM_API realm_app_credentials_t* realm_app_credentials_new_api_key(const char* api_key) noexcept -{ - return new realm_app_credentials_t(AppCredentials::api_key(api_key)); -} - -RLM_API realm_auth_provider_e realm_auth_credentials_get_provider(realm_app_credentials_t* credentials) noexcept -{ - return realm_auth_provider_e(credentials->provider()); -} - -RLM_API realm_app_config_t* realm_app_config_new(const char* app_id, - const realm_http_transport_t* http_transport) noexcept -{ - auto* config = new realm_app_config_t; - config->app_id = app_id; - config->transport = *http_transport; // realm_http_transport_t is a shared_ptr - return config; -} - -RLM_API void realm_app_config_set_base_url(realm_app_config_t* config, const char* base_url) noexcept -{ - config->base_url = std::string(base_url); -} - -RLM_API void realm_app_config_set_default_request_timeout(realm_app_config_t* config, uint64_t ms) noexcept -{ - config->default_request_timeout_ms = ms; -} - -RLM_API void realm_app_config_set_platform_version(realm_app_config_t* config, const char* platform_version) noexcept -{ - config->device_info.platform_version = std::string(platform_version); -} - -RLM_API void realm_app_config_set_sdk_version(realm_app_config_t* config, const char* sdk_version) noexcept -{ - config->device_info.sdk_version = std::string(sdk_version); -} - -RLM_API void realm_app_config_set_sdk(realm_app_config_t* config, const char* sdk) noexcept -{ - config->device_info.sdk = std::string(sdk); -} - -RLM_API void realm_app_config_set_device_name(realm_app_config_t* config, const char* device_name) noexcept -{ - config->device_info.device_name = std::string(device_name); -} - -RLM_API void realm_app_config_set_device_version(realm_app_config_t* config, const char* device_version) noexcept -{ - config->device_info.device_version = std::string(device_version); -} - -RLM_API void realm_app_config_set_framework_name(realm_app_config_t* config, const char* framework_name) noexcept -{ - config->device_info.framework_name = std::string(framework_name); -} - -RLM_API void realm_app_config_set_framework_version(realm_app_config_t* config, - const char* framework_version) noexcept -{ - config->device_info.framework_version = std::string(framework_version); -} - -RLM_API void realm_app_config_set_bundle_id(realm_app_config_t* config, const char* bundle_id) noexcept -{ - config->device_info.bundle_id = std::string(bundle_id); -} - -RLM_API void realm_app_config_set_base_file_path(realm_app_config_t* config, const char* path) noexcept -{ - config->base_file_path = path; -} - -RLM_API void realm_app_config_set_metadata_mode(realm_app_config_t* config, - realm_sync_client_metadata_mode_e mode) noexcept -{ - config->metadata_mode = app::AppConfig::MetadataMode(mode); -} - -RLM_API void realm_app_config_set_metadata_encryption_key(realm_app_config_t* config, const uint8_t key[64]) noexcept -{ - config->custom_encryption_key = std::vector(key, key + 64); -} - -RLM_API void realm_app_config_set_security_access_group(realm_app_config_t* config, const char* group) noexcept -{ - config->security_access_group = group; -} - -RLM_API const char* realm_app_credentials_serialize_as_json(realm_app_credentials_t* app_credentials) noexcept -{ - return wrap_err([&] { - return duplicate_string(app_credentials->serialize_as_json()); - }); -} - -RLM_API realm_sync_client_config_t* realm_app_config_get_sync_client_config(realm_app_config_t* app_config) noexcept -{ - return static_cast(&app_config->sync_client_config); -} - -RLM_API realm_app_t* realm_app_create(const realm_app_config_t* app_config) -{ - return wrap_err([&] { - return new realm_app_t(App::get_app(app::App::CacheMode::Disabled, *app_config)); - }); -} - -RLM_API realm_app_t* realm_app_create_cached(const realm_app_config_t* app_config) -{ - return wrap_err([&] { - return new realm_app_t(App::get_app(app::App::CacheMode::Enabled, *app_config)); - }); -} - -RLM_API bool realm_app_get_cached(const char* app_id, const char* base_url, realm_app_t** out_app) -{ - return wrap_err([&] { - auto app = - App::get_cached_app(std::string(app_id), base_url ? util::some(base_url) : util::none); - if (out_app) { - *out_app = app ? new realm_app_t(app) : nullptr; - } - - return true; - }); -} - -RLM_API void realm_clear_cached_apps(void) noexcept -{ - App::clear_cached_apps(); -} - -RLM_API const char* realm_app_get_app_id(const realm_app_t* app) noexcept -{ - return (*app)->app_id().c_str(); -} - -RLM_API bool realm_app_update_base_url(realm_app_t* app, const char* base_url, - realm_app_void_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->update_base_url(base_url ? base_url : "", make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API char* realm_app_get_base_url(realm_app_t* app) noexcept -{ - auto url_stg = (*app)->get_base_url(); - return duplicate_string(url_stg); -} - -RLM_API realm_user_t* realm_app_get_current_user(const realm_app_t* app) noexcept -{ - if (auto user = (*app)->current_user()) { - return new realm_user_t(user); - } - - return nullptr; -} - -RLM_API bool realm_app_get_all_users(const realm_app_t* app, realm_user_t** out_users, size_t capacity, size_t* out_n) -{ - return wrap_err([&] { - const auto& users = (*app)->all_users(); - set_out_param(out_n, users.size()); - if (out_users && capacity >= users.size()) { - OutBuffer buf(out_users); - for (const auto& user : users) { - buf.emplace(user); - } - buf.release(out_n); - } - return true; - }); -} - -RLM_API bool realm_app_log_in_with_credentials(realm_app_t* app, realm_app_credentials_t* credentials, - realm_app_user_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->log_in_with_credentials(*credentials, make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_log_out_current_user(realm_app_t* app, realm_app_void_completion_func_t callback, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->log_out(make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -namespace { -template -auto with_app_user(const realm_user_t* user, Fn&& fn) -{ - auto app_user = std::dynamic_pointer_cast(*user); - return wrap_err([&] { - if (!app_user) { - throw Exception(ErrorCodes::InvalidArgument, "App Services function require a user obtained from an App"); - } - if constexpr (std::is_void_v) { - fn(app_user); - return true; - } - else { - return fn(app_user); - } - }); -} -} // anonymous namespace - -RLM_API bool realm_app_refresh_custom_data(realm_app_t* app, realm_user_t* user, - realm_app_void_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->refresh_custom_data(user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_log_out(realm_app_t* app, realm_user_t* user, realm_app_void_completion_func_t callback, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->log_out(user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_link_user(realm_app_t* app, realm_user_t* user, realm_app_credentials_t* credentials, - realm_app_user_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->link_user(user, *credentials, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_switch_user(realm_app_t* app, realm_user_t* user) -{ - return with_app_user(user, [&](auto& user) { - (*app)->switch_user(user); - }); -} - -RLM_API bool realm_app_remove_user(realm_app_t* app, realm_user_t* user, realm_app_void_completion_func_t callback, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->remove_user(user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_delete_user(realm_app_t* app, realm_user_t* user, realm_app_void_completion_func_t callback, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->delete_user(user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_email_password_provider_client_register_email(realm_app_t* app, const char* email, - realm_string_t password, - realm_app_void_completion_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->provider_client().register_email( - email, from_capi(password), make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_email_password_provider_client_confirm_user(realm_app_t* app, const char* token, - const char* token_id, - realm_app_void_completion_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->provider_client().confirm_user( - token, token_id, make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_email_password_provider_client_resend_confirmation_email( - realm_app_t* app, const char* email, realm_app_void_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->provider_client().resend_confirmation_email( - email, make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_email_password_provider_client_send_reset_password_email( - realm_app_t* app, const char* email, realm_app_void_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->provider_client().send_reset_password_email( - email, make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_email_password_provider_client_retry_custom_confirmation( - realm_app_t* app, const char* email, realm_app_void_completion_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->provider_client().retry_custom_confirmation( - email, make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_email_password_provider_client_reset_password(realm_app_t* app, realm_string_t password, - const char* token, const char* token_id, - realm_app_void_completion_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - (*app)->provider_client().reset_password( - from_capi(password), token, token_id, make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_email_password_provider_client_call_reset_password_function( - realm_app_t* app, const char* email, realm_string_t password, const char* serialized_ejson_payload, - realm_app_void_completion_func_t callback, realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return wrap_err([&] { - bson::BsonArray args = parse_ejson_array(serialized_ejson_payload); - (*app)->provider_client().call_reset_password_function( - email, from_capi(password), args, make_callback(callback, userdata, userdata_free)); - return true; - }); -} - -RLM_API bool realm_app_user_apikey_provider_client_create_apikey(const realm_app_t* app, const realm_user_t* user, - const char* name, - realm_return_apikey_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->provider_client().create_api_key( - name, user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_user_apikey_provider_client_fetch_apikey(const realm_app_t* app, const realm_user_t* user, - realm_object_id_t id, - realm_return_apikey_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->provider_client().fetch_api_key( - from_capi(id), user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_user_apikey_provider_client_fetch_apikeys(const realm_app_t* app, const realm_user_t* user, - realm_return_apikey_list_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - auto cb = [callback, userdata = SharedUserdata{userdata, FreeUserdata(userdata_free)}]( - std::vector apikeys, util::Optional error) { - if (error) { - realm_app_error_t c_error(to_capi(*error)); - callback(userdata.get(), nullptr, 0, &c_error); - } - else { - std::vector c_apikeys; - c_apikeys.reserve(apikeys.size()); - for (const auto& apikey : apikeys) { - c_apikeys.push_back(to_capi(apikey)); - } - callback(userdata.get(), c_apikeys.data(), c_apikeys.size(), nullptr); - } - }; - - (*app)->provider_client().fetch_api_keys(user, std::move(cb)); - }); -} - -RLM_API bool realm_app_user_apikey_provider_client_delete_apikey(const realm_app_t* app, const realm_user_t* user, - realm_object_id_t id, - realm_app_void_completion_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->provider_client().delete_api_key( - from_capi(id), user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_user_apikey_provider_client_enable_apikey(const realm_app_t* app, const realm_user_t* user, - realm_object_id_t id, - realm_app_void_completion_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->provider_client().enable_api_key( - from_capi(id), user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_user_apikey_provider_client_disable_apikey(const realm_app_t* app, const realm_user_t* user, - realm_object_id_t id, - realm_app_void_completion_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app)->provider_client().disable_api_key( - from_capi(id), user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_push_notification_client_register_device( - const realm_app_t* app, const realm_user_t* user, const char* service_name, const char* registration_token, - realm_app_void_completion_func_t callback, realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app) - ->push_notification_client(service_name) - .register_device(registration_token, user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_push_notification_client_deregister_device(const realm_app_t* app, const realm_user_t* user, - const char* service_name, - realm_app_void_completion_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - (*app) - ->push_notification_client(service_name) - .deregister_device(user, make_callback(callback, userdata, userdata_free)); - }); -} - -RLM_API bool realm_app_call_function(const realm_app_t* app, const realm_user_t* user, const char* function_name, - const char* serialized_ejson_payload, const char* service_name, - realm_return_string_func_t callback, realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - auto cb = [callback, userdata = SharedUserdata{userdata, FreeUserdata(userdata_free)}]( - const std::string* reply, util::Optional error) { - if (error) { - realm_app_error_t c_error(to_capi(*error)); - callback(userdata.get(), nullptr, &c_error); - } - else { - callback(userdata.get(), reply->c_str(), nullptr); - } - }; - util::Optional service_name_opt = - service_name ? util::some(service_name) : util::none; - (*app)->call_function(user, function_name, serialized_ejson_payload, service_name_opt, std::move(cb)); - }); -} - -RLM_API void realm_app_sync_client_reconnect(realm_app_t* app) noexcept -{ - (*app)->sync_manager()->reconnect(); -} - -RLM_API bool realm_app_sync_client_has_sessions(const realm_app_t* app) noexcept -{ - return (*app)->sync_manager()->has_existing_sessions(); -} - -RLM_API void realm_app_sync_client_wait_for_sessions_to_terminate(realm_app_t* app) noexcept -{ - (*app)->sync_manager()->wait_for_sessions_to_terminate(); -} - -RLM_API char* realm_app_sync_client_get_default_file_path_for_realm(const realm_sync_config_t* config, - const char* custom_filename) -{ - return wrap_err([&]() -> char* { - auto user = std::dynamic_pointer_cast(config->user); - if (!user) { - return nullptr; - } - util::Optional filename = - custom_filename ? util::some(custom_filename) : util::none; - std::string file_path = user->app()->path_for_realm(*config, std::move(filename)); - return duplicate_string(file_path); - }); -} - -RLM_API bool realm_user_get_all_identities(const realm_user_t* user, realm_user_identity_t* out_identities, - size_t max, size_t* out_n) -{ - return with_app_user(user, [&](auto& user) { - const auto& identities = user->identities(); - set_out_param(out_n, identities.size()); - if (out_identities && max >= identities.size()) { - for (size_t i = 0; i < identities.size(); i++) { - out_identities[i] = {duplicate_string(identities[i].id), - realm_auth_provider_e(enum_from_provider_type(identities[i].provider_type))}; - } - } - }); -} - -RLM_API char* realm_user_get_device_id(const realm_user_t* user) noexcept -{ - char* device_id = nullptr; - with_app_user(user, [&](auto& user) { - if (user->has_device_id()) { - device_id = duplicate_string(user->device_id()); - } - }); - return device_id; -} - -RLM_API bool realm_user_log_out(realm_user_t* user) -{ - return with_app_user(user, [&](auto& user) { - user->log_out(); - }); -} - -RLM_API char* realm_user_get_profile_data(const realm_user_t* user) -{ - return with_app_user(user, [&](auto& user) { - std::string data = bson::Bson(user->user_profile().data()).to_string(); - return duplicate_string(data); - }); -} - -RLM_API char* realm_user_get_custom_data(const realm_user_t* user) noexcept -{ - return with_app_user(user, [&](auto& user) -> char* { - if (const auto& data = user->custom_data()) { - std::string json = bson::Bson(*data).to_string(); - return duplicate_string(json); - } - return nullptr; - }); -} - -RLM_API realm_app_t* realm_user_get_app(const realm_user_t* user) noexcept -{ - REALM_ASSERT(user); - return with_app_user(user, [&](auto& user) { - return new realm_app_t(user->app()); - }); -} - -RLM_API char* realm_user_get_identity(const realm_user_t* user) noexcept -{ - return duplicate_string((*user)->user_id()); -} - -RLM_API realm_user_state_e realm_user_get_state(const realm_user_t* user) noexcept -{ - return realm_user_state_e((*user)->state()); -} - -RLM_API bool realm_user_is_logged_in(const realm_user_t* user) noexcept -{ - return (*user)->is_logged_in(); -} - -RLM_API char* realm_user_get_access_token(const realm_user_t* user) -{ - return wrap_err([&] { - return duplicate_string((*user)->access_token()); - }); -} - -RLM_API char* realm_user_get_refresh_token(const realm_user_t* user) -{ - return wrap_err([&] { - return duplicate_string((*user)->refresh_token()); - }); -} - -RLM_API realm_app_user_subscription_token_t* -realm_sync_user_on_state_change_register_callback(realm_user_t* user, realm_sync_on_user_state_changed_t callback, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - return with_app_user(user, [&](auto& user) { - auto cb = [callback, - userdata = SharedUserdata{userdata, FreeUserdata(userdata_free)}](const SyncUser& sync_user) { - callback(userdata.get(), realm_user_state_e(sync_user.state())); - }; - auto token = user->subscribe(std::move(cb)); - return new realm_app_user_subscription_token_t{user, std::move(token)}; - }); -} - -RLM_API bool realm_sync_immediately_run_file_actions(realm_app_t* realm_app, const char* sync_path, - bool* did_run) noexcept -{ - return wrap_err([&]() { - *did_run = (*realm_app)->immediately_run_file_actions(sync_path); - return true; - }); -} - -#endif // REALM_APP_SERVICES - -#if !REALM_APP_SERVICES - -static void cb_proxy_for_completion(realm_userdata_t userdata, const realm_app_error_t* err) -{ - SyncUser::CompletionHandler* cxx_cb = static_cast(userdata); - REALM_ASSERT(cxx_cb); - std::optional cxx_err; - if (err) { - std::optional additional_error_code; - if (err->http_status_code) { - additional_error_code = err->http_status_code; - } - cxx_err = - AppError(ErrorCodes::Error(err->error), err->message, err->link_to_server_logs, additional_error_code); - } - (*cxx_cb)(cxx_err); - delete cxx_cb; -} - -struct CAPIAppUser : SyncUser { - void* m_userdata = nullptr; - realm_free_userdata_func_t m_free = nullptr; - const std::string m_app_id; - const std::string m_user_id; - realm_user_get_access_token_cb_t m_access_token_cb = nullptr; - realm_user_get_refresh_token_cb_t m_refresh_token_cb = nullptr; - realm_user_state_cb_t m_state_cb = nullptr; - realm_user_access_token_refresh_required_cb_t m_atrr_cb = nullptr; - realm_user_get_sync_manager_cb_t m_sync_manager_cb = nullptr; - realm_user_request_log_out_cb_t m_request_log_out_cb = nullptr; - realm_user_request_refresh_location_cb_t m_request_refresh_location_cb = nullptr; - realm_user_request_access_token_cb_t m_request_access_token_cb = nullptr; - realm_user_track_realm_cb_t m_track_realm_cb = nullptr; - realm_user_create_file_action_cb_t m_create_fa_cb = nullptr; - - CAPIAppUser(const char* app_id, const char* user_id) - : m_app_id(app_id) - , m_user_id(user_id) - { - } - CAPIAppUser(CAPIAppUser&& other) - : m_userdata(std::exchange(other.m_userdata, nullptr)) - , m_free(std::exchange(other.m_free, nullptr)) - , m_app_id(std::move(other.m_app_id)) - , m_user_id(std::move(other.m_user_id)) - , m_access_token_cb(std::move(other.m_access_token_cb)) - , m_refresh_token_cb(std::move(other.m_refresh_token_cb)) - , m_state_cb(std::move(other.m_state_cb)) - , m_atrr_cb(std::move(other.m_atrr_cb)) - , m_sync_manager_cb(std::move(other.m_sync_manager_cb)) - , m_request_log_out_cb(std::move(other.m_request_log_out_cb)) - , m_request_refresh_location_cb(std::move(other.m_request_refresh_location_cb)) - , m_request_access_token_cb(std::move(other.m_request_access_token_cb)) - , m_track_realm_cb(std::move(other.m_track_realm_cb)) - , m_create_fa_cb(std::move(other.m_create_fa_cb)) - { - } - - ~CAPIAppUser() - { - if (m_free) - m_free(m_userdata); - } - std::string user_id() const noexcept override - { - return m_user_id; - } - std::string app_id() const noexcept override - { - return m_app_id; - } - std::string access_token() const override - { - return m_access_token_cb(m_userdata); - } - std::string refresh_token() const override - { - return m_refresh_token_cb(m_userdata); - } - State state() const override - { - return State(m_state_cb(m_userdata)); - } - bool access_token_refresh_required() const override - { - return m_atrr_cb(m_userdata); - } - SyncManager* sync_manager() override - { - auto value = m_sync_manager_cb(m_userdata); - if (value && value->get()) { - return (value->get()); - } - return nullptr; - } - void request_log_out() override - { - m_request_log_out_cb(m_userdata); - } - void request_refresh_location(CompletionHandler&& callback) override - { - auto unscoped_cb = new CompletionHandler(std::move(callback)); - m_request_refresh_location_cb(m_userdata, cb_proxy_for_completion, unscoped_cb); - } - void request_access_token(CompletionHandler&& callback) override - { - auto unscoped_cb = new CompletionHandler(std::move(callback)); - m_request_access_token_cb(m_userdata, cb_proxy_for_completion, unscoped_cb); - } - void track_realm(std::string_view path) override - { - if (m_track_realm_cb) { - m_track_realm_cb(m_userdata, path.data()); - } - } - std::string create_file_action(SyncFileAction a, std::string_view path, - std::optional recovery_dir) override - { - - if (m_create_fa_cb) { - return m_create_fa_cb(m_userdata, realm_sync_file_action_e(a), path.data(), - recovery_dir ? recovery_dir->data() : nullptr); - } - return ""; - } -}; - -RLM_API realm_user_t* realm_user_new(realm_sync_user_create_config_t c) noexcept -{ - // optional to provide: - // m_userdata - // m_free - // m_track_realm_cb - // m_create_fa_cb - - REALM_ASSERT(c.app_id); - REALM_ASSERT(c.user_id); - REALM_ASSERT(c.access_token_cb); - REALM_ASSERT(c.refresh_token_cb); - REALM_ASSERT(c.state_cb); - REALM_ASSERT(c.atrr_cb); - REALM_ASSERT(c.sync_manager_cb); - REALM_ASSERT(c.request_log_out_cb); - REALM_ASSERT(c.request_refresh_location_cb); - REALM_ASSERT(c.request_access_token_cb); - - return wrap_err([&]() { - auto capi_user = std::make_shared(c.app_id, c.user_id); - capi_user->m_userdata = c.userdata; - capi_user->m_free = c.free_func; - capi_user->m_access_token_cb = c.access_token_cb; - capi_user->m_refresh_token_cb = c.refresh_token_cb; - capi_user->m_state_cb = c.state_cb; - capi_user->m_atrr_cb = c.atrr_cb; - capi_user->m_sync_manager_cb = c.sync_manager_cb; - capi_user->m_request_log_out_cb = c.request_log_out_cb; - capi_user->m_request_refresh_location_cb = c.request_refresh_location_cb; - capi_user->m_request_access_token_cb = c.request_access_token_cb; - capi_user->m_track_realm_cb = c.track_realm_cb; - capi_user->m_create_fa_cb = c.create_fa_cb; - - return new realm_user_t(std::move(capi_user)); - }); -} - -RLM_API realm_sync_manager_t* realm_sync_manager_create(const realm_sync_client_config_t* config) -{ - return wrap_err([&]() { - auto manager = SyncManager::create(*config); - return new realm_sync_manager_t(std::move(manager)); - }); -} - -RLM_API void realm_sync_manager_set_route(const realm_sync_manager_t* manager, const char* route, bool is_verified) -{ - REALM_ASSERT(manager); - (*manager)->set_sync_route(route, is_verified); -} -#endif // #!REALM_APP_SERVICES - -#if REALM_APP_SERVICES - -namespace { -template -util::Optional convert_to_optional(T data) -{ - return data ? util::Optional(data) : util::Optional(); -} - -template -util::Optional convert_to_optional_bson(realm_string_t doc) -{ - if (doc.data == nullptr || doc.size == 0) { - return util::Optional(); - } - return util::Optional(static_cast(bson::parse({doc.data, doc.size}))); -} - -template -T convert_to_bson(realm_string_t doc) -{ - auto res = convert_to_optional_bson(doc); - return res ? *res : T(); -} - -MongoCollection::FindOptions to_mongodb_collection_find_options(const realm_mongodb_find_options_t* options) -{ - MongoCollection::FindOptions mongodb_options; - mongodb_options.projection_bson = convert_to_optional_bson(options->projection_bson); - mongodb_options.sort_bson = convert_to_optional_bson(options->sort_bson); - mongodb_options.limit = convert_to_optional(options->limit); - return mongodb_options; -} - -MongoCollection::FindOneAndModifyOptions -to_mongodb_collection_find_one_and_modify_options(const realm_mongodb_find_one_and_modify_options_t* options) -{ - MongoCollection::FindOneAndModifyOptions mongodb_options; - mongodb_options.projection_bson = convert_to_optional_bson(options->projection_bson); - mongodb_options.sort_bson = convert_to_optional_bson(options->sort_bson); - mongodb_options.upsert = options->upsert; - mongodb_options.return_new_document = options->return_new_document; - return mongodb_options; -} - -void handle_mongodb_collection_result(util::Optional bson, util::Optional app_error, - UserdataPtr data, realm_mongodb_callback_t callback) -{ - if (app_error) { - auto error = to_capi(*app_error); - callback(data.get(), {nullptr, 0}, &error); - } - else if (bson) { - const auto& bson_data = bson->to_string(); - callback(data.get(), {bson_data.c_str(), bson_data.size()}, nullptr); - } -} -} // anonymous namespace - -RLM_API realm_mongodb_collection_t* realm_mongo_collection_get(realm_user_t* user, const char* service, - const char* database, const char* collection) -{ - REALM_ASSERT(user); - REALM_ASSERT(service); - REALM_ASSERT(database); - REALM_ASSERT(collection); - return with_app_user(user, [&](auto& user) { - auto col = user->mongo_client(service).db(database).collection(collection); - return new realm_mongodb_collection_t(col); - }); -} - -RLM_API bool realm_mongo_collection_find(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - const realm_mongodb_find_options_t* options, realm_userdata_t data, - realm_free_userdata_func_t delete_data, realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - REALM_ASSERT(options); - return wrap_err([&] { - collection->find_bson(convert_to_bson(filter_ejson), - to_mongodb_collection_find_options(options), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_find_one(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - const realm_mongodb_find_options_t* options, realm_userdata_t data, - realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - REALM_ASSERT(options); - return wrap_err([&] { - collection->find_one_bson( - convert_to_bson(filter_ejson), to_mongodb_collection_find_options(options), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_aggregate(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - realm_userdata_t data, realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - collection->aggregate_bson( - convert_to_bson(filter_ejson), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_count(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - int64_t limit, realm_userdata_t data, - realm_free_userdata_func_t delete_data, realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - collection->count_bson(convert_to_bson(filter_ejson), limit, - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_insert_one(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - realm_userdata_t data, realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - collection->insert_one_bson( - convert_to_bson(filter_ejson), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_insert_many(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - realm_userdata_t data, realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - collection->insert_many_bson( - convert_to_bson(filter_ejson), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_delete_one(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - realm_userdata_t data, realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - collection->delete_one_bson( - convert_to_bson(filter_ejson), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_delete_many(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - realm_userdata_t data, realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - collection->delete_many_bson( - convert_to_bson(filter_ejson), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_update_one(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - realm_string_t update_ejson, bool upsert, realm_userdata_t data, - realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - const auto& bson_filter = convert_to_bson(filter_ejson); - const auto& bson_update = convert_to_bson(update_ejson); - collection->update_one_bson( - bson_filter, bson_update, upsert, - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_update_many(realm_mongodb_collection_t* collection, realm_string_t filter_ejson, - realm_string_t update_ejson, bool upsert, realm_userdata_t data, - realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - const auto& bson_filter = convert_to_bson(filter_ejson); - const auto& bson_update = convert_to_bson(update_ejson); - collection->update_many_bson( - bson_filter, bson_update, upsert, - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_find_one_and_update(realm_mongodb_collection_t* collection, - realm_string_t filter_ejson, realm_string_t update_ejson, - const realm_mongodb_find_one_and_modify_options_t* options, - realm_userdata_t data, realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - const auto& bson_filter = convert_to_bson(filter_ejson); - const auto& bson_update = convert_to_bson(update_ejson); - collection->find_one_and_update_bson( - bson_filter, bson_update, to_mongodb_collection_find_one_and_modify_options(options), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_find_one_and_replace( - realm_mongodb_collection_t* collection, realm_string_t filter_ejson, realm_string_t replacement_ejson, - const realm_mongodb_find_one_and_modify_options_t* options, realm_userdata_t data, - realm_free_userdata_func_t delete_data, realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - const auto& filter_bson = convert_to_bson(filter_ejson); - const auto& replacement_bson = convert_to_bson(replacement_ejson); - collection->find_one_and_replace_bson( - filter_bson, replacement_bson, to_mongodb_collection_find_one_and_modify_options(options), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -RLM_API bool realm_mongo_collection_find_one_and_delete(realm_mongodb_collection_t* collection, - realm_string_t filter_ejson, - const realm_mongodb_find_one_and_modify_options_t* options, - realm_userdata_t data, realm_free_userdata_func_t delete_data, - realm_mongodb_callback_t callback) -{ - REALM_ASSERT(collection); - return wrap_err([&] { - const auto& bson_filter = convert_to_bson(filter_ejson); - collection->find_one_and_delete_bson( - bson_filter, to_mongodb_collection_find_one_and_modify_options(options), - [=](util::Optional bson, util::Optional app_error) { - handle_mongodb_collection_result(bson, app_error, {data, delete_data}, callback); - }); - return true; - }); -} - -#endif // REALM_APP_SERVICES - -} // namespace realm::c_api - -#if REALM_APP_SERVICES -// definitions outside the c_api namespace -realm_app_user_subscription_token::~realm_app_user_subscription_token() -{ - user->unsubscribe(token); -} -#endif diff --git a/src/realm/object-store/c_api/config.cpp b/src/realm/object-store/c_api/config.cpp index 7b1b00498c2..d0d577e5b67 100644 --- a/src/realm/object-store/c_api/config.cpp +++ b/src/realm/object-store/c_api/config.cpp @@ -173,16 +173,6 @@ RLM_API void realm_config_set_disable_format_upgrade(realm_config_t* config, boo config->disable_format_upgrade = b; } -RLM_API bool realm_config_get_force_sync_history(const realm_config_t* config) -{ - return config->force_sync_history; -} - -RLM_API void realm_config_set_force_sync_history(realm_config_t* config, bool b) -{ - config->force_sync_history = b; -} - RLM_API bool realm_config_get_automatic_change_notifications(const realm_config_t* config) { return config->automatic_change_notifications; diff --git a/src/realm/object-store/c_api/http.cpp b/src/realm/object-store/c_api/http.cpp deleted file mode 100644 index 56d7f2647b4..00000000000 --- a/src/realm/object-store/c_api/http.cpp +++ /dev/null @@ -1,94 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include - -namespace realm::c_api { -namespace { -using namespace realm::app; - -static_assert(realm_http_request_method_e(HttpMethod::get) == RLM_HTTP_REQUEST_METHOD_GET); -static_assert(realm_http_request_method_e(HttpMethod::post) == RLM_HTTP_REQUEST_METHOD_POST); -static_assert(realm_http_request_method_e(HttpMethod::patch) == RLM_HTTP_REQUEST_METHOD_PATCH); -static_assert(realm_http_request_method_e(HttpMethod::put) == RLM_HTTP_REQUEST_METHOD_PUT); -static_assert(realm_http_request_method_e(HttpMethod::del) == RLM_HTTP_REQUEST_METHOD_DELETE); - -class CNetworkTransport final : public GenericNetworkTransport { -public: - CNetworkTransport(UserdataPtr userdata, realm_http_request_func_t request_executor) - : m_userdata(std::move(userdata)) - , m_request_executor(request_executor) - { - } - - static void on_response_completed(void* request_context, const realm_http_response_t* response) noexcept - { - std::unique_ptr> completion( - static_cast*>(request_context)); - - HttpHeaders headers; - for (size_t i = 0; i < response->num_headers; i++) { - headers.emplace(response->headers[i].name, response->headers[i].value); - } - - (*completion)({response->status_code, response->custom_status_code, std::move(headers), - std::string(response->body, response->body_size)}); - } - -private: - void send_request_to_server(const Request& request, - util::UniqueFunction&& completion_block) final - { - auto completion_data = - std::make_unique>(std::move(completion_block)); - - std::vector c_headers; - c_headers.reserve(request.headers.size()); - for (auto&& header : request.headers) { - c_headers.push_back({header.first.c_str(), header.second.c_str()}); - } - - realm_http_request_t c_request{realm_http_request_method_e(request.method), - request.url.c_str(), - request.timeout_ms, - c_headers.data(), - c_headers.size(), - request.body.data(), - request.body.size()}; - m_request_executor(m_userdata.get(), c_request, completion_data.release()); - } - - UserdataPtr m_userdata; - realm_http_request_func_t m_request_executor; -}; -} // namespace -} // namespace realm::c_api - -RLM_API realm_http_transport_t* realm_http_transport_new(realm_http_request_func_t request_executor, - realm_userdata_t userdata, realm_free_userdata_func_t free) -{ - auto transport = std::make_shared(realm::c_api::UserdataPtr{userdata, free}, - request_executor); - return new realm_http_transport_t(std::move(transport)); -} - -RLM_API void realm_http_transport_complete_request(void* completion_data, const realm_http_response_t* response) -{ - realm::c_api::CNetworkTransport::on_response_completed(completion_data, response); -} diff --git a/src/realm/object-store/c_api/schema.cpp b/src/realm/object-store/c_api/schema.cpp index 4d04a6c31b0..d5c063330b0 100644 --- a/src/realm/object-store/c_api/schema.cpp +++ b/src/realm/object-store/c_api/schema.cpp @@ -62,11 +62,6 @@ RLM_API uint64_t realm_get_persisted_schema_version(const realm_config_t* config conf.path = config->path; conf.encryption_key = config->encryption_key; - if (config->sync_config) { - conf.sync_config = nullptr; - conf.force_sync_history = true; - } - auto realm = Realm::get_shared_realm(conf); uint64_t version = ObjectStore::get_schema_version(realm->read_group()); return version; diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp deleted file mode 100644 index 63e93732bf5..00000000000 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ /dev/null @@ -1,325 +0,0 @@ -#include -#include -#include -#include -#include - -namespace realm::c_api { -namespace { - -// THis class represents the timer resource that is returned to the sync client from the -// CAPI implementation details for canceling and deleting the timer resources. -struct CAPITimer : sync::SyncSocketProvider::Timer { -public: - CAPITimer(realm_userdata_t userdata, int64_t delay_ms, realm_sync_socket_timer_callback_t* handler, - realm_sync_socket_create_timer_func_t create_timer_func, - realm_sync_socket_timer_canceled_func_t cancel_timer_func, - realm_sync_socket_timer_free_func_t free_timer_func) - : m_userdata(userdata) - , m_timer_create(create_timer_func) - , m_timer_cancel(cancel_timer_func) - , m_timer_free(free_timer_func) - { - m_timer = m_timer_create(userdata, delay_ms, handler); - } - - /// Cancels the timer and destroys the timer instance. - ~CAPITimer() - { - // Make sure the timer is stopped, if not already - m_timer_cancel(m_userdata, m_timer); - m_timer_free(m_userdata, m_timer); - } - - // Cancel the timer immediately - the CAPI implementation will need to call the - // realm_sync_socket_timer_canceled function to notify the sync client that the - // timer has been canceled and must be called in the same execution thread as - // the timer complete. - void cancel() override - { - m_timer_cancel(m_userdata, m_timer); - } - -private: - // A pointer to the CAPI implementation's timer instance. This is provided by the - // CAPI implementation when the create_timer_func function is called. - realm_sync_socket_timer_t m_timer = nullptr; - - // These values were originally provided to the socket_provider instance by the CAPI - // implementation when it was created - realm_userdata_t m_userdata = nullptr; - realm_sync_socket_create_timer_func_t m_timer_create = nullptr; - realm_sync_socket_timer_canceled_func_t m_timer_cancel = nullptr; - realm_sync_socket_timer_free_func_t m_timer_free = nullptr; -}; - -static void realm_sync_socket_op_complete(realm_sync_socket_callback* realm_callback, - realm_sync_socket_callback_result_e result, const char* reason) -{ - if (!realm_callback) - return; - - (*realm_callback)(result, reason); - realm_release(realm_callback); -} - -RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_timer_callback_t* timer_handler, - realm_sync_socket_callback_result_e result, const char* reason) -{ - realm_sync_socket_op_complete(timer_handler, result, reason); -} - -RLM_API void realm_sync_socket_timer_canceled(realm_sync_socket_timer_callback_t* timer_handler) -{ - realm_sync_socket_op_complete(timer_handler, RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED, "Timer canceled"); -} - -// This class represents a websocket instance provided by the CAPI implememtation for sending -// and receiving data and connection state from the websocket. This class is used directly by -// the sync client. -struct CAPIWebSocket : sync::WebSocketInterface { -public: - CAPIWebSocket(realm_userdata_t userdata, realm_sync_socket_connect_func_t websocket_connect_func, - realm_sync_socket_websocket_async_write_func_t websocket_write_func, - realm_sync_socket_websocket_free_func_t websocket_free_func, realm_websocket_observer_t* observer, - sync::WebSocketEndpoint&& endpoint) - : m_observer(observer) - , m_userdata(userdata) - , m_websocket_connect(websocket_connect_func) - , m_websocket_async_write(websocket_write_func) - , m_websocket_free(websocket_free_func) - { - realm_websocket_endpoint_t capi_endpoint; - capi_endpoint.address = endpoint.address.c_str(); - capi_endpoint.port = endpoint.port; - capi_endpoint.path = endpoint.path.c_str(); - - std::vector protocols; - for (size_t i = 0; i < endpoint.protocols.size(); ++i) { - auto& protocol = endpoint.protocols[i]; - protocols.push_back(protocol.c_str()); - } - capi_endpoint.protocols = protocols.data(); - capi_endpoint.num_protocols = protocols.size(); - capi_endpoint.is_ssl = endpoint.is_ssl; - - m_socket = m_websocket_connect(m_userdata, capi_endpoint, observer); - } - - /// Destroys the web socket instance. - ~CAPIWebSocket() - { - m_websocket_free(m_userdata, m_socket); - realm_release(m_observer); - } - - void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) final - { - auto shared_handler = std::make_shared(std::move(handler)); - m_websocket_async_write(m_userdata, m_socket, data.data(), data.size(), - new realm_sync_socket_write_callback_t(std::move(shared_handler))); - } - -private: - // A pointer to the CAPI implementation's websocket instance. This is provided by - // the m_websocket_connect() function when this websocket instance is created. - realm_sync_socket_websocket_t m_socket = nullptr; - - // A wrapped reference to the websocket observer in the sync client that receives the - // websocket status callbacks. This is provided by the Sync Client. - realm_websocket_observer_t* m_observer = nullptr; - - // These values were originally provided to the socket_provider instance by the CAPI - // implementation when it was created. - realm_userdata_t m_userdata = nullptr; - realm_sync_socket_connect_func_t m_websocket_connect = nullptr; - realm_sync_socket_websocket_async_write_func_t m_websocket_async_write = nullptr; - realm_sync_socket_websocket_free_func_t m_websocket_free = nullptr; -}; - -// Represents the websocket observer in the sync client that receives websocket status -// callbacks and passes them along to the WebSocketObserver object. -struct CAPIWebSocketObserver : sync::WebSocketObserver { -public: - CAPIWebSocketObserver(std::unique_ptr observer) - : m_observer(std::move(observer)) - { - REALM_ASSERT_EX(m_observer, "WebSocketObserver cannot be null"); - } - - ~CAPIWebSocketObserver() = default; - - void websocket_connected_handler(const std::string& protocol) final - { - m_observer->websocket_connected_handler(protocol); - } - - void websocket_error_handler() final - { - m_observer->websocket_error_handler(); - } - - bool websocket_binary_message_received(util::Span data) final - { - return m_observer->websocket_binary_message_received(data); - } - - bool websocket_closed_handler(bool was_clean, sync::websocket::WebSocketError code, std::string_view msg) final - { - return m_observer->websocket_closed_handler(was_clean, code, msg); - } - -private: - std::unique_ptr m_observer; -}; - -// This is the primary resource for providing event loop, timer and websocket -// resources and synchronization for the Sync Client. The CAPI implementation -// needs to implement the "funct_t" functions provided to this class for connecting -// the implementation to the operations called by the Sync Client. -struct CAPISyncSocketProvider : sync::SyncSocketProvider { - realm_userdata_t m_userdata = nullptr; - realm_free_userdata_func_t m_userdata_free = nullptr; - realm_sync_socket_post_func_t m_post = nullptr; - realm_sync_socket_create_timer_func_t m_timer_create = nullptr; - realm_sync_socket_timer_canceled_func_t m_timer_cancel = nullptr; - realm_sync_socket_timer_free_func_t m_timer_free = nullptr; - realm_sync_socket_connect_func_t m_websocket_connect = nullptr; - realm_sync_socket_websocket_async_write_func_t m_websocket_async_write = nullptr; - realm_sync_socket_websocket_free_func_t m_websocket_free = nullptr; - - CAPISyncSocketProvider() = default; - CAPISyncSocketProvider(CAPISyncSocketProvider&& other) - : m_userdata(std::exchange(other.m_userdata, nullptr)) - , m_userdata_free(std::exchange(other.m_userdata_free, nullptr)) - , m_post(std::exchange(other.m_post, nullptr)) - , m_timer_create(std::exchange(other.m_timer_create, nullptr)) - , m_timer_cancel(std::exchange(other.m_timer_cancel, nullptr)) - , m_timer_free(std::exchange(other.m_timer_free, nullptr)) - , m_websocket_connect(std::exchange(other.m_websocket_connect, nullptr)) - , m_websocket_async_write(std::exchange(other.m_websocket_async_write, nullptr)) - , m_websocket_free(std::exchange(other.m_websocket_free, nullptr)) - { - // userdata_free can be null if userdata is not used - if (m_userdata != nullptr) { - REALM_ASSERT(m_userdata_free); - } - REALM_ASSERT(m_post); - REALM_ASSERT(m_timer_create); - REALM_ASSERT(m_timer_cancel); - REALM_ASSERT(m_timer_free); - REALM_ASSERT(m_websocket_connect); - REALM_ASSERT(m_websocket_async_write); - REALM_ASSERT(m_websocket_free); - } - - ~CAPISyncSocketProvider() - { - if (m_userdata_free) { - m_userdata_free(m_userdata); - } - } - - // Create a websocket object that will be returned to the Sync Client, which is expected to - // begin connecting to the endpoint as soon as the object is created. The state and any data - // received is passed to the socket observer via the helper functions defined below this class. - std::unique_ptr connect(std::unique_ptr observer, - sync::WebSocketEndpoint&& endpoint) final - { - auto capi_observer = std::make_shared(std::move(observer)); - return std::make_unique(m_userdata, m_websocket_connect, m_websocket_async_write, - m_websocket_free, new realm_websocket_observer_t(capi_observer), - std::move(endpoint)); - } - - void post(FunctionHandler&& handler) final - { - auto shared_handler = std::make_shared(std::move(handler)); - m_post(m_userdata, new realm_sync_socket_post_callback_t(std::move(shared_handler))); - } - - SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) final - { - auto shared_handler = std::make_shared(std::move(handler)); - return std::make_unique(m_userdata, delay.count(), - new realm_sync_socket_timer_callback_t(std::move(shared_handler)), - m_timer_create, m_timer_cancel, m_timer_free); - } -}; - -} // namespace - -RLM_API realm_sync_socket_t* realm_sync_socket_new( - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free, realm_sync_socket_post_func_t post_func, - realm_sync_socket_create_timer_func_t create_timer_func, - realm_sync_socket_timer_canceled_func_t cancel_timer_func, realm_sync_socket_timer_free_func_t free_timer_func, - realm_sync_socket_connect_func_t websocket_connect_func, - realm_sync_socket_websocket_async_write_func_t websocket_write_func, - realm_sync_socket_websocket_free_func_t websocket_free_func) -{ - return wrap_err([&]() { - auto capi_socket_provider = std::make_shared(); - capi_socket_provider->m_userdata = userdata; - capi_socket_provider->m_userdata_free = userdata_free; - capi_socket_provider->m_post = post_func; - capi_socket_provider->m_timer_create = create_timer_func; - capi_socket_provider->m_timer_cancel = cancel_timer_func; - capi_socket_provider->m_timer_free = free_timer_func; - capi_socket_provider->m_websocket_connect = websocket_connect_func; - capi_socket_provider->m_websocket_async_write = websocket_write_func; - capi_socket_provider->m_websocket_free = websocket_free_func; - return new realm_sync_socket_t(std::move(capi_socket_provider)); - }); -} - -RLM_API void realm_sync_socket_post_complete(realm_sync_socket_post_callback_t* post_handler, - realm_sync_socket_callback_result_e result, const char* reason) -{ - realm_sync_socket_op_complete(post_handler, result, reason); -} - -RLM_API void realm_sync_socket_write_complete(realm_sync_socket_write_callback_t* write_handler, - realm_sync_socket_callback_result_e result, const char* reason) -{ - realm_sync_socket_op_complete(write_handler, result, reason); -} - -RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer, - const char* protocol) -{ - if (realm_websocket_observer) - realm_websocket_observer->get()->websocket_connected_handler(protocol); -} - -RLM_API void realm_sync_socket_websocket_error(realm_websocket_observer_t* realm_websocket_observer) -{ - if (realm_websocket_observer) - realm_websocket_observer->get()->websocket_error_handler(); -} - -RLM_API bool realm_sync_socket_websocket_message(realm_websocket_observer_t* realm_websocket_observer, - const char* data, size_t data_size) -{ - if (!realm_websocket_observer) - return false; - - return realm_websocket_observer->get()->websocket_binary_message_received(util::Span{data, data_size}); -} - -RLM_API bool realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, - realm_web_socket_errno_e code, const char* reason) -{ - if (!realm_websocket_observer) - return false; - - return realm_websocket_observer->get()->websocket_closed_handler( - was_clean, static_cast(code), reason); -} - -RLM_API void realm_sync_client_config_set_sync_socket(realm_sync_client_config_t* config, - realm_sync_socket_t* sync_socket) RLM_API_NOEXCEPT -{ - config->socket_provider = *sync_socket; -} - -} // namespace realm::c_api diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp deleted file mode 100644 index 61276107460..00000000000 --- a/src/realm/object-store/c_api/sync.cpp +++ /dev/null @@ -1,844 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "types.hpp" -#include "util.hpp" - - -realm_async_open_task_progress_notification_token::~realm_async_open_task_progress_notification_token() -{ - task->unregister_download_progress_notifier(token); -} - -realm_sync_session_connection_state_notification_token::~realm_sync_session_connection_state_notification_token() -{ - session->unregister_connection_change_callback(token); -} - -namespace realm::c_api { - -static_assert(realm_sync_client_reconnect_mode_e(ReconnectMode::normal) == RLM_SYNC_CLIENT_RECONNECT_MODE_NORMAL); -static_assert(realm_sync_client_reconnect_mode_e(ReconnectMode::testing) == RLM_SYNC_CLIENT_RECONNECT_MODE_TESTING); - -static_assert(realm_sync_session_resync_mode_e(ClientResyncMode::Manual) == RLM_SYNC_SESSION_RESYNC_MODE_MANUAL); -static_assert(realm_sync_session_resync_mode_e(ClientResyncMode::DiscardLocal) == - RLM_SYNC_SESSION_RESYNC_MODE_DISCARD_LOCAL); -static_assert(realm_sync_session_resync_mode_e(ClientResyncMode::Recover) == RLM_SYNC_SESSION_RESYNC_MODE_RECOVER); -static_assert(realm_sync_session_resync_mode_e(ClientResyncMode::RecoverOrDiscard) == - RLM_SYNC_SESSION_RESYNC_MODE_RECOVER_OR_DISCARD); - -static_assert(realm_sync_session_stop_policy_e(SyncSessionStopPolicy::Immediately) == - RLM_SYNC_SESSION_STOP_POLICY_IMMEDIATELY); -static_assert(realm_sync_session_stop_policy_e(SyncSessionStopPolicy::LiveIndefinitely) == - RLM_SYNC_SESSION_STOP_POLICY_LIVE_INDEFINITELY); -static_assert(realm_sync_session_stop_policy_e(SyncSessionStopPolicy::AfterChangesUploaded) == - RLM_SYNC_SESSION_STOP_POLICY_AFTER_CHANGES_UPLOADED); - -static_assert(realm_sync_session_state_e(SyncSession::State::Active) == RLM_SYNC_SESSION_STATE_ACTIVE); -static_assert(realm_sync_session_state_e(SyncSession::State::Dying) == RLM_SYNC_SESSION_STATE_DYING); -static_assert(realm_sync_session_state_e(SyncSession::State::Inactive) == RLM_SYNC_SESSION_STATE_INACTIVE); -static_assert(realm_sync_session_state_e(SyncSession::State::WaitingForAccessToken) == - RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN); -static_assert(realm_sync_session_state_e(SyncSession::State::Paused) == RLM_SYNC_SESSION_STATE_PAUSED); - -static_assert(realm_sync_connection_state_e(SyncSession::ConnectionState::Disconnected) == - RLM_SYNC_CONNECTION_STATE_DISCONNECTED); -static_assert(realm_sync_connection_state_e(SyncSession::ConnectionState::Connecting) == - RLM_SYNC_CONNECTION_STATE_CONNECTING); -static_assert(realm_sync_connection_state_e(SyncSession::ConnectionState::Connected) == - RLM_SYNC_CONNECTION_STATE_CONNECTED); - -static_assert(realm_sync_progress_direction_e(SyncSession::ProgressDirection::upload) == - RLM_SYNC_PROGRESS_DIRECTION_UPLOAD); -static_assert(realm_sync_progress_direction_e(SyncSession::ProgressDirection::download) == - RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD); - - -namespace { -using namespace realm::sync; -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::NoAction) == RLM_SYNC_ERROR_ACTION_NO_ACTION); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::ProtocolViolation) == - RLM_SYNC_ERROR_ACTION_PROTOCOL_VIOLATION); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::ApplicationBug) == - RLM_SYNC_ERROR_ACTION_APPLICATION_BUG); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::Warning) == RLM_SYNC_ERROR_ACTION_WARNING); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::Transient) == RLM_SYNC_ERROR_ACTION_TRANSIENT); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::DeleteRealm) == - RLM_SYNC_ERROR_ACTION_DELETE_REALM); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::ClientReset) == - RLM_SYNC_ERROR_ACTION_CLIENT_RESET); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::ClientResetNoRecovery) == - RLM_SYNC_ERROR_ACTION_CLIENT_RESET_NO_RECOVERY); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::MigrateToFLX) == - RLM_SYNC_ERROR_ACTION_MIGRATE_TO_FLX); -static_assert(realm_sync_error_action_e(ProtocolErrorInfo::Action::RevertToPBS) == - RLM_SYNC_ERROR_ACTION_REVERT_TO_PBS); - -static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Pending) == - RLM_SYNC_SUBSCRIPTION_PENDING); -static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Bootstrapping) == - RLM_SYNC_SUBSCRIPTION_BOOTSTRAPPING); -static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::AwaitingMark) == - RLM_SYNC_SUBSCRIPTION_AWAITING_MARK); -static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Complete) == - RLM_SYNC_SUBSCRIPTION_COMPLETE); -static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Error) == RLM_SYNC_SUBSCRIPTION_ERROR); -static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Superseded) == - RLM_SYNC_SUBSCRIPTION_SUPERSEDED); -static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Uncommitted) == - RLM_SYNC_SUBSCRIPTION_UNCOMMITTED); - -static_assert(realm_sync_file_action(SyncFileAction::DeleteRealm) == RLM_SYNC_FILE_ACTION_DELETE_REALM); -static_assert(realm_sync_file_action(SyncFileAction::BackUpThenDeleteRealm) == - RLM_SYNC_FILE_ACTION_BACK_UP_THEN_DELETE_REALM); - -} // namespace - - -static Query add_ordering_to_realm_query(Query realm_query, const DescriptorOrdering& ordering) -{ - auto ordering_copy = util::make_bind(); - *ordering_copy = ordering; - realm_query.set_ordering(ordering_copy); - return realm_query; -} - -#if !REALM_APP_SERVICES -RLM_API realm_sync_client_config_t* realm_sync_client_config_new(void) noexcept -{ - return new realm_sync_client_config_t; -} -#endif // !REALM_APP_SERVICES - -RLM_API void realm_sync_client_config_set_reconnect_mode(realm_sync_client_config_t* config, - realm_sync_client_reconnect_mode_e mode) noexcept -{ - config->reconnect_mode = ReconnectMode(mode); -} -RLM_API void realm_sync_client_config_set_multiplex_sessions(realm_sync_client_config_t* config, - bool multiplex) noexcept -{ - config->multiplex_sessions = multiplex; -} - -RLM_API void realm_sync_client_config_set_user_agent_binding_info(realm_sync_client_config_t* config, - const char* info) noexcept -{ - config->user_agent_binding_info = info; -} - -RLM_API void realm_sync_client_config_set_user_agent_application_info(realm_sync_client_config_t* config, - const char* info) noexcept -{ - config->user_agent_application_info = info; -} - -RLM_API void realm_sync_client_config_set_connect_timeout(realm_sync_client_config_t* config, - uint64_t timeout) noexcept -{ - config->timeouts.connect_timeout = timeout; -} - -RLM_API void realm_sync_client_config_set_connection_linger_time(realm_sync_client_config_t* config, - uint64_t time) noexcept -{ - config->timeouts.connection_linger_time = time; -} - -RLM_API void realm_sync_client_config_set_ping_keepalive_period(realm_sync_client_config_t* config, - uint64_t period) noexcept -{ - config->timeouts.ping_keepalive_period = period; -} - -RLM_API void realm_sync_client_config_set_pong_keepalive_timeout(realm_sync_client_config_t* config, - uint64_t timeout) noexcept -{ - config->timeouts.pong_keepalive_timeout = timeout; -} - -RLM_API void realm_sync_client_config_set_fast_reconnect_limit(realm_sync_client_config_t* config, - uint64_t limit) noexcept -{ - config->timeouts.fast_reconnect_limit = limit; -} - -RLM_API void realm_sync_client_config_set_resumption_delay_interval(realm_sync_client_config_t* config, - uint64_t interval) noexcept -{ - config->timeouts.reconnect_backoff_info.resumption_delay_interval = std::chrono::milliseconds{interval}; -} - -RLM_API void realm_sync_client_config_set_max_resumption_delay_interval(realm_sync_client_config_t* config, - uint64_t interval) noexcept -{ - config->timeouts.reconnect_backoff_info.max_resumption_delay_interval = std::chrono::milliseconds{interval}; -} - -RLM_API void realm_sync_client_config_set_resumption_delay_backoff_multiplier(realm_sync_client_config_t* config, - int multiplier) noexcept -{ - config->timeouts.reconnect_backoff_info.resumption_delay_backoff_multiplier = multiplier; -} - -/// Register an app local callback handler for bindings interested in registering callbacks before/after -/// the ObjectStore thread runs for this app. This only works for the default socket provider implementation. -/// IMPORTANT: If a function is supplied that handles the exception, it must call abort() or cause the -/// application to crash since the SyncClient will be in a bad state if this occurs and will not be able to -/// shut down properly. -/// @param config pointer to sync client config created by realm_sync_client_config_new() -/// @param on_thread_create callback invoked when the object store thread is created -/// @param on_thread_destroy callback invoked when the object store thread is destroyed -/// @param on_error callback invoked to signal to the listener that some error has occurred. -/// @param user_data pointer to user defined data that is provided to each of the callback functions -/// @param free_userdata callback invoked when the user_data is to be freed -RLM_API void realm_sync_client_config_set_default_binding_thread_observer( - realm_sync_client_config_t* config, realm_on_object_store_thread_callback_t on_thread_create, - realm_on_object_store_thread_callback_t on_thread_destroy, realm_on_object_store_error_callback_t on_error, - realm_userdata_t user_data, realm_free_userdata_func_t free_userdata) -{ - config->default_socket_provider_thread_observer = std::make_shared( - on_thread_create, on_thread_destroy, on_error, user_data, free_userdata); -} - -RLM_API void realm_config_set_sync_config(realm_config_t* config, realm_sync_config_t* sync_config) -{ - config->sync_config = std::make_shared(*sync_config); -} - -RLM_API realm_sync_config_t* realm_sync_config_new(const realm_user_t* user, const char* partition_value) noexcept -{ - return new realm_sync_config_t(*user, partition_value); -} - -RLM_API realm_sync_config_t* realm_flx_sync_config_new(const realm_user_t* user) noexcept -{ - return new realm_sync_config(*user, realm::SyncConfig::FLXSyncEnabled{}); -} - -RLM_API void realm_sync_config_set_session_stop_policy(realm_sync_config_t* config, - realm_sync_session_stop_policy_e policy) noexcept -{ - config->stop_policy = SyncSessionStopPolicy(policy); -} - -RLM_API void realm_sync_config_set_error_handler(realm_sync_config_t* config, realm_sync_error_handler_func_t handler, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - auto cb = [handler, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - std::shared_ptr session, SyncError error) { - auto c_error = realm_sync_error_t(); - - std::string error_code_message; - c_error.status = to_capi(error.status); - c_error.is_fatal = error.is_fatal; - c_error.is_unrecognized_by_client = error.is_unrecognized_by_client; - c_error.is_client_reset_requested = error.is_client_reset_requested(); - c_error.server_requests_action = static_cast(error.server_requests_action); - c_error.c_original_file_path_key = error.c_original_file_path_key; - c_error.c_recovery_file_path_key = error.c_recovery_file_path_key; - c_error.user_code_error = ErrorStorage::get_thread_local()->get_and_clear_user_code_error(); - - std::vector c_user_info; - c_user_info.reserve(error.user_info.size()); - for (auto& info : error.user_info) { - c_user_info.push_back({info.first.c_str(), info.second.c_str()}); - } - - c_error.user_info_map = c_user_info.data(); - c_error.user_info_length = c_user_info.size(); - - std::vector c_compensating_writes; - for (const auto& compensating_write : error.compensating_writes_info) { - c_compensating_writes.push_back({compensating_write.reason.c_str(), - compensating_write.object_name.c_str(), - to_capi(compensating_write.primary_key)}); - } - c_error.compensating_writes = c_compensating_writes.data(); - c_error.compensating_writes_length = c_compensating_writes.size(); - - realm_sync_session_t c_session(session); - handler(userdata.get(), &c_session, std::move(c_error)); - }; - config->error_handler = std::move(cb); -} - -RLM_API void realm_sync_config_set_client_validate_ssl(realm_sync_config_t* config, bool validate) noexcept -{ - config->client_validate_ssl = validate; -} - -RLM_API void realm_sync_config_set_ssl_trust_certificate_path(realm_sync_config_t* config, const char* path) noexcept -{ - config->ssl_trust_certificate_path = std::string(path); -} - -RLM_API void realm_sync_config_set_ssl_verify_callback(realm_sync_config_t* config, - realm_sync_ssl_verify_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - auto cb = [callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - const std::string& server_address, SyncConfig::ProxyConfig::port_type server_port, - const char* pem_data, size_t pem_size, int preverify_ok, int depth) { - return callback(userdata.get(), server_address.c_str(), server_port, pem_data, pem_size, preverify_ok, depth); - }; - - config->ssl_verify_callback = std::move(cb); -} - -RLM_API void realm_sync_config_set_cancel_waits_on_nonfatal_error(realm_sync_config_t* config, bool cancel) noexcept -{ - config->cancel_waits_on_nonfatal_error = cancel; -} - -RLM_API void realm_sync_config_set_authorization_header_name(realm_sync_config_t* config, const char* name) noexcept -{ - config->authorization_header_name = std::string(name); -} - -RLM_API void realm_sync_config_set_custom_http_header(realm_sync_config_t* config, const char* name, - const char* value) noexcept -{ - config->custom_http_headers[name] = value; -} - -RLM_API void realm_sync_config_set_recovery_directory_path(realm_sync_config_t* config, const char* path) noexcept -{ - config->recovery_directory = std::string(path); -} - -RLM_API void realm_sync_config_set_resync_mode(realm_sync_config_t* config, - realm_sync_session_resync_mode_e mode) noexcept -{ - config->client_resync_mode = ClientResyncMode(mode); -} - -RLM_API realm_object_id_t realm_sync_subscription_id(const realm_flx_sync_subscription_t* subscription) noexcept -{ - REALM_ASSERT(subscription != nullptr); - return to_capi(subscription->id); -} - -RLM_API realm_string_t realm_sync_subscription_name(const realm_flx_sync_subscription_t* subscription) noexcept -{ - REALM_ASSERT(subscription != nullptr); - return to_capi(subscription->name); -} - -RLM_API realm_string_t -realm_sync_subscription_object_class_name(const realm_flx_sync_subscription_t* subscription) noexcept -{ - REALM_ASSERT(subscription != nullptr); - return to_capi(subscription->object_class_name); -} - -RLM_API realm_string_t -realm_sync_subscription_query_string(const realm_flx_sync_subscription_t* subscription) noexcept -{ - REALM_ASSERT(subscription != nullptr); - return to_capi(subscription->query_string); -} - -RLM_API realm_timestamp_t -realm_sync_subscription_created_at(const realm_flx_sync_subscription_t* subscription) noexcept -{ - REALM_ASSERT(subscription != nullptr); - return to_capi(subscription->created_at); -} - -RLM_API realm_timestamp_t -realm_sync_subscription_updated_at(const realm_flx_sync_subscription_t* subscription) noexcept -{ - REALM_ASSERT(subscription != nullptr); - return to_capi(subscription->updated_at); -} - -RLM_API void realm_sync_config_set_before_client_reset_handler(realm_sync_config_t* config, - realm_sync_before_client_reset_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - auto cb = [callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](SharedRealm before_realm) { - realm_t r1{before_realm}; - if (!callback(userdata.get(), &r1)) { - throw CallbackFailed{}; - } - }; - config->notify_before_client_reset = std::move(cb); -} - -RLM_API void realm_sync_config_set_after_client_reset_handler(realm_sync_config_t* config, - realm_sync_after_client_reset_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - auto cb = [callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - SharedRealm before_realm, ThreadSafeReference after_realm, bool did_recover) { - realm_t r1{before_realm}; - auto tsr = realm_t::thread_safe_reference(std::move(after_realm)); - if (!callback(userdata.get(), &r1, &tsr, did_recover)) { - throw CallbackFailed{}; - } - }; - config->notify_after_client_reset = std::move(cb); -} - -RLM_API void realm_sync_config_set_initial_subscription_handler( - realm_sync_config_t* config, realm_async_open_task_init_subscription_func_t callback, bool rerun_on_open, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - auto cb = [callback, - userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](ThreadSafeReference realm) { - auto tsr = new realm_t::thread_safe_reference(std::move(realm)); - callback(tsr, userdata.get()); - }; - config->subscription_initializer = std::move(cb); - config->rerun_init_subscription_on_open = rerun_on_open; -} - -RLM_API realm_flx_sync_subscription_set_t* realm_sync_get_latest_subscription_set(const realm_t* realm) -{ - REALM_ASSERT(realm != nullptr); - return wrap_err([&]() { - return new realm_flx_sync_subscription_set_t((*realm)->get_latest_subscription_set()); - }); -} - -RLM_API realm_flx_sync_subscription_set_t* realm_sync_get_active_subscription_set(const realm_t* realm) -{ - REALM_ASSERT(realm != nullptr); - return wrap_err([&]() { - return new realm_flx_sync_subscription_set_t((*realm)->get_active_subscription_set()); - }); -} - -RLM_API realm_flx_sync_subscription_set_state_e -realm_sync_on_subscription_set_state_change_wait(const realm_flx_sync_subscription_set_t* subscription_set, - realm_flx_sync_subscription_set_state_e notify_when) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - SubscriptionSet::State state = - subscription_set->get_state_change_notification(static_cast(notify_when)).get(); - return static_cast(state); -} - -RLM_API bool -realm_sync_on_subscription_set_state_change_async(const realm_flx_sync_subscription_set_t* subscription_set, - realm_flx_sync_subscription_set_state_e notify_when, - realm_sync_on_subscription_state_changed_t callback, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) -{ - REALM_ASSERT(subscription_set != nullptr && callback != nullptr); - return wrap_err([&]() { - auto future_state = - subscription_set->get_state_change_notification(static_cast(notify_when)); - std::move(future_state) - .get_async([callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - const StatusWith& state) -> void { - if (state.is_ok()) - callback(userdata.get(), static_cast(state.get_value())); - else - callback(userdata.get(), realm_flx_sync_subscription_set_state_e::RLM_SYNC_SUBSCRIPTION_ERROR); - }); - return true; - }); -} - -RLM_API int64_t -realm_sync_subscription_set_version(const realm_flx_sync_subscription_set_t* subscription_set) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - return subscription_set->version(); -} - -RLM_API realm_flx_sync_subscription_set_state_e -realm_sync_subscription_set_state(const realm_flx_sync_subscription_set_t* subscription_set) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - return static_cast(subscription_set->state()); -} - -RLM_API const char* -realm_sync_subscription_set_error_str(const realm_flx_sync_subscription_set_t* subscription_set) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - return subscription_set->error_str().data(); -} - -RLM_API size_t realm_sync_subscription_set_size(const realm_flx_sync_subscription_set_t* subscription_set) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - return subscription_set->size(); -} - -RLM_API realm_flx_sync_subscription_t* -realm_sync_find_subscription_by_name(const realm_flx_sync_subscription_set_t* subscription_set, - const char* name) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - auto ptr = subscription_set->find(name); - if (!ptr) - return nullptr; - return new realm_flx_sync_subscription_t(*ptr); -} - -RLM_API realm_flx_sync_subscription_t* -realm_sync_find_subscription_by_results(const realm_flx_sync_subscription_set_t* subscription_set, - realm_results_t* results) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - auto realm_query = add_ordering_to_realm_query(results->get_query(), results->get_ordering()); - auto ptr = subscription_set->find(realm_query); - if (!ptr) - return nullptr; - return new realm_flx_sync_subscription_t{*ptr}; -} - -RLM_API realm_flx_sync_subscription_t* -realm_sync_subscription_at(const realm_flx_sync_subscription_set_t* subscription_set, size_t index) -{ - REALM_ASSERT(subscription_set != nullptr && index < subscription_set->size()); - try { - return new realm_flx_sync_subscription_t{subscription_set->at(index)}; - } - catch (...) { - return nullptr; - } -} - -RLM_API realm_flx_sync_subscription_t* -realm_sync_find_subscription_by_query(const realm_flx_sync_subscription_set_t* subscription_set, - realm_query_t* query) noexcept -{ - REALM_ASSERT(subscription_set != nullptr); - auto realm_query = add_ordering_to_realm_query(query->get_query(), query->get_ordering()); - auto ptr = subscription_set->find(realm_query); - if (!ptr) - return nullptr; - return new realm_flx_sync_subscription_t(*ptr); -} - -RLM_API bool realm_sync_subscription_set_refresh(realm_flx_sync_subscription_set_t* subscription_set) -{ - REALM_ASSERT(subscription_set != nullptr); - return wrap_err([&]() { - subscription_set->refresh(); - return true; - }); -} - -RLM_API realm_flx_sync_mutable_subscription_set_t* -realm_sync_make_subscription_set_mutable(realm_flx_sync_subscription_set_t* subscription_set) -{ - REALM_ASSERT(subscription_set != nullptr); - return wrap_err([&]() { - return new realm_flx_sync_mutable_subscription_set_t{subscription_set->make_mutable_copy()}; - }); -} - -RLM_API bool realm_sync_subscription_set_clear(realm_flx_sync_mutable_subscription_set_t* subscription_set) -{ - REALM_ASSERT(subscription_set != nullptr); - return wrap_err([&]() { - subscription_set->clear(); - return true; - }); -} - -RLM_API bool -realm_sync_subscription_set_insert_or_assign_results(realm_flx_sync_mutable_subscription_set_t* subscription_set, - realm_results_t* results, const char* name, size_t* index, - bool* inserted) -{ - REALM_ASSERT(subscription_set != nullptr && results != nullptr); - return wrap_err([&]() { - auto realm_query = add_ordering_to_realm_query(results->get_query(), results->get_ordering()); - const auto [it, successful] = name ? subscription_set->insert_or_assign(name, realm_query) - : subscription_set->insert_or_assign(realm_query); - *index = std::distance(subscription_set->begin(), it); - *inserted = successful; - return true; - }); -} - -RLM_API bool -realm_sync_subscription_set_insert_or_assign_query(realm_flx_sync_mutable_subscription_set_t* subscription_set, - realm_query_t* query, const char* name, size_t* index, - bool* inserted) -{ - REALM_ASSERT(subscription_set != nullptr && query != nullptr); - return wrap_err([&]() { - auto realm_query = add_ordering_to_realm_query(query->get_query(), query->get_ordering()); - const auto [it, successful] = name ? subscription_set->insert_or_assign(name, realm_query) - : subscription_set->insert_or_assign(realm_query); - *index = std::distance(subscription_set->begin(), it); - *inserted = successful; - return true; - }); -} - -RLM_API bool realm_sync_subscription_set_erase_by_id(realm_flx_sync_mutable_subscription_set_t* subscription_set, - const realm_object_id_t* id, bool* erased) -{ - REALM_ASSERT(subscription_set != nullptr && id != nullptr); - *erased = false; - return wrap_err([&] { - *erased = subscription_set->erase_by_id(from_capi(*id)); - return true; - }); -} - -RLM_API bool realm_sync_subscription_set_erase_by_name(realm_flx_sync_mutable_subscription_set_t* subscription_set, - const char* name, bool* erased) -{ - REALM_ASSERT(subscription_set != nullptr && name != nullptr); - *erased = false; - return wrap_err([&]() { - *erased = subscription_set->erase(name); - return true; - }); -} - -RLM_API bool realm_sync_subscription_set_erase_by_query(realm_flx_sync_mutable_subscription_set_t* subscription_set, - realm_query_t* query, bool* erased) -{ - REALM_ASSERT(subscription_set != nullptr && query != nullptr); - *erased = false; - return wrap_err([&]() { - auto realm_query = add_ordering_to_realm_query(query->get_query(), query->get_ordering()); - *erased = subscription_set->erase(realm_query); - return true; - }); -} - -RLM_API bool realm_sync_subscription_set_erase_by_results(realm_flx_sync_mutable_subscription_set_t* subscription_set, - realm_results_t* results, bool* erased) -{ - REALM_ASSERT(subscription_set != nullptr && results != nullptr); - *erased = false; - return wrap_err([&]() { - auto realm_query = add_ordering_to_realm_query(results->get_query(), results->get_ordering()); - *erased = subscription_set->erase(realm_query); - return true; - }); -} - -RLM_API bool -realm_sync_subscription_set_erase_by_class_name(realm_flx_sync_mutable_subscription_set_t* subscription_set, - const char* object_class_name, bool* erased) -{ - REALM_ASSERT(subscription_set != nullptr && object_class_name != nullptr); - *erased = false; - return wrap_err([&]() { - *erased = subscription_set->erase_by_class_name(object_class_name); - return true; - }); -} - -RLM_API realm_flx_sync_subscription_set_t* -realm_sync_subscription_set_commit(realm_flx_sync_mutable_subscription_set_t* subscription_set) -{ - REALM_ASSERT(subscription_set != nullptr); - return wrap_err([&]() { - return new realm_flx_sync_subscription_set_t{std::move(*subscription_set).commit()}; - }); -} - -RLM_API realm_async_open_task_t* realm_open_synchronized(realm_config_t* config) noexcept -{ - return wrap_err([config] { - return new realm_async_open_task_t(Realm::get_synchronized_realm(*config)); - }); -} - -RLM_API void realm_async_open_task_start(realm_async_open_task_t* task, realm_async_open_task_completion_func_t done, - realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) noexcept -{ - auto cb = [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](ThreadSafeReference realm, - std::exception_ptr error) { - if (error) { - realm_async_error_t c_error(std::move(error)); - done(userdata.get(), nullptr, &c_error); - } - else { - auto tsr = new realm_t::thread_safe_reference(std::move(realm)); - done(userdata.get(), tsr, nullptr); - } - }; - (*task)->start(std::move(cb)); -} - -RLM_API void realm_async_open_task_cancel(realm_async_open_task_t* task) noexcept -{ - (*task)->cancel(); -} - -RLM_API realm_async_open_task_progress_notification_token_t* -realm_async_open_task_register_download_progress_notifier(realm_async_open_task_t* task, - realm_sync_progress_func_t notifier, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - auto cb = [notifier, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - uint64_t transferred, uint64_t transferrable, double progress_estimate) { - notifier(userdata.get(), transferred, transferrable, progress_estimate); - }; - auto token = (*task)->register_download_progress_notifier(std::move(cb)); - return new realm_async_open_task_progress_notification_token_t{(*task), token}; -} - -RLM_API realm_sync_session_t* realm_sync_session_get(const realm_t* realm) noexcept -{ - if (auto session = (*realm)->sync_session()) { - return new realm_sync_session_t(std::move(session)); - } - - return nullptr; -} - -RLM_API realm_sync_session_state_e realm_sync_session_get_state(const realm_sync_session_t* session) noexcept -{ - return realm_sync_session_state_e((*session)->state()); -} - -RLM_API realm_sync_connection_state_e -realm_sync_session_get_connection_state(const realm_sync_session_t* session) noexcept -{ - return realm_sync_connection_state_e((*session)->connection_state()); -} - -RLM_API realm_user_t* realm_sync_session_get_user(const realm_sync_session_t* session) noexcept -{ - return new realm_user_t((*session)->user()); -} - -RLM_API const char* realm_sync_session_get_partition_value(const realm_sync_session_t* session) noexcept -{ - return (*session)->config().partition_value.c_str(); -} - -RLM_API const char* realm_sync_session_get_file_path(const realm_sync_session_t* session) noexcept -{ - return (*session)->path().c_str(); -} - -RLM_API void realm_sync_session_pause(realm_sync_session_t* session) noexcept -{ - (*session)->pause(); -} - -RLM_API void realm_sync_session_resume(realm_sync_session_t* session) noexcept -{ - (*session)->resume(); -} - -RLM_API void realm_sync_session_get_file_ident(realm_sync_session_t* session, realm_salted_file_ident_t* out) noexcept -{ - auto file_ident = (*session)->get_file_ident(); - out->ident = file_ident.ident; - out->salt = file_ident.salt; -} - -RLM_API realm_sync_session_connection_state_notification_token_t* -realm_sync_session_register_connection_state_change_callback(realm_sync_session_t* session, - realm_sync_connection_state_changed_func_t callback, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - std::function cb = - [callback, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](auto old_state, auto new_state) { - callback(userdata.get(), realm_sync_connection_state_e(old_state), - realm_sync_connection_state_e(new_state)); - }; - auto token = (*session)->register_connection_change_callback(std::move(cb)); - return new realm_sync_session_connection_state_notification_token_t{(*session), token}; -} - -RLM_API realm_sync_session_connection_state_notification_token_t* realm_sync_session_register_progress_notifier( - realm_sync_session_t* session, realm_sync_progress_func_t notifier, realm_sync_progress_direction_e direction, - bool is_streaming, realm_userdata_t userdata, realm_free_userdata_func_t userdata_free) noexcept -{ - std::function cb = - [notifier, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))]( - uint64_t transferred, uint64_t transferrable, double progress_estimate) { - notifier(userdata.get(), transferred, transferrable, progress_estimate); - }; - auto token = (*session)->register_progress_notifier(std::move(cb), SyncSession::ProgressDirection(direction), - is_streaming); - return new realm_sync_session_connection_state_notification_token_t{(*session), token}; -} - -RLM_API void realm_sync_session_wait_for_download_completion(realm_sync_session_t* session, - realm_sync_wait_for_completion_func_t done, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - util::UniqueFunction cb = - [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { - if (!s.is_ok()) { - realm_error_t error = to_capi(s); - done(userdata.get(), &error); - } - else { - done(userdata.get(), nullptr); - } - }; - (*session)->wait_for_download_completion(std::move(cb)); -} - -RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* session, - realm_sync_wait_for_completion_func_t done, - realm_userdata_t userdata, - realm_free_userdata_func_t userdata_free) noexcept -{ - util::UniqueFunction cb = - [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { - if (!s.is_ok()) { - realm_error_t error = to_capi(s); - done(userdata.get(), &error); - } - else { - done(userdata.get(), nullptr); - } - }; - (*session)->wait_for_upload_completion(std::move(cb)); -} - -RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, - realm_errno_e error_code, const char* error_str, - bool is_fatal) -{ - REALM_ASSERT(session); - SyncSession::OnlyForTesting::handle_error( - *session->get(), - sync::SessionErrorInfo{Status{static_cast(error_code), error_str}, is_fatal}); -} - -} // namespace realm::c_api diff --git a/src/realm/object-store/c_api/types.hpp b/src/realm/object-store/c_api/types.hpp index bdd56073436..323095fd8f4 100644 --- a/src/realm/object-store/c_api/types.hpp +++ b/src/realm/object-store/c_api/types.hpp @@ -14,20 +14,6 @@ #include #include -#if REALM_ENABLE_SYNC - -#if REALM_APP_SERVICES -#include -#include -#include -#endif // REALM_APP_SERVICES - -#include -#include -#include -#include -#endif // REALM_ENABLE_SYNC - #include #include #include @@ -572,390 +558,4 @@ struct realm_results : realm::c_api::WrapC, realm::Results { } }; -#if REALM_ENABLE_SYNC - -struct realm_async_open_task_progress_notification_token : realm::c_api::WrapC { - realm_async_open_task_progress_notification_token(std::shared_ptr task, uint64_t token) - : task(task) - , token(token) - { - } - ~realm_async_open_task_progress_notification_token(); - std::shared_ptr task; - uint64_t token; -}; - -struct realm_sync_session_connection_state_notification_token : realm::c_api::WrapC { - realm_sync_session_connection_state_notification_token(std::shared_ptr session, - uint64_t token) - : session(session) - , token(token) - { - } - ~realm_sync_session_connection_state_notification_token(); - std::shared_ptr session; - uint64_t token; -}; - -struct realm_http_transport : realm::c_api::WrapC, std::shared_ptr { - realm_http_transport(std::shared_ptr transport) - : std::shared_ptr(std::move(transport)) - { - } - - realm_http_transport* clone() const override - { - return new realm_http_transport{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -#if REALM_APP_SERVICES -// This class doesn't support realm_release() since it is only meant to be used -// as a CAPI-compatible reference to the SyncClientConfig member variable that -// is part of AppConfig. -// To avoid data misalignment or other conflicts with the original SyncClientConfig, -// do not add any additional functions or member variables to this class. -struct realm_sync_client_config final : realm::SyncClientConfig { - using SyncClientConfig::SyncClientConfig; -}; -#else -// This class must be freed using realm_release() -struct realm_sync_client_config : realm::c_api::WrapC, realm::SyncClientConfig { - using SyncClientConfig::SyncClientConfig; -}; -#endif // REALM_APP_SERVICES - -struct realm_sync_config : realm::c_api::WrapC, realm::SyncConfig { - using SyncConfig::SyncConfig; - realm_sync_config(const SyncConfig& c) - : SyncConfig(c) - { - } -}; - -#if REALM_APP_SERVICES - -struct realm_app_config : realm::c_api::WrapC, realm::app::AppConfig { - using AppConfig::AppConfig; -}; - -struct realm_app : realm::c_api::WrapC, realm::app::SharedApp { - realm_app(realm::app::SharedApp app) - : realm::app::SharedApp{std::move(app)} - { - } - - realm_app* clone() const override - { - return new realm_app{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -struct realm_app_user_subscription_token : realm::c_api::WrapC { - using Token = realm::Subscribable::Token; - realm_app_user_subscription_token(std::shared_ptr user, Token&& token) - : user(user) - , token(std::move(token)) - { - } - ~realm_app_user_subscription_token(); - std::shared_ptr user; - Token token; -}; - -struct realm_app_credentials : realm::c_api::WrapC, realm::app::AppCredentials { - realm_app_credentials(realm::app::AppCredentials credentials) - : realm::app::AppCredentials{std::move(credentials)} - { - } -}; - -struct realm_mongodb_collection : realm::c_api::WrapC, realm::app::MongoCollection { - realm_mongodb_collection(realm::app::MongoCollection collection) - : realm::app::MongoCollection(std::move(collection)) - { - } -}; - -#endif // REALM_APP_SERVICES - -struct realm_user : realm::c_api::WrapC, std::shared_ptr { - realm_user(std::shared_ptr user) - : std::shared_ptr{std::move(user)} - { - } - - realm_user* clone() const override - { - return new realm_user{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -struct realm_sync_session : realm::c_api::WrapC, std::shared_ptr { - realm_sync_session(std::shared_ptr session) - : std::shared_ptr{std::move(session)} - { - } - - realm_sync_session* clone() const override - { - return new realm_sync_session{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -struct realm_sync_manager : realm::c_api::WrapC, std::shared_ptr { - realm_sync_manager(std::shared_ptr manager) - : std::shared_ptr{std::move(manager)} - { - } - - realm_sync_manager* clone() const override - { - return new realm_sync_manager{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -struct realm_flx_sync_subscription : realm::c_api::WrapC, realm::sync::Subscription { - realm_flx_sync_subscription(realm::sync::Subscription&& subscription) - : realm::sync::Subscription(std::move(subscription)) - { - } - - realm_flx_sync_subscription(const realm::sync::Subscription& subscription) - : realm::sync::Subscription(subscription) - { - } - - realm_flx_sync_subscription* clone() const override - { - return new realm_flx_sync_subscription{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return *ptr == *this; - } - return false; - } -}; - -struct realm_flx_sync_subscription_set : realm::c_api::WrapC, realm::sync::SubscriptionSet { - realm_flx_sync_subscription_set(realm::sync::SubscriptionSet&& subscription_set) - : realm::sync::SubscriptionSet(std::move(subscription_set)) - { - } -}; - -struct realm_flx_sync_mutable_subscription_set : realm::c_api::WrapC, realm::sync::MutableSubscriptionSet { - realm_flx_sync_mutable_subscription_set(realm::sync::MutableSubscriptionSet&& subscription_set) - : realm::sync::MutableSubscriptionSet(std::move(subscription_set)) - { - } -}; - -struct realm_async_open_task : realm::c_api::WrapC, std::shared_ptr { - realm_async_open_task(std::shared_ptr task) - : std::shared_ptr{std::move(task)} - { - } - - realm_async_open_task* clone() const override - { - return new realm_async_open_task{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -struct realm_sync_socket : realm::c_api::WrapC, std::shared_ptr { - explicit realm_sync_socket(std::shared_ptr ptr) - : std::shared_ptr(std::move(ptr)) - { - } - - realm_sync_socket* clone() const override - { - return new realm_sync_socket{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -struct realm_websocket_observer : realm::c_api::WrapC, std::shared_ptr { - explicit realm_websocket_observer(std::shared_ptr ptr) - : std::shared_ptr(std::move(ptr)) - { - } - - realm_websocket_observer* clone() const override - { - return new realm_websocket_observer{*this}; - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } -}; - -struct realm_sync_socket_callback : realm::c_api::WrapC, - std::shared_ptr { - explicit realm_sync_socket_callback(std::shared_ptr ptr) - : std::shared_ptr(std::move(ptr)) - { - } - - bool equals(const WrapC& other) const noexcept final - { - if (auto ptr = dynamic_cast(&other)) { - return get() == ptr->get(); - } - return false; - } - - void operator()(realm_sync_socket_callback_result_e result, const char* reason) - { - if (!get()) { - return; - } - - auto complete_status = result == RLM_ERR_SYNC_SOCKET_SUCCESS - ? realm::Status::OK() - : realm::Status{static_cast(result), reason}; - (*get())(complete_status); - } -}; - -struct CBindingThreadObserver final : public realm::BindingCallbackThreadObserver { -public: - CBindingThreadObserver(realm_on_object_store_thread_callback_t on_thread_create, - realm_on_object_store_thread_callback_t on_thread_destroy, - realm_on_object_store_error_callback_t on_error, realm_userdata_t userdata, - realm_free_userdata_func_t free_userdata) - : m_create_callback_func{on_thread_create} - , m_destroy_callback_func{on_thread_destroy} - , m_error_callback_func{on_error} - , m_user_data{userdata, [&free_userdata] { - if (free_userdata) - return free_userdata; - return CBindingThreadObserver::m_default_free_userdata; - }()} - { - } - - void did_create_thread() override - { - if (m_create_callback_func) - m_create_callback_func(m_user_data.get()); - } - - void will_destroy_thread() override - { - if (m_destroy_callback_func) - m_destroy_callback_func(m_user_data.get()); - } - - bool handle_error(std::exception const& e) override - { - if (!m_error_callback_func) - return false; - - return m_error_callback_func(m_user_data.get(), e.what()); - } - - bool has_handle_error() override - { - return bool(m_error_callback_func); - } - - /// {@ - /// For testing: Return the values in this CBindingThreadObserver for comparing if two objects - /// have the same callback functions and userdata ptr values. - realm_on_object_store_thread_callback_t test_get_create_callback_func() const noexcept - { - return m_create_callback_func; - } - realm_on_object_store_thread_callback_t test_get_destroy_callback_func() const noexcept - { - return m_destroy_callback_func; - } - realm_on_object_store_error_callback_t test_get_error_callback_func() const noexcept - { - return m_error_callback_func; - } - realm_userdata_t test_get_userdata_ptr() const noexcept - { - return m_user_data.get(); - } - /// @} - -private: - CBindingThreadObserver() = default; - - static constexpr realm_free_userdata_func_t m_default_free_userdata = [](realm_userdata_t) {}; - - realm_on_object_store_thread_callback_t m_create_callback_func = nullptr; - realm_on_object_store_thread_callback_t m_destroy_callback_func = nullptr; - realm_on_object_store_error_callback_t m_error_callback_func = nullptr; - realm::c_api::UserdataPtr m_user_data; -}; - -#endif // REALM_ENABLE_SYNC - #endif // REALM_OBJECT_STORE_C_API_TYPES_HPP diff --git a/src/realm/object-store/impl/realm_coordinator.cpp b/src/realm/object-store/impl/realm_coordinator.cpp index 3d9f2a95034..f2e39c66617 100644 --- a/src/realm/object-store/impl/realm_coordinator.cpp +++ b/src/realm/object-store/impl/realm_coordinator.cpp @@ -31,20 +31,10 @@ #include #include -#if REALM_ENABLE_SYNC -#include -#include -#include -#include -#include -#include -#endif - #include #include #include #include -#include #include #include @@ -89,9 +79,6 @@ void RealmCoordinator::set_config(const Realm::Config& config) { if (config.encryption_key.data() && config.encryption_key.size() != 64) throw InvalidEncryptionKey(); - if (config.schema_mode == SchemaMode::Immutable && config.sync_config) - throw InvalidArgument(ErrorCodes::IllegalCombination, - "Synchronized Realms cannot be opened in immutable mode"); if ((config.schema_mode == SchemaMode::AdditiveDiscovered || config.schema_mode == SchemaMode::AdditiveExplicit) && config.migration_function) @@ -126,19 +113,6 @@ void RealmCoordinator::set_config(const Realm::Config& config) // ResetFile also won't use the migration function, but specifying one is // allowed to simplify temporarily switching modes during development -#if REALM_ENABLE_SYNC - if (config.sync_config) { - if (config.sync_config->flx_sync_requested && !config.sync_config->partition_value.empty()) { - throw InvalidArgument(ErrorCodes::IllegalCombination, - "Cannot specify a partition value when flexible sync is enabled"); - } - if (!config.sync_config->user) { - throw InvalidArgument(ErrorCodes::IllegalCombination, - "A user must be provided to open a synchronized Realm."); - } - } -#endif - bool no_existing_realm = std::all_of(begin(m_weak_realm_notifiers), end(m_weak_realm_notifiers), [](auto& notifier) { return notifier.expired(); @@ -176,33 +150,6 @@ void RealmCoordinator::set_config(const Realm::Config& config) util::format("Realm at path '%1' already opened with different schema version.", config.path)); } -#if REALM_ENABLE_SYNC - if (bool(m_config.sync_config) != bool(config.sync_config)) { - throw LogicError( - ErrorCodes::MismatchedConfig, - util::format("Realm at path '%1' already opened with different sync configurations.", config.path)); - } - - if (config.sync_config) { - auto old_user = m_config.sync_config->user; - auto new_user = config.sync_config->user; - if (old_user != new_user) { - throw LogicError( - ErrorCodes::MismatchedConfig, - util::format("Realm at path '%1' already opened with different sync user.", config.path)); - } - if (m_config.sync_config->partition_value != config.sync_config->partition_value) { - throw LogicError( - ErrorCodes::MismatchedConfig, - util::format("Realm at path '%1' already opened with different partition value.", config.path)); - } - if (m_config.sync_config->flx_sync_requested != config.sync_config->flx_sync_requested) { - throw LogicError(ErrorCodes::MismatchedConfig, - util::format("Realm at path '%1' already opened in a different synchronization mode", - config.path)); - } - } -#endif // Mixing cached and uncached Realms is allowed m_config.cache = config.cache; @@ -321,22 +268,9 @@ ThreadSafeReference RealmCoordinator::get_unbound_realm() } void RealmCoordinator::do_get_realm(RealmConfig&& config, std::shared_ptr& realm, - util::Optional version, util::CheckedUniqueLock& realm_lock, - bool first_time_open) + util::Optional version, util::CheckedUniqueLock& realm_lock, bool) { - const auto db_created = open_db(); -#ifdef REALM_ENABLE_SYNC - SyncConfig::SubscriptionInitializerCallback subscription_function = nullptr; - bool rerun_on_open = false; - if (config.sync_config && config.sync_config->flx_sync_requested && - config.sync_config->subscription_initializer) { - subscription_function = config.sync_config->subscription_initializer; - rerun_on_open = config.sync_config->rerun_init_subscription_on_open; - } -#else - static_cast(first_time_open); - static_cast(db_created); -#endif + open_db(); auto schema = std::move(config.schema); auto migration_function = std::move(config.migration_function); @@ -346,20 +280,8 @@ void RealmCoordinator::do_get_realm(RealmConfig&& config, std::shared_ptr realm = Realm::make_shared_realm(std::move(config), version, shared_from_this()); m_weak_realm_notifiers.emplace_back(realm, config.cache); -#ifdef REALM_ENABLE_SYNC - if (m_sync_session && m_sync_session->user()->is_logged_in()) - m_sync_session->revive_if_needed(); - - if (realm->config().audit_config) { - if (m_audit_context) - m_audit_context->update_metadata(realm->config().audit_config->metadata); - else - m_audit_context = make_audit_context(m_db, realm->config()); - } -#else if (realm->config().audit_config) REALM_TERMINATE("Cannot use Audit interface if Realm Core is built without Sync"); -#endif // Cached frozen Realms need to initialize their schema before releasing // the lock as otherwise they could be read from the cache on another thread @@ -375,29 +297,6 @@ void RealmCoordinator::do_get_realm(RealmConfig&& config, std::shared_ptr realm->update_schema(std::move(*schema), config.schema_version, std::move(migration_function), std::move(initialization_function)); } - -#ifdef REALM_ENABLE_SYNC - // run subscription initializer if the SDK has instructed core to do so. The subscription callback will be run if: - // 1. this is the first time we are creating the realm file - // 2. the database was already created, but this is the first time we are opening the db and the flag - // rerun_on_open was set - if (subscription_function) { - const auto current_subscription = realm->get_latest_subscription_set(); - const auto subscription_version = current_subscription.version(); - // in case we are hitting this check while during a normal open, we need to take in - // consideration if the db was created during this call. Since this may be the first time - // we are actually creating a realm. For async open this does not apply, in fact db_created - // will always be false. - if (!first_time_open) - first_time_open = db_created; - if (subscription_version == 0 || (first_time_open && rerun_on_open)) { - bool was_in_read = realm->is_in_read_transaction(); - subscription_function(realm); - if (!was_in_read) - realm->invalidate(); - } - } -#endif } void RealmCoordinator::bind_to_context(Realm& realm) @@ -412,45 +311,11 @@ void RealmCoordinator::bind_to_context(Realm& realm) REALM_TERMINATE("Invalid Realm passed to bind_to_context()"); } -#if REALM_ENABLE_SYNC -std::shared_ptr RealmCoordinator::get_synchronized_realm(Realm::Config config) -{ - if (!config.sync_config) - throw LogicError(ErrorCodes::IllegalOperation, - "This method is only available for fully synchronized Realms."); - - util::CheckedLockGuard lock(m_realm_mutex); - set_config(config); - const auto db_open_first_time = open_db(); - return std::make_shared(AsyncOpenTask::Private(), shared_from_this(), m_sync_session, - db_open_first_time); -} - -#endif - bool RealmCoordinator::open_db() { if (m_db) return false; -#if REALM_ENABLE_SYNC - if (m_config.sync_config) { - REALM_ASSERT(m_config.sync_config->user); - // If we previously opened this Realm, we may have a lingering sync - // session which outlived its RealmCoordinator. If that happens we - // want to reuse it instead of creating a new DB. - if (auto sync_manager = m_config.sync_config->user->sync_manager()) { - m_sync_session = sync_manager->get_existing_session(m_config.path); - } - if (m_sync_session) { - m_db = SyncSession::Internal::get_db(*m_sync_session); - init_external_helpers(); - return false; - } - } -#endif - - bool server_synchronization_mode = m_config.sync_config || m_config.force_sync_history; bool schema_mode_reset_file = m_config.schema_mode == SchemaMode::SoftResetFile || m_config.schema_mode == SchemaMode::HardResetFile; try { @@ -459,15 +324,7 @@ bool RealmCoordinator::open_db() return true; } std::unique_ptr history; - if (server_synchronization_mode) { -#if REALM_ENABLE_SYNC - bool apply_server_changes = !m_config.sync_config || m_config.sync_config->apply_server_changes; - history = std::make_unique(apply_server_changes); -#else - REALM_TERMINATE("Realm was not built with sync enabled"); -#endif - } - else if (!m_config.immutable()) { + if (!m_config.immutable()) { history = make_in_realm_history(); } @@ -542,21 +399,6 @@ void RealmCoordinator::init_external_helpers() // where sync commits notify ECH and other commits notify sync via ECH. This // happens on background threads, so to avoid needing locking on every access // we have to wire things up in a specific order. -#if REALM_ENABLE_SYNC - // We may have reused an existing sync session that outlived its original - // RealmCoordinator. If not, we need to create a new one now. - if (m_config.sync_config && !m_sync_session) { - if (!m_config.sync_config->user || m_config.sync_config->user->state() == SyncUser::State::Removed) { - throw app::AppError( - ErrorCodes::ClientUserNotFound, - util::format("Cannot start a sync session for user '%1' because this user has been removed.", - m_config.sync_config->user->user_id())); - } - if (auto sync_manager = m_config.sync_config->user->sync_manager()) { - m_sync_session = sync_manager->get_session(m_db, m_config); - } - } -#endif if (!m_notifier && !m_config.immutable() && m_config.automatic_change_notifications) { try { @@ -881,14 +723,6 @@ void RealmCoordinator::on_commit(DB::version_type) void RealmCoordinator::on_change() { -#if REALM_ENABLE_SYNC - // Invoke realm sync if another process has notified for a change - if (m_sync_session) { - auto version = m_db->get_version_of_latest_snapshot(); - SyncSession::Internal::nonsync_transact_notify(*m_sync_session, version); - } -#endif - { util::CheckedUniqueLock lock(m_running_notifiers_mutex); run_async_notifiers(); diff --git a/src/realm/object-store/impl/realm_coordinator.hpp b/src/realm/object-store/impl/realm_coordinator.hpp index fdf2820dddc..058b03e32d5 100644 --- a/src/realm/object-store/impl/realm_coordinator.hpp +++ b/src/realm/object-store/impl/realm_coordinator.hpp @@ -67,21 +67,6 @@ class RealmCoordinator : public std::enable_shared_from_this, // if the source Realm has caching enabled. std::shared_ptr freeze_realm(const Realm& source_realm) REQUIRES(!m_realm_mutex); -#if REALM_ENABLE_SYNC - // Get a thread-local shared Realm with the given configuration - // If the Realm is not already present, it will be fully downloaded before being returned. - // If the Realm is already on disk, it will be fully synchronized before being returned. - // Timeouts and interruptions are not handled by this method and must be handled by upper layers. - std::shared_ptr get_synchronized_realm(Realm::Config config) - REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); - - std::shared_ptr sync_session() REQUIRES(!m_realm_mutex) - { - util::CheckedLockGuard lock(m_realm_mutex); - return m_sync_session; - } -#endif - // Get the existing cached Realm if it exists for the specified scheduler or config.scheduler std::shared_ptr get_cached_realm(Realm::Config const& config, std::shared_ptr scheduler = nullptr) @@ -262,10 +247,6 @@ class RealmCoordinator : public std::enable_shared_from_this, std::unique_ptr<_impl::ExternalCommitHelper> m_notifier; -#if REALM_ENABLE_SYNC - std::shared_ptr m_sync_session; -#endif - std::shared_ptr m_audit_context; // returns true the first time the database is opened, false otherwise. diff --git a/src/realm/object-store/object_store.cpp b/src/realm/object-store/object_store.cpp index 1190c5865ff..088c8e90d65 100644 --- a/src/realm/object-store/object_store.cpp +++ b/src/realm/object-store/object_store.cpp @@ -29,10 +29,6 @@ #include #include -#if REALM_ENABLE_SYNC -#include -#endif // REALM_ENABLE_SYNC - #include #include @@ -189,11 +185,6 @@ void add_initial_columns(Group& group, ObjectSchema const& object_schema) TableRef table = group.get_table(name); for (auto const& prop : object_schema.persisted_properties) { -#if REALM_ENABLE_SYNC - // The sync::create_table* functions create the PK column for us. - if (prop.is_primary) - continue; -#endif // REALM_ENABLE_SYNC add_column(group, *table, prop); } } diff --git a/src/realm/object-store/shared_realm.cpp b/src/realm/object-store/shared_realm.cpp index 6e4fcc47d97..db7048ecb97 100644 --- a/src/realm/object-store/shared_realm.cpp +++ b/src/realm/object-store/shared_realm.cpp @@ -39,17 +39,6 @@ #include #include -#if REALM_ENABLE_SYNC -#include -#include -#include -#include - -#include -#include -#include -#include -#endif #ifdef REALM_DEBUG #include #endif @@ -190,40 +179,6 @@ SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, std::shared_ptr Realm::get_synchronized_realm(Config config) -{ - auto coordinator = RealmCoordinator::get_coordinator(config.path); - return coordinator->get_synchronized_realm(std::move(config)); -} - -std::shared_ptr Realm::sync_session() const -{ - return m_coordinator ? m_coordinator->sync_session() : nullptr; -} - -sync::SubscriptionSet Realm::get_latest_subscription_set() -{ - if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) { - throw IllegalOperation("Flexible sync is not enabled"); - } - // If there is a subscription store, then return the active set - auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store(); - REALM_ASSERT(flx_sub_store); - return flx_sub_store->get_latest(); -} - -sync::SubscriptionSet Realm::get_active_subscription_set() -{ - if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) { - throw IllegalOperation("Flexible sync is not enabled"); - } - // If there is a subscription store, then return the active set - auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store(); - REALM_ASSERT(flx_sub_store); - return flx_sub_store->get_active(); -} -#endif void Realm::set_schema(Schema const& reference, Schema schema) { @@ -341,10 +296,6 @@ bool Realm::schema_change_needs_write_transaction(Schema& schema, std::vectorflx_sync_requested) - return; -#endif if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned) throw InvalidSchemaVersionException(m_schema_version, version, false); } @@ -432,12 +383,6 @@ void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction mig DataInitializationFunction initialization_function, bool in_transaction) { uint64_t validation_mode = SchemaValidationMode::Basic; -#if REALM_ENABLE_SYNC - if (auto sync_config = m_config.sync_config) { - validation_mode |= - sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS; - } -#endif if (m_config.schema_mode == SchemaMode::AdditiveExplicit) { validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans; } @@ -1161,22 +1106,6 @@ void Realm::convert(const Config& config, bool merge_into_existing) verify_thread(); verify_open(); -#if REALM_ENABLE_SYNC - auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested; - auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested; - auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested; - - if (dst_is_flx_sync && !src_is_flx_sync) { - throw IllegalOperation( - "Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled"); - } - if (dst_is_pbs_sync && src_is_flx_sync) { - throw IllegalOperation( - "Realm cannot be converted from a flexible sync realm to a partition based sync realm"); - } - -#endif - if (merge_into_existing && util::File::exists(config.path)) { auto destination_realm = Realm::get_shared_realm(config); destination_realm->begin_transaction(); @@ -1190,27 +1119,7 @@ void Realm::convert(const Config& config, bool merge_into_existing) throw InvalidEncryptionKey(); } - auto& tr = transaction(); - auto repl = tr.get_replication(); - bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient; - bool dst_is_sync = config.sync_config || config.force_sync_history; - - if (dst_is_sync) { - m_coordinator->write_copy(config.path, config.encryption_key.data()); - if (!src_is_sync) { -#if REALM_ENABLE_SYNC - DBOptions options; - if (config.encryption_key.size()) { - options.encryption_key = config.encryption_key.data(); - } - auto db = DB::create(make_in_realm_history(), config.path, options); - db->create_new_history(sync::make_client_replication()); -#endif - } - } - else { - tr.write(config.path, config.encryption_key.data()); - } + transaction().write(config.path, config.encryption_key.data()); } OwnedBinaryData Realm::write_copy() diff --git a/src/realm/object-store/shared_realm.hpp b/src/realm/object-store/shared_realm.hpp index 5e98c96149f..b1fe2e20b8b 100644 --- a/src/realm/object-store/shared_realm.hpp +++ b/src/realm/object-store/shared_realm.hpp @@ -44,9 +44,7 @@ class StringData; class Table; class ThreadSafeReference; class Transaction; -class SyncSession; struct AuditConfig; -struct SyncConfig; typedef std::shared_ptr SharedRealm; typedef std::weak_ptr WeakRealm; @@ -155,13 +153,6 @@ struct RealmConfig { // a default one for the current thread will be used. std::shared_ptr scheduler; - /// A data structure storing data used to configure the Realm for sync support. - std::shared_ptr sync_config; - - // Open the Realm using the sync history mode even if a sync - // configuration is not supplied. - bool force_sync_history = false; - // A factory function which produces an audit implementation. std::shared_ptr audit_config; @@ -206,23 +197,6 @@ class Realm : public std::enable_shared_from_this { // from the thread safe reference. static SharedRealm get_shared_realm(ThreadSafeReference, std::shared_ptr = nullptr); -#if REALM_ENABLE_SYNC - // Open a synchronized Realm and make sure it is fully up to date before - // returning it. - // - // It is possible to both cancel the download and listen to download progress - // using the `AsyncOpenTask` returned. Note that the download doesn't actually - // start until you call `AsyncOpenTask::start(callback)` - static std::shared_ptr get_synchronized_realm(Config config); - - std::shared_ptr sync_session() const; - - // Returns the latest/active subscription set for a FLX-sync enabled realm. - // Throws an exception for a non-FLX realm - sync::SubscriptionSet get_latest_subscription_set(); - sync::SubscriptionSet get_active_subscription_set(); -#endif - // Returns a frozen Realm for the given Realm. This Realm can be accessed from any thread. static SharedRealm get_frozen_realm(Config config, VersionID version); diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp deleted file mode 100644 index 9306807912a..00000000000 --- a/src/realm/object-store/sync/app.cpp +++ /dev/null @@ -1,1550 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __EMSCRIPTEN__ -#include -#endif - -#include -#include -#include - -using namespace realm; -using namespace realm::app; -using namespace bson; -using util::Optional; -using util::UniqueFunction; - -namespace { -// MARK: - Helpers - -REALM_COLD -REALM_NOINLINE -REALM_NORETURN -void throw_json_error(ErrorCodes::Error ec, std::string_view message) -{ - throw AppError(ec, std::string(message)); -} - -template -T as(const Bson& bson) -{ - if (holds_alternative(bson)) { - return static_cast(bson); - } - throw_json_error(ErrorCodes::MalformedJson, "?"); -} - -template -T get(const BsonDocument& doc, const std::string& key) -{ - if (auto val = doc.find(key)) { - return as(*val); - } - throw_json_error(ErrorCodes::MissingJsonKey, key); - return {}; -} - -template -void read_field(const BsonDocument& data, const std::string& key, T& value) -{ - if (auto val = data.find(key)) { - value = as(*val); - } - else { - throw_json_error(ErrorCodes::MissingJsonKey, key); - } -} - -template <> -void read_field(const BsonDocument& data, const std::string& key, ObjectId& value) -{ - value = ObjectId(get(data, key).c_str()); -} - -template -void read_field(const BsonDocument& data, const std::string& key, Optional& value) -{ - if (auto val = data.find(key)) { - value = as(*val); - } -} - -template -T parse(std::string_view str) -{ - try { - return as(bson::parse(str)); - } - catch (const std::exception& e) { - throw_json_error(ErrorCodes::MalformedJson, e.what()); - } -} - -struct UserAPIKeyResponseHandler { - UniqueFunction)> completion; - void operator()(const Response& response) - { - if (auto error = AppUtils::check_for_errors(response)) { - return completion({}, std::move(error)); - } - - try { - auto json = parse(response.body); - completion(read_user_api_key(json), {}); - } - catch (AppError& e) { - completion({}, std::move(e)); - } - } - - static App::UserAPIKey read_user_api_key(const BsonDocument& doc) - { - App::UserAPIKey user_api_key; - read_field(doc, "_id", user_api_key.id); - read_field(doc, "key", user_api_key.key); - read_field(doc, "name", user_api_key.name); - read_field(doc, "disabled", user_api_key.disabled); - return user_api_key; - } -}; - -// generate the request headers for a HTTP call, by default it will generate -// headers with a refresh token if a user is passed -HttpHeaders get_request_headers(const std::shared_ptr& user, RequestTokenType token_type) -{ - HttpHeaders headers{{"Content-Type", "application/json;charset=utf-8"}, {"Accept", "application/json"}}; - if (user) { - switch (token_type) { - case RequestTokenType::NoAuth: - break; - case RequestTokenType::AccessToken: - headers.insert({"Authorization", util::format("Bearer %1", user->access_token())}); - break; - case RequestTokenType::RefreshToken: - headers.insert({"Authorization", util::format("Bearer %1", user->refresh_token())}); - break; - } - } - return headers; -} - -std::string trim_base_url(std::string base_url) -{ - while (!base_url.empty() && base_url.back() == '/') { - base_url.pop_back(); - } - - return base_url; -} - -std::string base_url_from_app_config(const AppConfig& app_config) -{ - if (!app_config.base_url) { - return std::string{App::default_base_url()}; - } - - return trim_base_url(*app_config.base_url); -} - -UniqueFunction handle_default_response(UniqueFunction)>&& completion) -{ - return [completion = std::move(completion)](const Response& response) { - completion(AppUtils::check_for_errors(response)); - }; -} - -constexpr static std::string_view s_base_path = "/api/client/v2.0"; -constexpr static std::string_view s_app_path = "/app"; -constexpr static std::string_view s_auth_path = "/auth"; -constexpr static std::string_view s_sync_path = "/realm-sync"; -constexpr static uint64_t s_default_timeout_ms = 60000; -constexpr static std::string_view s_username_password_provider_key = "local-userpass"; -constexpr static std::string_view s_user_api_key_provider_key_path = "api_keys"; -constexpr static int s_max_http_redirects = 20; -static util::FlatMap> s_apps_cache; // app_id -> base_url -> app -std::mutex s_apps_mutex; -} // anonymous namespace - -namespace realm::app { - -std::string_view App::default_base_url() -{ - return "https://services.cloud.mongodb.com"; -} - -// NO_THREAD_SAFETY_ANALYSIS because clang generates a false positive. -// "Calling function configure requires negative capability '!app->m_route_mutex'" -// But 'app' is an object just created in this static method so it is not possible to annotate this in the header. -SharedApp App::get_app(CacheMode mode, const AppConfig& config) NO_THREAD_SAFETY_ANALYSIS -{ - if (mode == CacheMode::Enabled) { - std::lock_guard lock(s_apps_mutex); - auto& app = s_apps_cache[config.app_id][base_url_from_app_config(config)]; - if (!app) { - app = App::make_app(config); - } - return app; - } - REALM_ASSERT(mode == CacheMode::Disabled); - return App::make_app(config); -} - -SharedApp App::make_app(const AppConfig& config) -{ -#ifdef __EMSCRIPTEN__ - if (!config.transport) { - // Make a copy and provide a default transport if not provided - AppConfig config_copy = config; - config_copy.transport = std::make_shared<_impl::EmscriptenNetworkTransport>(); - return std::make_shared(Private(), config_copy); - } - return std::make_shared(Private(), config); -#else - return std::make_shared(Private(), config); -#endif -} - -SharedApp App::get_cached_app(const std::string& app_id, const std::optional& base_url) -{ - std::lock_guard lock(s_apps_mutex); - if (auto it = s_apps_cache.find(app_id); it != s_apps_cache.end()) { - const auto& apps_by_url = it->second; - - auto app_it = base_url ? apps_by_url.find(trim_base_url(*base_url)) : apps_by_url.begin(); - if (app_it != apps_by_url.end()) { - return app_it->second; - } - } - - return nullptr; -} - -void App::clear_cached_apps() -{ - std::lock_guard lock(s_apps_mutex); - s_apps_cache.clear(); -} - -void App::close_all_sync_sessions() -{ - std::lock_guard lock(s_apps_mutex); - for (auto& apps_by_url : s_apps_cache) { - for (auto& app : apps_by_url.second) { - app.second->sync_manager()->close_all_sessions(); - } - } -} - -App::App(Private, const AppConfig& config) - : m_config(config) - , m_base_url(base_url_from_app_config(m_config)) - , m_request_timeout_ms(m_config.default_request_timeout_ms.value_or(s_default_timeout_ms)) - , m_file_manager(std::make_unique(config)) - , m_metadata_store(create_metadata_store(config, *m_file_manager)) - , m_sync_manager(SyncManager::create(config.sync_client_config)) -{ - REALM_ASSERT(m_config.transport); - - // if a base url is provided, then verify the value - if (m_config.base_url) { - util::Uri::parse(*m_config.base_url); - } - // Setup a baseline set of routes using the provided or default base url - // These will be updated when the location info is refreshed prior to sending the - // first AppServices HTTP request. - configure_route(m_base_url, ""); - m_sync_manager->set_sync_route(make_sync_route(), false); - - if (m_config.device_info.platform_version.empty()) { - throw InvalidArgument("You must specify the Platform Version in App::Config::device_info"); - } - - if (m_config.device_info.sdk.empty()) { - throw InvalidArgument("You must specify the SDK Name in App::Config::device_info"); - } - - if (m_config.device_info.sdk_version.empty()) { - throw InvalidArgument("You must specify the SDK Version in App::Config::device_info"); - } -} - -App::~App() {} - -bool App::init_logger() -{ - if (!m_logger_ptr) { - m_logger_ptr = m_sync_manager->get_logger(); - if (!m_logger_ptr) { - m_logger_ptr = util::Logger::get_default_logger(); - } - } - return bool(m_logger_ptr); -} - -bool App::would_log(util::Logger::Level level) -{ - return init_logger() && m_logger_ptr->would_log(util::LogCategory::app, level); -} - -template -void App::log_debug(const char* message, Params&&... params) -{ - if (init_logger()) { - m_logger_ptr->log(util::LogCategory::app, util::Logger::Level::debug, message, - std::forward(params)...); - } -} - -template -void App::log_error(const char* message, Params&&... params) -{ - if (init_logger()) { - m_logger_ptr->log(util::LogCategory::app, util::Logger::Level::error, message, - std::forward(params)...); - } -} - -std::string App::auth_route() -{ - util::CheckedLockGuard guard(m_route_mutex); - return m_auth_route; -} - -std::string App::base_url() -{ - util::CheckedLockGuard guard(m_route_mutex); - return m_base_url; -} - -std::string App::get_host_url() -{ - util::CheckedLockGuard guard(m_route_mutex); - return m_host_url; -} - -std::string App::get_ws_host_url() -{ - util::CheckedLockGuard guard(m_route_mutex); - return m_ws_host_url; -} - -std::string App::make_sync_route(Optional ws_host_url) -{ - return util::format("%1%2%3/%4%5", ws_host_url.value_or(m_ws_host_url), s_base_path, s_app_path, m_config.app_id, - s_sync_path); -} - -void App::configure_route(const std::string& host_url, const std::string& ws_host_url) -{ - m_host_url = host_url; - m_ws_host_url = ws_host_url; - if (m_ws_host_url.empty()) - m_ws_host_url = App::create_ws_host_url(m_host_url); - - // host_url is the url to the server: e.g., https://services.cloud.mongodb.com or https://localhost:9090 - // base_route is the baseline client api path: e.g. /api/client/v2.0 - m_base_route = util::format("%1%2", m_host_url, s_base_path); - // app_route is the cloud app URL: /api/client/v2.0/app/ - m_app_route = util::format("%1%2/%3", m_base_route, s_app_path, m_config.app_id); - // auth_route is cloud app auth URL: /api/client/v2.0/app//auth - m_auth_route = util::format("%1%2", m_app_route, s_auth_path); -} - -// Create a temporary websocket URL domain using the given host URL -// This updates the URL based on the following assumptions: -// If the URL doesn't start with 'http' => -// http[s]://[region-prefix]realm.mongodb.com => ws[s]://ws.[region-prefix]realm.mongodb.com -// http[s]://[region-prefix]services.cloud.mongodb.com => ws[s]://[region-prefix].ws.services.cloud.mongodb.com -// All others => http[s]:// => ws[s]:// -std::string App::create_ws_host_url(std::string_view host_url) -{ - constexpr static std::string_view old_base_domain = "realm.mongodb.com"; - constexpr static std::string_view new_base_domain = "services.cloud.mongodb.com"; - const size_t base_len = std::char_traits::length("http://"); - - // Doesn't contain 7 or more characters (length of 'http://') or start with http, - // just return provided string - if (host_url.length() < base_len || host_url.substr(0, 4) != "http") { - return std::string(host_url); - } - // If it starts with 'https' then ws url will start with 'wss' - bool https = host_url[4] == 's'; - size_t prefix_len = base_len + (https ? 1 : 0); - std::string_view prefix = https ? "wss://" : "ws://"; - - // http[s]://[]realm.mongodb.com[/] => - // ws[s]://ws.[]realm.mongodb.com[/] - if (host_url.find(old_base_domain) != std::string_view::npos) { - return util::format("%1ws.%2", prefix, host_url.substr(prefix_len)); - } - // http[s]://[]services.cloud.mongodb.com[/] => - // ws[s]://[].ws.services.cloud.mongodb.com[/] - if (auto start = host_url.find(new_base_domain); start != std::string_view::npos) { - return util::format("%1%2ws.%3", prefix, host_url.substr(prefix_len, start - prefix_len), - host_url.substr(start)); - } - - // All others => http[s]://[/] => ws[s]://[/] - return util::format("ws%1", host_url.substr(4)); -} - -void App::update_hostname(const std::string& host_url, const std::string& ws_host_url, - const std::string& new_base_url) -{ - log_debug("App: update_hostname: %1 | %2 | %3", host_url, ws_host_url, new_base_url); - m_base_url = trim_base_url(new_base_url); - // If a new host url was returned from the server, use it to configure the routes - // Otherwise, use the m_base_url value - std::string base_url = host_url.length() > 0 ? host_url : m_base_url; - configure_route(base_url, ws_host_url); -} - -// MARK: - Template specializations - -template <> -App::UsernamePasswordProviderClient App::provider_client() -{ - return App::UsernamePasswordProviderClient(shared_from_this()); -} - -template <> -App::UserAPIKeyProviderClient App::provider_client() -{ - return App::UserAPIKeyProviderClient(*this); -} - -// MARK: - UsernamePasswordProviderClient - -void App::UsernamePasswordProviderClient::register_email(const std::string& email, const std::string& password, - UniqueFunction)>&& completion) -{ - m_parent->log_debug("App: register_email: %1", email); - m_parent->post(util::format("%1/providers/%2/register", m_parent->auth_route(), s_username_password_provider_key), - std::move(completion), {{"email", email}, {"password", password}}); -} - -void App::UsernamePasswordProviderClient::confirm_user(const std::string& token, const std::string& token_id, - UniqueFunction)>&& completion) -{ - m_parent->log_debug("App: confirm_user"); - m_parent->post(util::format("%1/providers/%2/confirm", m_parent->auth_route(), s_username_password_provider_key), - std::move(completion), {{"token", token}, {"tokenId", token_id}}); -} - -void App::UsernamePasswordProviderClient::resend_confirmation_email( - const std::string& email, UniqueFunction)>&& completion) -{ - m_parent->log_debug("App: resend_confirmation_email: %1", email); - m_parent->post( - util::format("%1/providers/%2/confirm/send", m_parent->auth_route(), s_username_password_provider_key), - std::move(completion), {{"email", email}}); -} - -void App::UsernamePasswordProviderClient::retry_custom_confirmation( - const std::string& email, UniqueFunction)>&& completion) -{ - m_parent->log_debug("App: retry_custom_confirmation: %1", email); - m_parent->post( - util::format("%1/providers/%2/confirm/call", m_parent->auth_route(), s_username_password_provider_key), - std::move(completion), {{"email", email}}); -} - -void App::UsernamePasswordProviderClient::send_reset_password_email( - const std::string& email, UniqueFunction)>&& completion) -{ - m_parent->log_debug("App: send_reset_password_email: %1", email); - m_parent->post( - util::format("%1/providers/%2/reset/send", m_parent->auth_route(), s_username_password_provider_key), - std::move(completion), {{"email", email}}); -} - -void App::UsernamePasswordProviderClient::reset_password(const std::string& password, const std::string& token, - const std::string& token_id, - UniqueFunction)>&& completion) -{ - m_parent->log_debug("App: reset_password"); - m_parent->post(util::format("%1/providers/%2/reset", m_parent->auth_route(), s_username_password_provider_key), - std::move(completion), {{"password", password}, {"token", token}, {"tokenId", token_id}}); -} - -void App::UsernamePasswordProviderClient::call_reset_password_function( - const std::string& email, const std::string& password, const BsonArray& args, - UniqueFunction)>&& completion) -{ - m_parent->log_debug("App: call_reset_password_function: %1", email); - m_parent->post( - util::format("%1/providers/%2/reset/call", m_parent->auth_route(), s_username_password_provider_key), - std::move(completion), {{"email", email}, {"password", password}, {"arguments", args}}); -} - -// MARK: - UserAPIKeyProviderClient - -std::string App::UserAPIKeyProviderClient::url_for_path(const std::string& path = "") const -{ - if (!path.empty()) { - return m_auth_request_client.url_for_path( - util::format("%1/%2/%3", s_auth_path, s_user_api_key_provider_key_path, path)); - } - - return m_auth_request_client.url_for_path(util::format("%1/%2", s_auth_path, s_user_api_key_provider_key_path)); -} - -void App::UserAPIKeyProviderClient::create_api_key( - const std::string& name, const std::shared_ptr& user, - UniqueFunction)>&& completion) -{ - m_auth_request_client.do_authenticated_request( - HttpMethod::post, url_for_path(), Bson(BsonDocument{{"name", name}}).to_string(), user, - RequestTokenType::RefreshToken, UserAPIKeyResponseHandler{std::move(completion)}); -} - -void App::UserAPIKeyProviderClient::fetch_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - UniqueFunction)>&& completion) -{ - m_auth_request_client.do_authenticated_request(HttpMethod::get, url_for_path(id.to_string()), "", user, - RequestTokenType::RefreshToken, - UserAPIKeyResponseHandler{std::move(completion)}); -} - -void App::UserAPIKeyProviderClient::fetch_api_keys( - const std::shared_ptr& user, - UniqueFunction&&, Optional)>&& completion) -{ - m_auth_request_client.do_authenticated_request( - HttpMethod::get, url_for_path(), "", user, RequestTokenType::RefreshToken, - [completion = std::move(completion)](const Response& response) { - if (auto error = AppUtils::check_for_errors(response)) { - return completion({}, std::move(error)); - } - - try { - auto json = parse(response.body); - std::vector keys; - keys.reserve(json.size()); - for (auto&& api_key_json : json) { - keys.push_back(UserAPIKeyResponseHandler::read_user_api_key(as(api_key_json))); - } - return completion(std::move(keys), {}); - } - catch (AppError& e) { - completion({}, std::move(e)); - } - }); -} - -void App::UserAPIKeyProviderClient::delete_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - UniqueFunction)>&& completion) -{ - m_auth_request_client.do_authenticated_request(HttpMethod::del, url_for_path(id.to_string()), "", user, - RequestTokenType::RefreshToken, - handle_default_response(std::move(completion))); -} - -void App::UserAPIKeyProviderClient::enable_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - UniqueFunction)>&& completion) -{ - m_auth_request_client.do_authenticated_request( - HttpMethod::put, url_for_path(util::format("%1/enable", id.to_string())), "", user, - RequestTokenType::RefreshToken, handle_default_response(std::move(completion))); -} - -void App::UserAPIKeyProviderClient::disable_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - UniqueFunction)>&& completion) -{ - m_auth_request_client.do_authenticated_request( - HttpMethod::put, url_for_path(util::format("%1/disable", id.to_string())), "", user, - RequestTokenType::RefreshToken, handle_default_response(std::move(completion))); -} -// MARK: - App - -// The user cache can have an expired pointer to an object if another thread is -// currently waiting for the mutex so that it can unregister the object, which -// will result in shared_from_this() throwing. We could instead do -// `weak_from_this().lock()`, but that is more expensive in the much more common -// case where the pointer is valid. -// -// Storing weak_ptrs in m_user would also avoid this problem, but would introduce -// a different one where the natural way to use the users could result in us -// trying to release the final strong reference while holding the lock, which -// would lead to a deadlock -static std::shared_ptr try_lock(User& user) -{ - try { - return user.shared_from_this(); - } - catch (const std::bad_weak_ptr&) { - return nullptr; - } -} - -std::shared_ptr App::get_user_for_id(const std::string& user_id) -{ - if (auto& user = m_users[user_id]) { - if (auto locked = try_lock(*user)) { - return locked; - } - } - return User::make(shared_from_this(), user_id); -} - -void App::user_data_updated(const std::string& user_id) -{ - if (auto it = m_users.find(user_id); it != m_users.end()) { - it->second->update_backing_data(m_metadata_store->get_user(user_id)); - } -} - -std::shared_ptr App::current_user() -{ - util::CheckedLockGuard lock(m_user_mutex); - if (m_current_user && m_current_user->is_logged_in()) { - if (auto user = try_lock(*m_current_user)) { - return user; - } - } - if (auto user_id = m_metadata_store->get_current_user(); !user_id.empty()) { - auto user = get_user_for_id(user_id); - m_current_user = user.get(); - return user; - } - return nullptr; -} - -std::shared_ptr App::get_existing_logged_in_user(std::string_view user_id) -{ - util::CheckedLockGuard lock(m_user_mutex); - if (auto it = m_users.find(std::string(user_id)); it != m_users.end() && it->second->is_logged_in()) { - if (auto user = try_lock(*it->second)) { - return user; - } - } - if (m_metadata_store->has_logged_in_user(user_id)) { - return User::make(shared_from_this(), user_id); - } - return nullptr; -} - -std::string App::get_base_url() const -{ - util::CheckedLockGuard guard(m_route_mutex); - return m_base_url; -} - -void App::update_base_url(std::string_view new_base_url, UniqueFunction)>&& completion) -{ - if (new_base_url.empty()) { - // Treat an empty string the same as requesting the default base url - new_base_url = App::default_base_url(); - log_debug("App::update_base_url: empty => %1", new_base_url); - } - else { - log_debug("App::update_base_url: %1", new_base_url); - } - - // Validate the new base_url - util::Uri::parse(new_base_url); - - bool update_not_needed; - { - util::CheckedLockGuard guard(m_route_mutex); - // Update the location if the base_url is different or a location update is already needed - m_location_updated = (new_base_url == m_base_url) && m_location_updated; - update_not_needed = m_location_updated; - } - // If the new base_url is the same as the current base_url and the location has already been updated, - // then we're done - if (update_not_needed) { - completion(util::none); - return; - } - - // Otherwise, request the location information at the new base URL - request_location(std::move(completion), std::string(new_base_url)); -} - -std::vector> App::all_users() -{ - util::CheckedLockGuard lock(m_user_mutex); - auto user_ids = m_metadata_store->get_all_users(); - std::vector> users; - users.reserve(user_ids.size()); - for (auto& user_id : user_ids) { - users.push_back(get_user_for_id(user_id)); - } - return users; -} - -void App::get_profile(const std::shared_ptr& user, - UniqueFunction&, Optional)>&& completion) -{ - do_authenticated_request( - HttpMethod::get, url_for_path("/auth/profile"), "", user, RequestTokenType::AccessToken, - [completion = std::move(completion), self = shared_from_this(), user, - this](const Response& profile_response) { - if (auto error = AppUtils::check_for_errors(profile_response)) { - return completion(nullptr, std::move(error)); - } - - try { - auto profile_json = parse(profile_response.body); - auto identities_json = get(profile_json, "identities"); - - std::vector identities; - identities.reserve(identities_json.size()); - for (auto& identity_json : identities_json) { - auto doc = as(identity_json); - identities.push_back({get(doc, "id"), get(doc, "provider_type")}); - } - - m_metadata_store->update_user(user->user_id(), [&](auto& data) { - data.identities = std::move(identities); - data.profile = UserProfile(get(profile_json, "data")); - user->update_backing_data(data); // FIXME - }); - } - catch (const AppError& err) { - return completion(nullptr, err); - } - - return completion(user, {}); - }); -} - -void App::attach_auth_options(BsonDocument& body) -{ - log_debug("App: version info: platform: %1 version: %2 - sdk: %3 - sdk version: %4 - core version: %5", - util::get_library_platform(), m_config.device_info.platform_version, m_config.device_info.sdk, - m_config.device_info.sdk_version, REALM_VERSION_STRING); - - BsonDocument options; - options["appId"] = m_config.app_id; - options["platform"] = util::get_library_platform(); - options["platformVersion"] = m_config.device_info.platform_version; - options["sdk"] = m_config.device_info.sdk; - options["sdkVersion"] = m_config.device_info.sdk_version; - options["cpuArch"] = util::get_library_cpu_arch(); - options["deviceName"] = m_config.device_info.device_name; - options["deviceVersion"] = m_config.device_info.device_version; - options["frameworkName"] = m_config.device_info.framework_name; - options["frameworkVersion"] = m_config.device_info.framework_version; - options["coreVersion"] = REALM_VERSION_STRING; - options["bundleId"] = m_config.device_info.bundle_id; - - body["options"] = BsonDocument({{"device", options}}); -} - -void App::log_in_with_credentials(const AppCredentials& credentials, const std::shared_ptr& linking_user, - UniqueFunction&, Optional)>&& completion) -{ - if (would_log(util::Logger::Level::debug)) { - auto app_info = util::format("app_id: %1", m_config.app_id); - log_debug("App: log_in_with_credentials: %1", app_info); - } - // if we try logging in with an anonymous user while there - // is already an anonymous session active, reuse it - std::shared_ptr anon_user; - if (credentials.provider() == AuthProvider::ANONYMOUS) { - util::CheckedLockGuard lock(m_user_mutex); - for (auto& [_, user] : m_users) { - if (user->is_anonymous()) { - anon_user = try_lock(*user); - if (!anon_user) - continue; - m_current_user = user; - m_metadata_store->set_current_user(user->user_id()); - break; - } - } - } - - if (anon_user) { - emit_change_to_subscribers(); - completion(anon_user, util::none); - return; - } - - if (linking_user) { - util::CheckedLockGuard lock(m_user_mutex); - if (!verify_user_present(linking_user)) { - return completion(nullptr, AppError(ErrorCodes::ClientUserNotFound, "The specified user was not found.")); - } - } - - // construct the route - std::string route = util::format("%1/providers/%2/login%3", auth_route(), credentials.provider_as_string(), - linking_user ? "?link=true" : ""); - - BsonDocument body = credentials.serialize_as_bson(); - attach_auth_options(body); - - do_request( - make_request(HttpMethod::post, std::move(route), linking_user, RequestTokenType::AccessToken, - Bson(body).to_string()), - [completion = std::move(completion), credentials, linking_user, self = shared_from_this(), - this](auto&&, const Response& response) mutable { - if (auto error = AppUtils::check_for_errors(response)) { - log_error("App: log_in_with_credentials failed: %1 message: %2", response.http_status_code, - error->what()); - return completion(nullptr, std::move(error)); - } - - std::shared_ptr user = linking_user; - try { - auto json = parse(response.body); - if (linking_user) { - m_metadata_store->update_user(linking_user->user_id(), [&](auto& data) { - data.access_token = RealmJWT(get(json, "access_token")); - // FIXME: should be powered by callback - linking_user->update_backing_data(data); - }); - } - else { - auto user_id = get(json, "user_id"); - m_metadata_store->create_user(user_id, get(json, "refresh_token"), - get(json, "access_token"), - get(json, "device_id")); - util::CheckedLockGuard lock(m_user_mutex); - user_data_updated(user_id); // FIXME: needs to be callback from metadata store - user = get_user_for_id(user_id); - } - } - catch (const AppError& e) { - return completion(nullptr, e); - } - // If the user has not been logged in, then there is a problem with the token - if (!user->is_logged_in()) { - return completion(nullptr, - AppError(ErrorCodes::BadToken, "Could not log in user: received malformed JWT")); - } - - get_profile(user, [this, completion = std::move(completion)](const std::shared_ptr& user, - Optional error) { - if (!error) { - switch_user(user); - } - completion(user, error); - }); - }, - false); -} - -void App::log_in_with_credentials( - const AppCredentials& credentials, - util::UniqueFunction&, Optional)>&& completion) -{ - App::log_in_with_credentials(credentials, nullptr, std::move(completion)); -} - -void App::log_out(const std::shared_ptr& user, SyncUser::State new_state, - UniqueFunction)>&& completion) -{ - if (!user || user->state() == new_state || user->state() == SyncUser::State::Removed) { - if (completion) { - completion(util::none); - } - return; - } - - log_debug("App: log_out(%1)", user->user_id()); - auto request = - make_request(HttpMethod::del, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, ""); - - m_metadata_store->log_out(user->user_id(), new_state); - user->update_backing_data(m_metadata_store->get_user(user->user_id())); - - do_request(std::move(request), - [self = shared_from_this(), completion = std::move(completion)](auto&&, const Response& response) { - auto error = AppUtils::check_for_errors(response); - if (!error) { - self->emit_change_to_subscribers(); - } - if (completion) { - completion(error); - } - }); -} - -void App::log_out(const std::shared_ptr& user, UniqueFunction)>&& completion) -{ - auto new_state = user && user->is_anonymous() ? SyncUser::State::Removed : SyncUser::State::LoggedOut; - log_out(user, new_state, std::move(completion)); -} - -void App::log_out(UniqueFunction)>&& completion) -{ - log_out(current_user(), std::move(completion)); -} - -bool App::verify_user_present(const std::shared_ptr& user) const -{ - for (auto& [_, u] : m_users) { - if (u == user.get()) - return true; - } - return false; -} - -void App::switch_user(const std::shared_ptr& user) -{ - if (!user || user->state() != SyncUser::State::LoggedIn) { - throw AppError(ErrorCodes::ClientUserNotLoggedIn, "User is no longer valid or is logged out"); - } - { - util::CheckedLockGuard lock(m_user_mutex); - if (!verify_user_present(user)) { - throw AppError(ErrorCodes::ClientUserNotFound, "User does not exist"); - } - - m_current_user = user.get(); - m_metadata_store->set_current_user(user->user_id()); - } - emit_change_to_subscribers(); -} - -void App::remove_user(const std::shared_ptr& user, UniqueFunction)>&& completion) -{ - if (!user || user->state() == SyncUser::State::Removed) { - return completion(AppError(ErrorCodes::ClientUserNotFound, "User has already been removed")); - } - - { - util::CheckedLockGuard lock(m_user_mutex); - if (!verify_user_present(user)) { - return completion(AppError(ErrorCodes::ClientUserNotFound, "No user has been found")); - } - } - - if (user->is_logged_in()) { - log_out( - user, SyncUser::State::Removed, - [user, completion = std::move(completion), self = shared_from_this()](const Optional& error) { - user->update_backing_data(std::nullopt); - if (completion) { - completion(error); - } - }); - } - else { - m_metadata_store->log_out(user->user_id(), SyncUser::State::Removed); - user->update_backing_data(std::nullopt); - if (completion) { - completion(std::nullopt); - } - } -} - -void App::delete_user(const std::shared_ptr& user, UniqueFunction)>&& completion) -{ - if (!user) { - return completion(AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found.")); - } - if (user->state() != SyncUser::State::LoggedIn) { - return completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "User must be logged in to be deleted.")); - } - - { - util::CheckedLockGuard lock(m_user_mutex); - if (!verify_user_present(user)) { - return completion(AppError(ErrorCodes::ClientUserNotFound, "No user has been found.")); - } - } - - do_authenticated_request( - HttpMethod::del, url_for_path("/auth/delete"), "", user, RequestTokenType::AccessToken, - [self = shared_from_this(), completion = std::move(completion), user, this](const Response& response) { - auto error = AppUtils::check_for_errors(response); - if (!error) { - auto user_id = user->user_id(); - user->detach_and_tear_down(); - m_metadata_store->delete_user(*m_file_manager, user_id); - emit_change_to_subscribers(); - } - completion(std::move(error)); - }); -} - -void App::link_user(const std::shared_ptr& user, const AppCredentials& credentials, - UniqueFunction&, Optional)>&& completion) -{ - if (!user) { - return completion(nullptr, - AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found.")); - } - if (user->state() != SyncUser::State::LoggedIn) { - return completion(nullptr, - AppError(ErrorCodes::ClientUserNotLoggedIn, "The specified user is not logged in.")); - } - if (credentials.provider() == AuthProvider::ANONYMOUS) { - return completion(nullptr, AppError(ErrorCodes::ClientUserAlreadyNamed, - "Cannot add anonymous credentials to an existing user.")); - } - - log_in_with_credentials(credentials, user, std::move(completion)); -} - -std::shared_ptr App::create_fake_user_for_testing(const std::string& user_id, const std::string& access_token, - const std::string& refresh_token) -{ - std::shared_ptr user; - { - m_metadata_store->create_user(user_id, refresh_token, access_token, "fake_device"); - util::CheckedLockGuard lock(m_user_mutex); - user_data_updated(user_id); // FIXME: needs to be callback from metadata store - user = get_user_for_id(user_id); - } - - switch_user(user); - return user; -} - - -void App::refresh_custom_data(const std::shared_ptr& user, - UniqueFunction)>&& completion) -{ - refresh_access_token(user, false, std::move(completion)); -} - -void App::refresh_custom_data(const std::shared_ptr& user, bool update_location, - UniqueFunction)>&& completion) -{ - refresh_access_token(user, update_location, std::move(completion)); -} - -std::string App::url_for_path(const std::string& path = "") const -{ - util::CheckedLockGuard guard(m_route_mutex); - return util::format("%1%2", m_base_route, path); -} - -std::string App::get_app_route(const Optional& hostname) const -{ - if (hostname) { - return util::format("%1%2%3/%4", *hostname, s_base_path, s_app_path, m_config.app_id); - } - return m_app_route; -} - -void App::request_location(UniqueFunction)>&& completion, - std::optional&& new_hostname, std::optional&& redir_location, - int redirect_count) -{ - // Request the new location information at the new base url hostname; or redir response location if a redirect - // occurred during the initial location request. redirect_count is used to track the number of sequential - // redirect responses received during the location update and return an error if this count exceeds - // max_http_redirects. If neither new_hostname nor redir_location is provided, the current value of m_base_url - // will be used. - std::string app_route; - std::string base_url; - { - util::CheckedUniqueLock lock(m_route_mutex); - // Skip if the location info has already been initialized and a new hostname is not provided - if (!new_hostname && !redir_location && m_location_updated) { - // Release the lock before calling the completion function - lock.unlock(); - completion(util::none); - return; - } - base_url = new_hostname.value_or(m_base_url); - // If this is for a redirect after querying new_hostname, then use the redirect location - if (redir_location) - app_route = get_app_route(redir_location); - // If this is querying the new_hostname, then use that location - else if (new_hostname) - app_route = get_app_route(new_hostname); - else - app_route = get_app_route(); - REALM_ASSERT(!app_route.empty()); - } - - Request req; - req.method = HttpMethod::get; - req.url = util::format("%1/location", app_route); - req.timeout_ms = m_request_timeout_ms; - - log_debug("App: request location: %1", req.url); - - m_config.transport->send_request_to_server(req, [self = shared_from_this(), completion = std::move(completion), - base_url = std::move(base_url), - redirect_count](const Response& response) mutable { - // Check to see if a redirect occurred - if (AppUtils::is_redirect_status_code(response.http_status_code)) { - // Make sure we don't do too many redirects (max_http_redirects (20) is an arbitrary number) - if (redirect_count >= s_max_http_redirects) { - completion(AppError{ErrorCodes::ClientTooManyRedirects, - util::format("number of redirections exceeded %1", s_max_http_redirects), - {}, - response.http_status_code}); - return; - } - // Handle the redirect response when requesting the location - extract the - // new location header field and resend the request. - auto redir_location = AppUtils::extract_redir_location(response.headers); - if (!redir_location) { - // Location not found in the response, pass error response up the chain - completion(AppError{ErrorCodes::ClientRedirectError, - "Redirect response missing location header", - {}, - response.http_status_code}); - return; - } - // try to request the location info at the new location in the redirect response - // retry_count is passed in to track the number of subsequent redirection attempts - self->request_location(std::move(completion), std::move(base_url), std::move(redir_location), - redirect_count + 1); - return; - } - - // Location request was successful - update the location info - auto update_response = self->update_location(response, base_url); - if (update_response) { - self->log_error("App: request location failed (%1%2): %3", update_response->code_string(), - update_response->additional_status_code - ? util::format(" %1", *update_response->additional_status_code) - : "", - update_response->reason()); - } - completion(update_response); - }); -} - -std::optional App::update_location(const Response& response, const std::string& base_url) -{ - // Validate the location info response for errors and update the stored location info if it is - // a valid response. base_url is the new hostname or m_base_url value when request_location() - // was called. - - if (auto error = AppUtils::check_for_errors(response)) { - return error; - } - - // Update the location info with the data from the response - try { - auto json = parse(response.body); - auto hostname = get(json, "hostname"); - auto ws_hostname = get(json, "ws_hostname"); - std::optional sync_route; - read_field(json, "sync_route", sync_route); - - util::CheckedLockGuard guard(m_route_mutex); - // Update the local hostname and path information - update_hostname(hostname, ws_hostname, base_url); - m_location_updated = true; - if (!sync_route) { - sync_route = make_sync_route(); - } - m_sync_manager->set_sync_route(*sync_route, true); - } - catch (const AppError& ex) { - return ex; - } - return util::none; -} - -void App::update_location_and_resend(std::unique_ptr&& request, IntermediateCompletion&& completion, - Optional&& redir_location) -{ - // Update the location information if a redirect response was received or m_location_updated == false - // and then send the request to the server with request.url updated to the new AppServices hostname. - request_location( - [completion = std::move(completion), request = std::move(request), - self = shared_from_this()](Optional error) mutable { - if (error) { - // Operation failed, pass it up the chain - return completion(std::move(request), AppUtils::make_apperror_response(*error)); - } - - // If the location info was updated, update the original request to point - // to the new location URL. - auto url = util::Uri::parse(request->url); - request->url = - util::format("%1%2%3%4", self->get_host_url(), url.get_path(), url.get_query(), url.get_frag()); - - self->log_debug("App: send_request(after location update): %1 %2", request->method, request->url); - // Retry the original request with the updated url - auto& request_ref = *request; - self->m_config.transport->send_request_to_server( - request_ref, [self = std::move(self), completion = std::move(completion), - request = std::move(request)](const Response& response) mutable { - self->check_for_redirect_response(std::move(request), response, std::move(completion)); - }); - }, - // The base_url is not changing for this request - util::none, std::move(redir_location)); -} - -void App::post(std::string&& route, UniqueFunction)>&& completion, const BsonDocument& body) -{ - do_request( - make_request(HttpMethod::post, std::move(route), nullptr, RequestTokenType::NoAuth, Bson(body).to_string()), - [completion = std::move(completion)](auto&&, const Response& response) { - completion(AppUtils::check_for_errors(response)); - }); -} - -void App::do_request(std::unique_ptr&& request, IntermediateCompletion&& completion, bool update_location) -{ - // Verify the request URL to make sure it is valid - util::Uri::parse(request->url); - - // Refresh the location info when app is created or when requested (e.g. after a websocket redirect) - // to ensure the http and websocket URL information is up to date. - { - util::CheckedUniqueLock lock(m_route_mutex); - if (update_location) { - // If requesting a location update, force the location to be updated before sending the request. - m_location_updated = false; - } - if (!m_location_updated) { - lock.unlock(); - // Location info needs to be requested, update the location info and then send the request - update_location_and_resend(std::move(request), std::move(completion)); - return; - } - } - - log_debug("App: do_request: %1 %2", request->method, request->url); - // If location info has already been updated, then send the request directly - auto& request_ref = *request; - m_config.transport->send_request_to_server( - request_ref, [self = shared_from_this(), completion = std::move(completion), - request = std::move(request)](const Response& response) mutable { - self->check_for_redirect_response(std::move(request), response, std::move(completion)); - }); -} - -void App::check_for_redirect_response(std::unique_ptr&& request, const Response& response, - IntermediateCompletion&& completion) -{ - // If this isn't a redirect response, then we're done - if (!AppUtils::is_redirect_status_code(response.http_status_code)) { - return completion(std::move(request), response); - } - - // Handle a redirect response when sending the original request - extract the location - // header field and resend the request. - auto redir_location = AppUtils::extract_redir_location(response.headers); - if (!redir_location) { - // Location not found in the response, pass error response up the chain - return completion(std::move(request), - AppUtils::make_clienterror_response(ErrorCodes::ClientRedirectError, - "Redirect response missing location header", - response.http_status_code)); - } - - // Request the location info at the new location - once this is complete, the original - // request will be sent to the new server - update_location_and_resend(std::move(request), std::move(completion), std::move(redir_location)); -} - -void App::do_authenticated_request(HttpMethod method, std::string&& route, std::string&& body, - const std::shared_ptr& user, RequestTokenType token_type, - util::UniqueFunction&& completion) -{ - auto request = make_request(method, std::move(route), user, token_type, std::move(body)); - do_request(std::move(request), [token_type, user, completion = std::move(completion), self = shared_from_this()]( - std::unique_ptr&& request, const Response& response) mutable { - if (auto error = AppUtils::check_for_errors(response)) { - self->handle_auth_failure(std::move(*error), std::move(request), response, user, token_type, - std::move(completion)); - } - else { - completion(response); - } - }); -} - -void App::handle_auth_failure(const AppError& error, std::unique_ptr&& request, const Response& response, - const std::shared_ptr& user, RequestTokenType token_type, - util::UniqueFunction&& completion) -{ - // Only handle auth failures - if (*error.additional_status_code != 401) { - completion(response); - return; - } - - // If the refresh token is invalid then the user needs to be logged back - // in to be able to use it again - if (token_type == RequestTokenType::RefreshToken) { - if (user && user->is_logged_in()) { - user->log_out(); - } - completion(response); - return; - } - - // Otherwise we may be able to request a new access token and have the request succeed with that - refresh_access_token(user, false, - [self = shared_from_this(), request = std::move(request), completion = std::move(completion), - response = std::move(response), user](Optional&& error) mutable { - if (error) { - // pass the error back up the chain - completion(response); - return; - } - - // Reissue the request with the new access token - request->headers = get_request_headers(user, RequestTokenType::AccessToken); - self->do_request(std::move(request), - [completion = std::move(completion)](auto&&, auto& response) { - completion(response); - }); - }); -} - -/// MARK: - refresh access token -void App::refresh_access_token(const std::shared_ptr& user, bool update_location, - util::UniqueFunction)>&& completion) -{ - if (!user) { - completion(AppError(ErrorCodes::ClientUserNotFound, "No current user exists")); - return; - } - - if (!user->is_logged_in()) { - completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "The user is not logged in")); - return; - } - - log_debug("App: refresh_access_token: user_id: %1%2", user->user_id(), - update_location ? " (updating location)" : ""); - - // If update_location is set, force the location info to be updated before sending the request - do_request( - make_request(HttpMethod::post, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, ""), - [completion = std::move(completion), self = shared_from_this(), user](auto&&, const Response& response) { - if (auto error = AppUtils::check_for_errors(response)) { - self->log_error("App: refresh_access_token: %1 -> %2 ERROR: %3", user->user_id(), - response.http_status_code, error->what()); - - return completion(std::move(error)); - } - - try { - auto json = parse(response.body); - RealmJWT access_token{get(json, "access_token")}; - self->m_metadata_store->update_user(user->user_id(), [&](auto& data) { - data.access_token = access_token; - user->update_backing_data(data); - }); - } - catch (AppError& err) { - return completion(std::move(err)); - } - - return completion(util::none); - }, - update_location); -} - -std::string App::function_call_url_path() const -{ - util::CheckedLockGuard guard(m_route_mutex); - return util::format("%1/functions/call", m_app_route); -} - -void App::call_function(const std::shared_ptr& user, const std::string& name, std::string_view args_ejson, - const Optional& service_name_opt, - UniqueFunction)>&& completion) -{ - auto service_name = service_name_opt ? *service_name_opt : ""; - if (would_log(util::Logger::Level::debug)) { - log_debug("App: call_function: %1 service_name: %2 args_bson: %3", name, service_name, args_ejson); - } - - auto args = util::format("{\"arguments\":%1,\"name\":%2%3}", args_ejson, nlohmann::json(name).dump(), - service_name_opt ? (",\"service\":" + nlohmann::json(service_name).dump()) : ""); - - do_authenticated_request(HttpMethod::post, function_call_url_path(), std::move(args), user, - RequestTokenType::AccessToken, - [self = shared_from_this(), name = name, service_name = std::move(service_name), - completion = std::move(completion)](const Response& response) { - if (auto error = AppUtils::check_for_errors(response)) { - self->log_error("App: call_function: %1 service_name: %2 -> %3 ERROR: %4", name, - service_name, response.http_status_code, error->what()); - return completion(nullptr, error); - } - completion(&response.body, util::none); - }); -} - -void App::call_function(const std::shared_ptr& user, const std::string& name, const BsonArray& args_bson, - const Optional& service_name, - UniqueFunction&&, Optional)>&& completion) -{ - auto service_name2 = service_name ? *service_name : ""; - std::stringstream args_ejson; - args_ejson << "["; - bool not_first = false; - for (auto&& arg : args_bson) { - if (not_first) - args_ejson << ','; - args_ejson << arg.toJson(); - not_first = true; - } - args_ejson << "]"; - - call_function(user, name, std::move(args_ejson).str(), service_name, - [self = shared_from_this(), name, service_name = std::move(service_name2), - completion = std::move(completion)](const std::string* response, util::Optional err) { - if (err) { - return completion({}, err); - } - if (!response) { - return completion({}, AppError{ErrorCodes::AppUnknownError, "Empty response from server"}); - } - util::Optional body_as_bson; - try { - body_as_bson = bson::parse(*response); - if (self->would_log(util::Logger::Level::debug)) { - self->log_debug("App: call_function: %1 service_name: %2 - results: %3", name, - service_name, body_as_bson ? body_as_bson->to_string() : ""); - } - } - catch (const std::exception& e) { - self->log_error("App: call_function: %1 service_name: %2 - error parsing result: %3", name, - service_name, e.what()); - return completion(util::none, AppError(ErrorCodes::BadBsonParse, e.what())); - }; - completion(std::move(body_as_bson), util::none); - }); -} - -void App::call_function(const std::shared_ptr& user, const std::string& name, const BsonArray& args_bson, - UniqueFunction&&, Optional)>&& completion) -{ - call_function(user, name, args_bson, util::none, std::move(completion)); -} - -void App::call_function(const std::string& name, const BsonArray& args_bson, - const Optional& service_name, - UniqueFunction&&, Optional)>&& completion) -{ - call_function(current_user(), name, args_bson, service_name, std::move(completion)); -} - -void App::call_function(const std::string& name, const BsonArray& args_bson, - UniqueFunction&&, Optional)>&& completion) -{ - call_function(current_user(), name, args_bson, std::move(completion)); -} - -Request App::make_streaming_request(const std::shared_ptr& user, const std::string& name, - const BsonArray& args_bson, const Optional& service_name) const -{ - auto args = BsonDocument{ - {"arguments", args_bson}, - {"name", name}, - }; - if (service_name) { - args["service"] = *service_name; - } - const auto args_json = Bson(args).to_string(); - - auto args_base64 = std::string(util::base64_encoded_size(args_json.size()), '\0'); - util::base64_encode(args_json, args_base64); - - auto url = function_call_url_path() + "?baas_request=" + util::uri_percent_encode(args_base64); - if (user) { - url += "&baas_at="; - url += user->access_token(); // doesn't need url encoding - } - - return Request{ - HttpMethod::get, - url, - m_request_timeout_ms, - {{"Accept", "text/event-stream"}}, - }; -} - -std::unique_ptr App::make_request(HttpMethod method, std::string&& url, const std::shared_ptr& user, - RequestTokenType token_type, std::string&& body) const -{ - auto request = std::make_unique(); - request->method = method; - request->url = std::move(url); - request->body = std::move(body); - request->headers = get_request_headers(user, token_type); - request->timeout_ms = m_request_timeout_ms; - return request; -} - -PushClient App::push_notification_client(const std::string& service_name) -{ - return PushClient(service_name, m_config.app_id, std::shared_ptr(shared_from_this(), this)); -} - -void App::emit_change_to_subscribers() -{ - // This wrapper is needed only to be able to add the `REQUIRES(!m_user_mutex)` - // annotation. Calling this function with the lock held leads to a deadlock - // if any of the listeners try to access us. - Subscribable::emit_change_to_subscribers(*this); -} - -// MARK: - UserProvider - -void App::register_sync_user(User& user) -{ - auto& tracked_user = m_users[user.user_id()]; - REALM_ASSERT(!tracked_user || !tracked_user->weak_from_this().lock()); - tracked_user = &user; - user.update_backing_data(m_metadata_store->get_user(user.user_id())); -} - -void App::unregister_sync_user(User& user) -{ - util::CheckedLockGuard lock(m_user_mutex); - auto it = m_users.find(user.user_id()); - REALM_ASSERT(it != m_users.end()); - // If the user was requested while we were waiting for the lock, it may - // have already been replaced with a new instance for the same user id - if (it != m_users.end() && it->second == &user) { - m_users.erase(it); - } - if (m_current_user == &user) { - m_current_user = nullptr; - } -} - -bool App::immediately_run_file_actions(std::string_view realm_path) -{ - return m_metadata_store->immediately_run_file_actions(*m_file_manager, realm_path); -} - -std::string App::path_for_realm(const SyncConfig& config, std::optional custom_file_name) const -{ - return m_file_manager->path_for_realm(config, std::move(custom_file_name)); -} - -} // namespace realm::app diff --git a/src/realm/object-store/sync/app.hpp b/src/realm/object-store/sync/app.hpp deleted file mode 100644 index 3fe735ca648..00000000000 --- a/src/realm/object-store/sync/app.hpp +++ /dev/null @@ -1,642 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_APP_HPP -#define REALM_APP_HPP - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace realm { -class SyncSession; -class SyncManager; -class SyncFileManager; - -namespace app { - -class App; -class MetadataStore; -class User; - -typedef std::shared_ptr SharedApp; - -/// The `App` has the fundamental set of methods for communicating with a Atlas App Services backend. -/// -/// This class provides access to login and authentication. -/// -/// You can also use it to execute [Functions](https://docs.mongodb.com/stitch/functions/). -class App : public std::enable_shared_from_this, - private AuthRequestClient, - private AppServiceClient, - public Subscribable { - struct Private {}; - -public: - // MARK: - App Initialization - enum class CacheMode { - Enabled, // Return a cached app instance if one was previously generated for `config`'s app_id+base_url combo, - Disabled // Bypass the app cache; return a new app instance. - }; - /// Get a shared pointer to a configured App instance. Sync is fully enabled and the external backing store - /// factory provided is used to create a store if the cache is not used. If you want the - /// default storage engine, construct a RealmMetadataStore instance in the factory. - static SharedApp get_app(CacheMode mode, const AppConfig& config); - - // Returns the default base_url for SDKs to use instead of defining their own - static std::string_view default_base_url(); - - /// Return a cached app instance if one was previously generated for the `app_id`+`base_url` combo using - /// `App::get_app()`. - /// If base_url is not provided, and there are multiple cached apps with the same app_id but different base_urls, - /// then a non-determinstic one will be returned. - /// - /// Prefer using `App::get_app()` or populating `base_url` to avoid the non-deterministic behavior. - static SharedApp get_cached_app(const std::string& app_id, - const std::optional& base_url = std::nullopt); - - /// Clear the cache used for `get_app(CacheMode::Enable)` and `get_cached_app()`. - static void clear_cached_apps(); - - explicit App(Private, const AppConfig& config); - App(App&&) noexcept = delete; - App& operator=(App&&) noexcept = delete; - ~App(); - - const AppConfig& config() const - { - return m_config; - } - - const std::string& app_id() const noexcept - { - return m_config.app_id; - } - - // MARK: - Other objects owned by App - const std::shared_ptr& sync_manager() const - { - return m_sync_manager; - } - - std::shared_ptr auth_request_client() - { - return std::shared_ptr(shared_from_this(), this); - } - - std::shared_ptr app_service_client() - { - return std::shared_ptr(shared_from_this(), this); - } - - // MARK: - User Management - - /// Get the last used user. - std::shared_ptr current_user() REQUIRES(!m_user_mutex); - /// Get the user object for the given `user_id` if a user with that id is logged in, or nullptr if not. - std::shared_ptr get_existing_logged_in_user(std::string_view user_id) REQUIRES(!m_user_mutex); - /// Get all users. - std::vector> all_users() REQUIRES(!m_user_mutex); - /// Set the current user to the given one. The user must be logged in and have been obtained from this `App` - /// instance. - void switch_user(const std::shared_ptr& user) REQUIRES(!m_user_mutex); - - /// Log in a user and asynchronously retrieve a user object. - /// If the log in completes successfully, the completion block will be called, and a - /// `User` representing the logged-in user will be passed to it. This user object - /// can be used to open `Realm`s and retrieve `SyncSession`s. Otherwise, the - /// completion block will be called with an error. - /// - /// @param credentials An `AppCredentials` object representing the user to log in. - /// @param completion A callback block to be invoked once the log in completes. - void log_in_with_credentials( - const AppCredentials& credentials, - util::UniqueFunction&, std::optional)>&& completion) - REQUIRES(!m_route_mutex, !m_user_mutex); - - /// Logout the current user. - void log_out(util::UniqueFunction)>&&) REQUIRES(!m_route_mutex, !m_user_mutex); - - /// Refreshes the custom data for a specified user - /// @param user The user you want to refresh - /// @param update_location If true, the location metadata will be updated before refresh - void refresh_custom_data(const std::shared_ptr& user, bool update_location, - util::UniqueFunction)>&& completion) - REQUIRES(!m_route_mutex); - void refresh_custom_data(const std::shared_ptr& user, - util::UniqueFunction)>&& completion) - REQUIRES(!m_route_mutex); - - /// Log out the given user if they are not already logged out. - void log_out(const std::shared_ptr& user, util::UniqueFunction)>&& completion) - REQUIRES(!m_route_mutex); - - /// Links the currently authenticated user with a new identity, where the identity is defined by the credential - /// specified as a parameter. This will only be successful if this `User` is the currently authenticated - /// with the client from which it was created. On success the user will be returned with the new identity. - /// - /// @param user The user which will have the credentials linked to, the user must be logged in - /// @param credentials The `AppCredentials` used to link the user to a new identity. - /// @param completion The completion handler to call when the linking is complete. - /// If the operation is successful, the result will contain the original - /// `User` object representing the user. - void link_user(const std::shared_ptr& user, const AppCredentials& credentials, - util::UniqueFunction&, std::optional)>&& completion) - REQUIRES(!m_route_mutex, !m_user_mutex); - - - /// Logs out and removes the provided user. - /// This invokes logout on the server. - /// @param user the user to remove - /// @param completion Will return an error if the user is not found or the http request failed. - void remove_user(const std::shared_ptr& user, - util::UniqueFunction)>&& completion) - REQUIRES(!m_route_mutex, !m_user_mutex); - - /// Deletes a user and all its data from the server. - /// @param user The user to delete - /// @param completion Will return an error if the user is not found or the http request failed. - void delete_user(const std::shared_ptr& user, - util::UniqueFunction)>&& completion) - REQUIRES(!m_route_mutex, !m_user_mutex); - - /// Creates a fake user with the provided access and refresh tokens. No validation is done to ensure that the - /// credentials are actually valid and as such, this should only be used for testing purposes. - /// @param user_id The id of the user that will be created - /// @param access_token The access token of the user - /// @param refresh_token The refresh token of the user - std::shared_ptr create_fake_user_for_testing(const std::string& user_id, const std::string& access_token, - const std::string& refresh_token) REQUIRES(!m_user_mutex); - - // MARK: - Provider Clients - - /// A struct representing a user API key as returned by the App server. - struct UserAPIKey { - // The ID of the key. - ObjectId id; - - /// The actual key. Will only be included in - /// the response when an API key is first created. - std::optional key; - - /// The name of the key. - std::string name; - - /// Whether or not the key is disabled. - bool disabled; - }; - - /// A client for the user API key authentication provider which - /// can be used to create and modify user API keys. This - /// client should only be used by an authenticated user. - class UserAPIKeyProviderClient { - public: - /// Creates a user API key that can be used to authenticate as the current user. - /// @param name The name of the API key to be created. - /// @param completion A callback to be invoked once the call is complete. - void create_api_key(const std::string& name, const std::shared_ptr& user, - util::UniqueFunction)>&& completion); - - /// Fetches a user API key associated with the current user. - /// @param id The id of the API key to fetch. - /// @param completion A callback to be invoked once the call is complete. - void fetch_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - util::UniqueFunction)>&& completion); - - /// Fetches the user API keys associated with the current user. - /// @param completion A callback to be invoked once the call is complete. - void - fetch_api_keys(const std::shared_ptr& user, - util::UniqueFunction&&, std::optional)>&& completion); - - /// Deletes a user API key associated with the current user. - /// @param id The id of the API key to delete. - /// @param user The user to perform this operation. - /// @param completion A callback to be invoked once the call is complete. - void delete_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - util::UniqueFunction)>&& completion); - - /// Enables a user API key associated with the current user. - /// @param id The id of the API key to enable. - /// @param user The user to perform this operation. - /// @param completion A callback to be invoked once the call is complete. - void enable_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - util::UniqueFunction)>&& completion); - - /// Disables a user API key associated with the current user. - /// @param id The id of the API key to disable. - /// @param user The user to perform this operation. - /// @param completion A callback to be invoked once the call is complete. - void disable_api_key(const realm::ObjectId& id, const std::shared_ptr& user, - util::UniqueFunction)>&& completion); - - private: - friend class App; - UserAPIKeyProviderClient(AuthRequestClient& auth_request_client) - : m_auth_request_client(auth_request_client) - { - } - - std::string url_for_path(const std::string& path) const; - AuthRequestClient& m_auth_request_client; - }; - - /// A client for the username/password authentication provider which - /// can be used to obtain a credential for logging in, - /// and to perform requests specifically related to the username/password provider. - /// - class UsernamePasswordProviderClient { - public: - /// Registers a new email identity with the username/password provider, - /// and sends a confirmation email to the provided address. - /// @param email The email address of the user to register. - /// @param password The password that the user created for the new username/password identity. - /// @param completion A callback to be invoked once the call is complete. - void register_email(const std::string& email, const std::string& password, - util::UniqueFunction)>&& completion); - - /// Confirms an email identity with the username/password provider. - /// @param token The confirmation token that was emailed to the user. - /// @param token_id The confirmation token id that was emailed to the user. - /// @param completion A callback to be invoked once the call is complete. - void confirm_user(const std::string& token, const std::string& token_id, - util::UniqueFunction)>&& completion); - - /// Re-sends a confirmation email to a user that has registered but - /// not yet confirmed their email address. - /// @param email The email address of the user to re-send a confirmation for. - /// @param completion A callback to be invoked once the call is complete. - void resend_confirmation_email(const std::string& email, - util::UniqueFunction)>&& completion); - - void send_reset_password_email(const std::string& email, - util::UniqueFunction)>&& completion); - - /// Retries the custom confirmation function on a user for a given email. - /// @param email The email address of the user to retry the custom confirmation for. - /// @param completion A callback to be invoked once the retry is complete. - void retry_custom_confirmation(const std::string& email, - util::UniqueFunction)>&& completion); - - /// Resets the password of an email identity using the - /// password reset token emailed to a user. - /// @param password The desired new password. - /// @param token The password reset token that was emailed to the user. - /// @param token_id The password reset token id that was emailed to the user. - /// @param completion A callback to be invoked once the call is complete. - void reset_password(const std::string& password, const std::string& token, const std::string& token_id, - util::UniqueFunction)>&& completion); - - /// Resets the password of an email identity using the - /// password reset function set up in the application. - /// @param email The email address of the user. - /// @param password The desired new password. - /// @param args A bson array of arguments. - /// @param completion A callback to be invoked once the call is complete. - void call_reset_password_function(const std::string& email, const std::string& password, - const bson::BsonArray& args, - util::UniqueFunction)>&& completion); - - private: - friend class App; - UsernamePasswordProviderClient(SharedApp app) - : m_parent(app) - { - REALM_ASSERT(app); - } - SharedApp m_parent; - }; - - - // Get a provider client for the given class type. - template - T provider_client() - { - return T(this); - } - - // MARK: - App Services - - // Return the base url path used for HTTP AppServices requests - std::string get_host_url() REQUIRES(!m_route_mutex); - - /// Get the current base URL for the AppServices server used for http requests and sync - /// connections. - /// If an update_base_url() operation is currently in progress, this value will not be - /// updated with the new value until that operation is complete. - /// @return String containing the current base url value - std::string get_base_url() const REQUIRES(!m_route_mutex); - - /// Update the base URL after the app has been created. The location info will be retrieved - /// using the provided base URL. If this operation fails, the app will continue to use the original base URL and - /// the error will be provided to the completion callback. If the operation is successful, the app and sync - /// client will use the new location info for future connections. - /// NOTE: If another App operation is started while this function is in progress, that request will use the - /// original base URL location information. - /// @param base_url The new base URL to use for future AppServices requests and sync websocket connections. If - /// an empty string, the default Device Sync base_url will be used. - /// @param completion A callback block to be invoked once the location update completes. - void update_base_url(std::string_view base_url, util::UniqueFunction)>&& completion) - REQUIRES(!m_route_mutex); - - void call_function(const std::shared_ptr& user, const std::string& name, std::string_view args_ejson, - const std::optional& service_name, - util::UniqueFunction)>&& completion) final - REQUIRES(!m_route_mutex); - - void - call_function(const std::shared_ptr& user, const std::string& name, const bson::BsonArray& args_bson, - const std::optional& service_name, - util::UniqueFunction&&, std::optional)>&& completion) final - REQUIRES(!m_route_mutex); - - void - call_function(const std::shared_ptr& user, const std::string&, const bson::BsonArray& args_bson, - util::UniqueFunction&&, std::optional)>&& completion) final - REQUIRES(!m_route_mutex); - - void - call_function(const std::string& name, const bson::BsonArray& args_bson, - const std::optional& service_name, - util::UniqueFunction&&, std::optional)>&& completion) final - REQUIRES(!m_route_mutex, !m_user_mutex); - - void - call_function(const std::string&, const bson::BsonArray& args_bson, - util::UniqueFunction&&, std::optional)>&& completion) final - REQUIRES(!m_route_mutex, !m_user_mutex); - - template - void call_function(const std::shared_ptr& user, const std::string& name, const bson::BsonArray& args_bson, - util::UniqueFunction&&, std::optional)>&& completion) - REQUIRES(!m_route_mutex) - { - call_function( - user, name, args_bson, util::none, - [completion = std::move(completion)](std::optional&& value, std::optional error) { - if (value) { - return completion(util::some(static_cast(*value)), std::move(error)); - } - - return completion(util::none, std::move(error)); - }); - } - - template - void call_function(const std::string& name, const bson::BsonArray& args_bson, - util::UniqueFunction&&, std::optional)>&& completion) - REQUIRES(!m_route_mutex, !m_user_mutex) - { - - call_function(current_user(), name, args_bson, std::move(completion)); - } - - // NOTE: only sets "Accept: text/event-stream" header. If you use an API that sets that but doesn't support - // setting other headers (eg. EventSource() in JS), you can ignore the headers field on the request. - Request make_streaming_request(const std::shared_ptr& user, const std::string& name, - const bson::BsonArray& args_bson, - const std::optional& service_name) const REQUIRES(!m_route_mutex); - - PushClient push_notification_client(const std::string& service_name); - - // MARK: - Sync - - // Immediately close all open sync sessions for all cached apps. - // Used by JS SDK to ensure no sync clients remain open when a developer - // reloads an app (#5411). - static void close_all_sync_sessions(); - - // Return the base url path used for Sync Session Websocket requests - std::string get_ws_host_url() REQUIRES(!m_route_mutex); - - static std::string create_ws_host_url(const std::string_view host_url); - - // Get the default path for a Realm for the given configuration. - // The default value is `///.realm`. - // If the file cannot be created at this location, for example due to path length restrictions, - // this function may pass back `/.realm` - std::string path_for_realm(const SyncConfig& config, - std::optional custom_file_name = std::nullopt) const; - - // Attempt to perform all pending file actions for the given path. Returns - // true if any were performed. - bool immediately_run_file_actions(std::string_view realm_path); - -private: - const AppConfig m_config; - - static SharedApp make_app(const AppConfig& config); - - util::CheckedMutex m_route_mutex; - // The following variables hold the different paths to Atlas, depending on the - // request being performed - // Base hostname from config.base_url or update_base_url() for querying location info - // (e.g. "https://services.cloud.mongodb.com") - std::string m_base_url GUARDED_BY(m_route_mutex); - // Baseline URL for AppServices and Device Sync requests - // (e.g. "https://us-east-1.aws.services.cloud.mongodb.com/api/client/v2.0" or - // "wss://us-east-1.aws.ws.services.cloud.mongodb.com/api/client/v2.0") - std::string m_base_route GUARDED_BY(m_route_mutex); - // URL for app-based AppServices and Device Sync requests using config.app_id - // (e.g. "https://us-east-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/" - // or "wss://us-east-1.aws.ws.services.cloud.mongodb.com/api/client/v2.0/app/") - std::string m_app_route GUARDED_BY(m_route_mutex); - // URL for app-based AppServices authentication requests (e.g. email/password) - // (e.g. "https://us-east-1.aws.services.cloud.mongodb.com/api/client/v2.0/app//auth") - std::string m_auth_route GUARDED_BY(m_route_mutex); - // If false, the location info will be updated upon the next AppServices request - bool m_location_updated GUARDED_BY(m_route_mutex) = false; - // Storage for the location info returned by the base URL location endpoint - // Base hostname for AppServices HTTP requests - std::string m_host_url GUARDED_BY(m_route_mutex); - // Base hostname for Device Sync websocket requests - std::string m_ws_host_url GUARDED_BY(m_route_mutex); - - const uint64_t m_request_timeout_ms; - std::unique_ptr m_file_manager; - std::unique_ptr m_metadata_store; - std::shared_ptr m_sync_manager; - std::shared_ptr m_logger_ptr; - - /// m_logger_ptr is not set until the first call to one of these functions. - /// If configure() not been called, a logger will not be available yet. - /// @returns true if the logger was set, otherwise false. - bool init_logger(); - /// These helpers prevent all the checks for if(m_logger_ptr) throughout the - /// code. - bool would_log(util::Logger::Level level); - template - void log_debug(const char* message, Params&&... params); - template - void log_error(const char* message, Params&&... params); - - /// Refreshes the access token for a specified `User` - /// @param completion Passes an error should one occur. - /// @param update_location If true, the location metadata will be updated before refresh - void refresh_access_token(const std::shared_ptr& user, bool update_location, - util::UniqueFunction)>&& completion) - REQUIRES(!m_route_mutex); - - /// The completion type for all intermediate operations which occur before performing the original request - using IntermediateCompletion = util::UniqueFunction&&, const Response&)>; - - /// Checks if an auth failure has taken place and if so it will attempt to refresh the - /// access token and then perform the orginal request again with the new access token - /// @param error The error to check for auth failures - /// @param request The request to perform - /// @param completion returns the original response in the case it is not an auth error, or if a failure - /// occurs, if the refresh was a success the newly attempted response will be passed back - void handle_auth_failure(const AppError& error, std::unique_ptr&& request, const Response& response, - const std::shared_ptr& user, RequestTokenType token_type, - util::UniqueFunction&& completion) REQUIRES(!m_route_mutex); - - std::string url_for_path(const std::string& path) const override REQUIRES(!m_route_mutex); - - /// Return the app route for this App instance, or creates a new app route string if - /// a new hostname is provided - /// @param hostname The hostname to generate a new app route - std::string get_app_route(const std::optional& hostname = util::none) const REQUIRES(m_route_mutex); - - /// Request the app metadata information from the server if it has not been processed yet. If - /// a new hostname is provided, the app metadata will be refreshed using the new hostname. - /// @param completion The callback that will be called with the error on failure or empty on success - /// @param new_hostname The (Original) new hostname to request the location from - /// @param redir_location The location provided by the last redirect response when querying location - /// @param redirect_count The current number of redirects that have occurred in a row - void request_location(util::UniqueFunction)>&& completion, - std::optional&& new_hostname = std::nullopt, - std::optional&& redir_location = std::nullopt, int redirect_count = 0) - REQUIRES(!m_route_mutex); - - /// Update the location metadata from the location response - /// @param response The response returned from the location request - /// @param base_url The base URL to use when setting the location metadata - /// @return std::nullopt if the updated was successful, otherwise an AppError with the error - std::optional update_location(const Response& response, const std::string& base_url) - REQUIRES(!m_route_mutex); - - /// Update the app metadata and resend the request with the updated metadata - /// @param request The original request object that needs to be sent after the update - /// @param completion The original completion object that will be called with the response to the request - /// @param new_hostname If provided, the metadata will be requested from this hostname - void update_location_and_resend(std::unique_ptr&& request, IntermediateCompletion&& completion, - std::optional&& new_hostname = util::none) REQUIRES(!m_route_mutex); - - void post(std::string&& route, util::UniqueFunction)>&& completion, - const bson::BsonDocument& body) REQUIRES(!m_route_mutex); - - /// Performs a request to the Stitch server. This request does not contain authentication state. - /// @param request The request to be performed - /// @param completion Returns the response from the server - /// @param update_location Force the location metadata to be updated prior to sending the request - void do_request(std::unique_ptr&& request, IntermediateCompletion&& completion, - bool update_location = false) REQUIRES(!m_route_mutex); - - std::unique_ptr make_request(HttpMethod method, std::string&& url, const std::shared_ptr& user, - RequestTokenType, std::string&& body) const; - - /// Process the redirect response received from the last request that was sent to the server - /// @param request The request to be performed (in case it needs to be sent again) - /// @param response The response from the send_request_to_server operation - /// @param completion Returns the response from the server if not a redirect - void check_for_redirect_response(std::unique_ptr&& request, const Response& response, - IntermediateCompletion&& completion) REQUIRES(!m_route_mutex); - - void do_authenticated_request(HttpMethod, std::string&& route, std::string&& body, - const std::shared_ptr& user, RequestTokenType, - util::UniqueFunction&&) override REQUIRES(!m_route_mutex); - - - /// Gets the social profile for a `User`. - /// - /// @param completion Callback will pass the `User` with the social profile details - void get_profile(const std::shared_ptr& user, - util::UniqueFunction&, std::optional)>&& completion) - REQUIRES(!m_route_mutex); - - /// Log in a user and asynchronously retrieve a user object. - /// If the log in completes successfully, the completion block will be called, and a - /// `User` representing the logged-in user will be passed to it. This user object - /// can be used to open `Realm`s and retrieve `SyncSession`s. Otherwise, the - /// completion block will be called with an error. - /// - /// @param credentials An `AppCredentials` object representing the user to log in. - /// @param linking_user A `User` you want to link these credentials too - /// @param completion A callback block to be invoked once the log in completes. - void log_in_with_credentials( - const AppCredentials& credentials, const std::shared_ptr& linking_user, - util::UniqueFunction&, std::optional)>&& completion) - REQUIRES(!m_route_mutex, !m_user_mutex); - - /// Provides MongoDB Realm Cloud with metadata related to the users session - void attach_auth_options(bson::BsonDocument& body); - - std::string function_call_url_path() const REQUIRES(!m_route_mutex); - - static SharedApp do_get_app(CacheMode mode, const AppConfig& config, - util::FunctionRef do_config); - - void configure_backing_store(std::unique_ptr store) REQUIRES(!m_route_mutex); - - std::string make_sync_route(util::Optional ws_host_url = util::none) REQUIRES(m_route_mutex); - void configure_route(const std::string& host_url, const std::string& ws_host_url) REQUIRES(m_route_mutex); - void update_hostname(const std::string& host_url, const std::string& ws_host_url, const std::string& new_base_url) - REQUIRES(m_route_mutex); - std::string auth_route() REQUIRES(!m_route_mutex); - std::string base_url() REQUIRES(!m_route_mutex); - - bool verify_user_present(const std::shared_ptr& user) const REQUIRES(m_user_mutex); - - void emit_change_to_subscribers() REQUIRES(!m_user_mutex); - - // UserProvider implementation - friend class User; - - util::CheckedMutex m_user_mutex; - mutable std::unordered_map m_users GUARDED_BY(m_user_mutex); - User* m_current_user GUARDED_BY(m_user_mutex) = nullptr; - - void register_sync_user(User& sync_user) REQUIRES(m_user_mutex); - void unregister_sync_user(User& user) REQUIRES(!m_user_mutex); - - // user helpers - std::shared_ptr get_user_for_id(const std::string& user_id) REQUIRES(m_user_mutex); - void user_data_updated(const std::string& user_id) REQUIRES(m_user_mutex); - void log_out(const std::shared_ptr& user, SyncUser::State new_state, - util::UniqueFunction)>&& completion) REQUIRES(!m_route_mutex); -}; - -// MARK: Provider client templates -template <> -App::UsernamePasswordProviderClient App::provider_client(); -template <> -App::UserAPIKeyProviderClient App::provider_client(); - -} // namespace app -} // namespace realm - -#endif /* REALM_APP_HPP */ diff --git a/src/realm/object-store/sync/app_config.hpp b/src/realm/object-store/sync/app_config.hpp deleted file mode 100644 index 387b08cd3b5..00000000000 --- a/src/realm/object-store/sync/app_config.hpp +++ /dev/null @@ -1,113 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_SYNC_APP_CONFIG_HPP -#define REALM_OS_SYNC_APP_CONFIG_HPP - -#include -#include -#include -#include -#include - -namespace realm { -struct SyncClientTimeouts { - SyncClientTimeouts(); - // See sync::Client::Config for the meaning of these fields. - uint64_t connect_timeout; - uint64_t connection_linger_time; - uint64_t ping_keepalive_period; - uint64_t pong_keepalive_timeout; - uint64_t fast_reconnect_limit; - // Used for requesting location metadata at startup and reconnecting sync connections. - // NOTE: delay_jitter_divisor is not configurable - sync::ResumptionDelayInfo reconnect_backoff_info; -}; - -struct SyncClientConfig { - using LoggerFactory = std::function(util::Logger::Level)>; - LoggerFactory logger_factory; - util::Logger::Level log_level = util::Logger::Level::info; - ReconnectMode reconnect_mode = ReconnectMode::normal; // For internal sync-client testing only! -#if REALM_DISABLE_SYNC_MULTIPLEXING - bool multiplex_sessions = false; -#else - bool multiplex_sessions = true; -#endif - - // The SyncSocket instance used by the Sync Client for event synchronization - // and creating WebSockets. If not provided the default implementation will be used. - std::shared_ptr socket_provider; - - // Optional thread observer for event loop thread events in the default SyncSocketProvider - // implementation. It is not used for custom SyncSocketProvider implementations. - std::shared_ptr default_socket_provider_thread_observer; - - // {@ - // Optional information about the binding/application that is sent as part of the User-Agent - // when establishing a connection to the server. These values are only used by the default - // SyncSocket implementation. Custom SyncSocket implementations must update the User-Agent - // directly, if supported by the platform APIs. - std::string user_agent_binding_info; - std::string user_agent_application_info; - // @} - - SyncClientTimeouts timeouts; -}; - -namespace app { -struct AppConfig { - // Information about the device where the app is running - struct DeviceInfo { - std::string platform_version; // json: platformVersion - std::string sdk_version; // json: sdkVersion - std::string sdk; // json: sdk - std::string device_name; // json: deviceName - std::string device_version; // json: deviceVersion - std::string framework_name; // json: frameworkName - std::string framework_version; // json: frameworkVersion - std::string bundle_id; // json: bundleId - }; - - std::string app_id; - std::shared_ptr transport; - std::optional base_url; - std::optional default_request_timeout_ms; - DeviceInfo device_info; - - std::string base_file_path; - SyncClientConfig sync_client_config; - - enum class MetadataMode { - NoEncryption, // Enable metadata, but disable encryption. - Encryption, // Enable metadata, and use encryption (automatic if possible). - InMemory, // Do not persist metadata - }; - MetadataMode metadata_mode = MetadataMode::Encryption; - std::optional> custom_encryption_key; - // If non-empty, mode is Encryption, and no key is explicitly set, the - // automatically generated key is stored in the keychain using this access - // group. Must be set when the metadata Realm is stored in an access group - // and shared between apps. Not applicable on non-Apple platforms. - std::string security_access_group; -}; - -} // namespace app -} // namespace realm - -#endif // REALM_OS_SYNC_APP_CONFIG_HPP diff --git a/src/realm/object-store/sync/app_credentials.cpp b/src/realm/object-store/sync/app_credentials.cpp deleted file mode 100644 index a438f38f55e..00000000000 --- a/src/realm/object-store/sync/app_credentials.cpp +++ /dev/null @@ -1,196 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include - -namespace realm::app { - -std::string const kAppProviderKey = "provider"; - -IdentityProvider const IdentityProviderAnonymous = "anon-user"; -IdentityProvider const IdentityProviderGoogle = "oauth2-google"; -IdentityProvider const IdentityProviderFacebook = "oauth2-facebook"; -IdentityProvider const IdentityProviderApple = "oauth2-apple"; -IdentityProvider const IdentityProviderUsernamePassword = "local-userpass"; -IdentityProvider const IdentityProviderCustom = "custom-token"; -IdentityProvider const IdentityProviderFunction = "custom-function"; -IdentityProvider const IdentityProviderAPIKey = "api-key"; - -IdentityProvider provider_type_from_enum(AuthProvider provider) -{ - switch (provider) { - case AuthProvider::ANONYMOUS: - case AuthProvider::ANONYMOUS_NO_REUSE: - return IdentityProviderAnonymous; - case AuthProvider::APPLE: - return IdentityProviderApple; - case AuthProvider::FACEBOOK: - return IdentityProviderFacebook; - case AuthProvider::GOOGLE: - return IdentityProviderGoogle; - case AuthProvider::CUSTOM: - return IdentityProviderCustom; - case AuthProvider::USERNAME_PASSWORD: - return IdentityProviderUsernamePassword; - case AuthProvider::FUNCTION: - return IdentityProviderFunction; - case AuthProvider::API_KEY: - return IdentityProviderAPIKey; - } - throw InvalidArgument("unknown provider type in provider_type_from_enum"); -} - -AuthProvider enum_from_provider_type(const IdentityProvider& provider) -{ - if (provider == IdentityProviderAnonymous) { - return AuthProvider::ANONYMOUS; - } - else if (provider == IdentityProviderApple) { - return AuthProvider::APPLE; - } - else if (provider == IdentityProviderFacebook) { - return AuthProvider::FACEBOOK; - } - else if (provider == IdentityProviderGoogle) { - return AuthProvider::GOOGLE; - } - else if (provider == IdentityProviderCustom) { - return AuthProvider::CUSTOM; - } - else if (provider == IdentityProviderUsernamePassword) { - return AuthProvider::USERNAME_PASSWORD; - } - else if (provider == IdentityProviderFunction) { - return AuthProvider::FUNCTION; - } - else if (provider == IdentityProviderAPIKey) { - return AuthProvider::API_KEY; - } - else { - REALM_UNREACHABLE(); - } -} - -AppCredentials::AppCredentials(AuthProvider provider, std::unique_ptr payload) - : m_provider(provider) - , m_payload(std::move(payload)) -{ -} - -AppCredentials::AppCredentials(AuthProvider provider, - std::initializer_list> values) - : m_provider(provider) - , m_payload(std::make_unique()) -{ - (*m_payload)[kAppProviderKey] = provider_type_from_enum(provider); - for (auto& [key, value] : values) { - (*m_payload)[key] = std::move(value); - } -} - -AuthProvider AppCredentials::provider() const -{ - return m_provider; -} - -std::string AppCredentials::provider_as_string() const -{ - return provider_type_from_enum(m_provider); -} - -bson::BsonDocument AppCredentials::serialize_as_bson() const -{ - return *m_payload; -} - -std::string AppCredentials::serialize_as_json() const -{ - return bson::Bson(*m_payload).to_string(); -} - -AppCredentials AppCredentials::anonymous(bool reuse_credentials) -{ - return reuse_credentials ? AppCredentials(AuthProvider::ANONYMOUS, {}) - : AppCredentials(AuthProvider::ANONYMOUS_NO_REUSE, {}); -} - -AppCredentials AppCredentials::apple(AppCredentialsToken id_token) -{ - return AppCredentials(AuthProvider::APPLE, {{"id_token", id_token}}); -} - -AppCredentials AppCredentials::facebook(AppCredentialsToken access_token) -{ - return AppCredentials(AuthProvider::FACEBOOK, {{"accessToken", access_token}}); -} - -AppCredentials AppCredentials::google(AuthCode&& auth_token) -{ - return AppCredentials(AuthProvider::GOOGLE, {{"authCode", bson::Bson(auth_token)}}); -} - -AppCredentials AppCredentials::google(IdToken&& id_token) -{ - return AppCredentials(AuthProvider::GOOGLE, {{"id_token", bson::Bson(id_token)}}); -} - -AppCredentials AppCredentials::custom(AppCredentialsToken token) -{ - return AppCredentials(AuthProvider::CUSTOM, {{"token", token}}); -} - -AppCredentials AppCredentials::username_password(std::string username, std::string password) -{ - return AppCredentials(AuthProvider::USERNAME_PASSWORD, {{"username", username}, {"password", password}}); -} - -AppCredentials AppCredentials::function(const bson::BsonDocument& payload) -{ - return AppCredentials(AuthProvider::FUNCTION, std::make_unique(payload)); -} - -AppCredentials AppCredentials::function(const std::string& serialized_payload) -{ - return AppCredentials( - AuthProvider::FUNCTION, - std::make_unique(static_cast(bson::parse(serialized_payload)))); -} - - -AppCredentials AppCredentials::api_key(std::string api_key) -{ - return AppCredentials(AuthProvider::API_KEY, {{"key", api_key}}); -} - -AppCredentials::AppCredentials(const AppCredentials& credentials) - : m_provider(credentials.m_provider) - , m_payload(std::make_unique(*credentials.m_payload)) -{ -} - -AppCredentials& AppCredentials::operator=(const AppCredentials& credentials) -{ - if (&credentials != this) { - m_payload = std::make_unique(*credentials.m_payload); - m_provider = credentials.m_provider; - } - return *this; -} - -} // namespace realm::app diff --git a/src/realm/object-store/sync/app_credentials.hpp b/src/realm/object-store/sync/app_credentials.hpp deleted file mode 100644 index 5f2d4344e82..00000000000 --- a/src/realm/object-store/sync/app_credentials.hpp +++ /dev/null @@ -1,142 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_APP_CREDENTIALS_HPP -#define REALM_APP_CREDENTIALS_HPP - -#include - -#include -#include - -namespace realm { -namespace bson { -class Bson; -class BsonDocument; -} // namespace bson -namespace app { - -typedef std::string IdentityProvider; -typedef std::string AppCredentialsToken; - -using AuthCode = util::TaggedString; -using IdToken = util::TaggedString; - -// The username/password identity provider. User accounts are handled by the Realm Object Server directly without the -// involvement of a third-party identity provider. -extern IdentityProvider const IdentityProviderUsernamePassword; - -// A Facebook account as an identity provider. -extern IdentityProvider const IdentityProviderFacebook; - -// A Google account as an identity provider. -extern IdentityProvider const IdentityProviderGoogle; - -// A JSON Web Token as an identity provider. -extern IdentityProvider const IdentityProviderCustom; - -// An Anonymous account as an identity provider. -extern IdentityProvider const IdentityProviderAnonymous; - -// A Google account as an identity provider. -extern IdentityProvider const IdentityProviderApple; - -// The `FunctionCredential` can be used to log in using -// the [Function Authentication Provider](https://docs.mongodb.com/stitch/authentication/custom-function/). -extern IdentityProvider const IdentityProviderFunction; - -// A credential which can be used to log in as a Stitch user -// using the Server's API Key authentication provider. -extern IdentityProvider const IdentityProviderAPIKey; - -enum class AuthProvider { - ANONYMOUS, - ANONYMOUS_NO_REUSE, - FACEBOOK, - GOOGLE, - APPLE, - CUSTOM, - USERNAME_PASSWORD, - FUNCTION, - API_KEY, -}; - -IdentityProvider provider_type_from_enum(AuthProvider provider); - -AuthProvider enum_from_provider_type(const IdentityProvider& provider); - -// Opaque credentials representing a specific Realm Object Server user. -struct AppCredentials { - // Construct and return credentials from a Facebook account token. - static AppCredentials facebook(const AppCredentialsToken access_token); - - // Construct and return anonymous credentials - static AppCredentials anonymous(bool reuse_anonymous_credentials = true); - - // Construct and return credentials from an Apple account token. - static AppCredentials apple(const AppCredentialsToken id_token); - - // Construct and return credentials from a google account token. - static AppCredentials google(AuthCode&& id_token); - - // Construct and return credentials from a google account token. - static AppCredentials google(IdToken&& id_token); - - // Construct and return credentials from a jwt token. - static AppCredentials custom(const AppCredentialsToken token); - - // Construct and return credentials from a username and password. - static AppCredentials username_password(std::string username, std::string password); - - // Construct and return credentials with the payload. - // The payload is a MongoDB document as json - static AppCredentials function(const bson::BsonDocument& payload); - - // Construct and return credentials with the payload. - // The payload is a MongoDB document as json - static AppCredentials function(const std::string& serialized_payload); - - // Construct and return credentials with the api key. - static AppCredentials api_key(std::string api_key); - - // The provider of the credential - AuthProvider provider() const; - std::string provider_as_string() const; - - // The serialized payload - bson::BsonDocument serialize_as_bson() const; - std::string serialize_as_json() const; - - AppCredentials() = default; - AppCredentials(const AppCredentials&); - AppCredentials(AppCredentials&&) = default; - AppCredentials& operator=(AppCredentials const&); - AppCredentials& operator=(AppCredentials&&) = default; - -private: - AppCredentials(AuthProvider provider, std::unique_ptr payload); - AppCredentials(AuthProvider provider, std::initializer_list>); - // The name of the identity provider which generated the credentials token. - AuthProvider m_provider; - std::unique_ptr m_payload; -}; - -} // namespace app -} // namespace realm - -#endif /* REALM_APP_CREDENTIALS_HPP */ diff --git a/src/realm/object-store/sync/app_service_client.hpp b/src/realm/object-store/sync/app_service_client.hpp deleted file mode 100644 index 922e9286435..00000000000 --- a/src/realm/object-store/sync/app_service_client.hpp +++ /dev/null @@ -1,100 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef APP_SERVICE_CLIENT_HPP -#define APP_SERVICE_CLIENT_HPP - -#include -#include -#include - -#include - -namespace realm { -namespace app { -class User; -struct AppError; - -/// A class providing the core functionality necessary to make authenticated -/// function call requests for a particular Stitch service. -class AppServiceClient { -public: - virtual ~AppServiceClient() = default; - - /// Calls the Realm Cloud function with the provided name and arguments. - /// @param user The sync user to perform this request. - /// @param name The name of the Realm Cloud function to be called. - /// @param args_ejson The arguments array to be provided to the function encoded as an ejson string. - /// @param service_name The name of the service, this is optional. - /// @param completion Returns the result from the intended call, will return an Optional AppError is an - /// error is thrown and ejson-encoded reply if successful. The reply string will be a null pointer only in - /// the case of error. Using a string* rather than optional to avoid copying a potentially large - /// string. - virtual void - call_function(const std::shared_ptr& user, const std::string& name, std::string_view args_ejson, - const util::Optional& service_name, - util::UniqueFunction)>&& completion) = 0; - - /// Calls the Realm Cloud function with the provided name and arguments. - /// @param user The sync user to perform this request. - /// @param name The name of the Realm Cloud function to be called. - /// @param args_bson The `BSONArray` of arguments to be provided to the function. - /// @param service_name The name of the service, this is optional. - /// @param completion Returns the result from the intended call, will return an Optional AppError is an - /// error is thrown and bson if successful - virtual void call_function( - const std::shared_ptr& user, const std::string& name, const bson::BsonArray& args_bson, - const util::Optional& service_name, - util::UniqueFunction&&, util::Optional)>&& completion) = 0; - - /// Calls the Realm Cloud function with the provided name and arguments. - /// @param user The sync user to perform this request. - /// @param name The name of the Realm Cloud function to be called. - /// @param args_bson The `BSONArray` of arguments to be provided to the function. - /// @param completion Returns the result from the intended call, will return an Optional AppError is an - /// error is thrown and bson if successful - virtual void call_function( - const std::shared_ptr& user, const std::string& name, const bson::BsonArray& args_bson, - util::UniqueFunction&&, util::Optional)>&& completion) = 0; - - /// Calls the Realm Cloud function with the provided name and arguments. - /// This will use the current logged in user to perform the request - /// @param name The name of the Realm Cloud function to be called. - /// @param args_bson The `BSONArray` of arguments to be provided to the function. - /// @param service_name The name of the service, this is optional. - /// @param completion Returns the result from the intended call, will return an Optional AppError is an - /// error is thrown and bson if successful - virtual void call_function( - const std::string& name, const bson::BsonArray& args_bson, const util::Optional& service_name, - util::UniqueFunction&&, util::Optional)>&& completion) = 0; - - /// Calls the Realm Cloud function with the provided name and arguments. - /// This will use the current logged in user to perform the request - /// @param name The name of the Realm Cloud function to be called. - /// @param args_bson The `BSONArray` of arguments to be provided to the function. - /// @param completion Returns the result from the intended call, will return an Optional AppError is an - /// error is thrown and bson if successful - virtual void call_function( - const std::string& name, const bson::BsonArray& args_bson, - util::UniqueFunction&&, util::Optional)>&& completion) = 0; -}; - -} // namespace app -} // namespace realm - -#endif /* APP_SERVICE_CLIENT_HPP */ diff --git a/src/realm/object-store/sync/app_user.cpp b/src/realm/object-store/sync/app_user.cpp deleted file mode 100644 index ddc3df215c8..00000000000 --- a/src/realm/object-store/sync/app_user.cpp +++ /dev/null @@ -1,304 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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 - -#include -#include -#include -#include -#include -#include -#include - -namespace realm::app { - -UserIdentity::UserIdentity(const std::string& id, const std::string& provider_type) - : id(id) - , provider_type(provider_type) -{ -} - -User::User(Private, std::shared_ptr app, std::string_view user_id) - : m_app(std::move(app)) - , m_app_id(m_app->app_id()) - , m_user_id(user_id) -{ - REALM_ASSERT(m_app); - m_app->register_sync_user(*this); -} - -User::~User() -{ - if (m_app) { - m_app->unregister_sync_user(*this); - } -} - -std::string User::user_id() const noexcept -{ - return m_user_id; -} - -std::string User::app_id() const noexcept -{ - return m_app_id; -} - -std::vector User::legacy_identities() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_data.legacy_identities; -} - -std::string User::access_token() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_data.access_token.token; -} - -std::string User::refresh_token() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_data.refresh_token.token; -} - -SyncUser::State User::state() const -{ - util::CheckedLockGuard lock(m_mutex); - if (!m_app) - return SyncUser::State::Removed; - return m_data.access_token ? SyncUser::State::LoggedIn : SyncUser::State::LoggedOut; -} - -bool User::is_anonymous() const -{ - util::CheckedLockGuard lock(m_mutex); - return do_is_anonymous(); -} - -bool User::do_is_anonymous() const -{ - return m_data.access_token && m_data.identities.size() == 1 && - m_data.identities[0].provider_type == app::IdentityProviderAnonymous; -} - -std::string User::device_id() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_data.device_id; -} - -bool User::has_device_id() const -{ - // The server will sometimes send us an all-zero device ID as a way to - // explicitly signal that it did not generate a device ID for this login. - util::CheckedLockGuard lock(m_mutex); - return !m_data.device_id.empty() && m_data.device_id != "000000000000000000000000"; -} - -UserProfile User::user_profile() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_data.profile; -} - -std::vector User::identities() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_data.identities; -} - -std::optional User::custom_data() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_data.access_token.user_data; -} - -std::shared_ptr User::app() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_app; -} - -SyncManager* User::sync_manager() -{ - util::CheckedLockGuard lock(m_mutex); - return m_app ? m_app->sync_manager().get() : nullptr; -} - -app::MongoClient User::mongo_client(const std::string& service_name) -{ - util::CheckedLockGuard lock(m_mutex); - return app::MongoClient(shared_from_this(), m_app->app_service_client(), service_name); -} - -bool User::access_token_refresh_required() const -{ - using namespace std::chrono; - constexpr size_t buffer_seconds = 5; // arbitrary - util::CheckedLockGuard lock(m_mutex); - const auto now = duration_cast(system_clock::now().time_since_epoch()).count() + - m_seconds_to_adjust_time_for_testing.load(std::memory_order_relaxed); - const auto threshold = now - buffer_seconds; - return m_data.access_token && m_data.access_token.expires_at < static_cast(threshold); -} - -void User::log_out() -{ - if (auto app = this->app()) { - app->log_out(shared_from_this(), nullptr); - } -} - -void User::detach_and_tear_down() -{ - std::shared_ptr app; - { - util::CheckedLockGuard lk(m_mutex); - m_data.access_token.token.clear(); - m_data.refresh_token.token.clear(); - app = std::exchange(m_app, nullptr); - } - - if (app) { - app->sync_manager()->update_sessions_for(*this, SyncUser::State::LoggedIn, SyncUser::State::Removed, {}); - app->unregister_sync_user(*this); - } -} - -void User::update_data_for_testing(util::FunctionRef fn) -{ - UserData data; - { - util::CheckedLockGuard lock(m_mutex); - data = m_data; - } - fn(data); - update_backing_data(std::move(data)); -} - -void User::update_backing_data(std::optional&& data) -{ - if (!data) { - detach_and_tear_down(); - emit_change_to_subscribers(*this); - return; - } - - std::string new_token; - SyncUser::State old_state; - SyncUser::State new_state = data->access_token ? SyncUser::State::LoggedIn : SyncUser::State::LoggedOut; - std::shared_ptr sync_manager; - { - util::CheckedLockGuard lock(m_mutex); - if (!m_app) { - return; // is already detached - } - sync_manager = m_app->sync_manager(); - old_state = m_data.access_token ? SyncUser::State::LoggedIn : SyncUser::State::LoggedOut; - if (new_state == SyncUser::State::LoggedIn && data->access_token != m_data.access_token) - new_token = data->access_token.token; - m_data = std::move(*data); - } - - sync_manager->update_sessions_for(*this, old_state, new_state, new_token); - emit_change_to_subscribers(*this); -} - -void User::request_log_out() -{ - if (auto app = this->app()) { - auto new_state = is_anonymous() ? SyncUser::State::Removed : SyncUser::State::LoggedOut; - app->m_metadata_store->log_out(m_user_id, new_state); - update_backing_data(app->m_metadata_store->get_user(m_user_id)); - } -} - -void User::request_refresh_location(util::UniqueFunction)>&& completion) -{ - if (auto app = this->app()) { - bool update_location = true; - app->refresh_access_token(shared_from_this(), update_location, std::move(completion)); - } -} - -void User::request_access_token(util::UniqueFunction)>&& completion) -{ - if (auto app = this->app()) { - bool update_location = false; - app->refresh_access_token(shared_from_this(), update_location, std::move(completion)); - } -} - -void User::track_realm(std::string_view path) -{ - if (auto app = this->app()) { - app->m_metadata_store->add_realm_path(m_user_id, path); - } -} - -std::string User::create_file_action(SyncFileAction action, std::string_view original_path, - std::optional requested_recovery_dir) -{ - if (auto app = this->app()) { - std::string recovery_path; - if (action == SyncFileAction::BackUpThenDeleteRealm) { - recovery_path = - util::reserve_unique_file_name(app->m_file_manager->recovery_directory_path(requested_recovery_dir), - util::create_timestamped_template("recovered_realm")); - } - app->m_metadata_store->create_file_action(action, original_path, recovery_path); - return recovery_path; - } - return ""; -} - -void User::refresh_custom_data(util::UniqueFunction)> completion_block) - REQUIRES(!m_mutex) -{ - refresh_custom_data(false, std::move(completion_block)); -} - -void User::refresh_custom_data(bool update_location, - util::UniqueFunction)> completion_block) -{ - if (auto app = this->app()) { - app->refresh_custom_data(shared_from_this(), update_location, std::move(completion_block)); - return; - } - completion_block(app::AppError( - ErrorCodes::ClientUserNotFound, - util::format("Cannot initiate a refresh on user '%1' because the user has been removed", m_user_id))); -} - -std::string User::path_for_realm(const SyncConfig& config, std::optional custom_file_name) const -{ - if (auto app = this->app()) { - return app->m_file_manager->path_for_realm(config, std::move(custom_file_name)); - } - return ""; -} -} // namespace realm::app - -namespace std { -size_t hash::operator()(const realm::app::UserIdentity& k) const -{ - return ((hash()(k.id) ^ (hash()(k.provider_type) << 1)) >> 1); -} -} // namespace std diff --git a/src/realm/object-store/sync/app_user.hpp b/src/realm/object-store/sync/app_user.hpp deleted file mode 100644 index 1eec2783eb3..00000000000 --- a/src/realm/object-store/sync/app_user.hpp +++ /dev/null @@ -1,266 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_APP_USER_HPP -#define REALM_OS_APP_USER_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace realm { -struct SyncConfig; -} - -namespace realm::app { -class App; -struct AppError; -class MetadataStore; -class MongoClient; - -struct UserProfile { - // The full name of the user. - std::optional name() const - { - return get_field("name"); - } - // The email address of the user. - std::optional email() const - { - return get_field("email"); - } - // A URL to the user's profile picture. - std::optional picture_url() const - { - return get_field("picture_url"); - } - // The first name of the user. - std::optional first_name() const - { - return get_field("first_name"); - } - // The last name of the user. - std::optional last_name() const - { - return get_field("last_name"); - } - // The gender of the user. - std::optional gender() const - { - return get_field("gender"); - } - // The birthdate of the user. - std::optional birthday() const - { - return get_field("birthday"); - } - // The minimum age of the user. - std::optional min_age() const - { - return get_field("min_age"); - } - // The maximum age of the user. - std::optional max_age() const - { - return get_field("max_age"); - } - - bson::Bson operator[](const std::string& key) const - { - return m_data.at(key); - } - - const bson::BsonDocument& data() const - { - return m_data; - } - - UserProfile(bson::BsonDocument&& data) - : m_data(std::move(data)) - { - } - UserProfile() = default; - -private: - bson::BsonDocument m_data; - - std::optional get_field(const char* name) const - { - if (auto val = m_data.find(name)) { - return static_cast((*val)); - } - return util::none; - } -}; - -// A struct that represents an identity that a `User` is linked to -struct UserIdentity { - // the id of the identity - std::string id; - // the associated provider type of the identity - std::string provider_type; - - UserIdentity(const std::string& id, const std::string& provider_type); - - bool operator==(const UserIdentity& other) const - { - return id == other.id && provider_type == other.provider_type; - } - - bool operator!=(const UserIdentity& other) const - { - return id != other.id || provider_type != other.provider_type; - } -}; - -struct UserData { - // Current refresh token or empty if user is logged out - RealmJWT refresh_token; - // Current access token or empty if user is logged out - RealmJWT access_token; - // UUIDs which used to be used to generate local Realm file paths. Now only - // used to locate existing files. - std::vector legacy_identities; - // Identities which were used to log into this user - std::vector identities; - // Id for the device which this user was logged in on. Users are not - // portable between devices so this cannot be changed after the user - // is created - std::string device_id; - // Server-stored user profile - UserProfile profile; -}; - -class User final : public SyncUser, public std::enable_shared_from_this, public Subscribable { - struct Private {}; - -public: - // ------------------------------------------------------------------------ - // SyncUser implementation - - std::string user_id() const noexcept override; - std::string app_id() const noexcept override; - std::vector legacy_identities() const override REQUIRES(!m_mutex); - - std::string access_token() const override REQUIRES(!m_mutex); - std::string refresh_token() const override REQUIRES(!m_mutex); - SyncUser::State state() const override REQUIRES(!m_mutex); - - /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns - /// true. - bool access_token_refresh_required() const override REQUIRES(!m_mutex); - - SyncManager* sync_manager() override REQUIRES(!m_mutex); - void request_log_out() override REQUIRES(!m_mutex); - void request_refresh_location(util::UniqueFunction)>&&) override - REQUIRES(!m_mutex); - void request_access_token(util::UniqueFunction)>&&) override REQUIRES(!m_mutex); - - void track_realm(std::string_view path) override REQUIRES(!m_mutex); - std::string create_file_action(SyncFileAction action, std::string_view original_path, - std::optional requested_recovery_dir) override REQUIRES(!m_mutex); - - // ------------------------------------------------------------------------ - // SDK public API - - /// Returns true if the user's only identity is anonymous. - bool is_anonymous() const REQUIRES(!m_mutex); - - std::string device_id() const REQUIRES(!m_mutex); - bool has_device_id() const REQUIRES(!m_mutex); - UserProfile user_profile() const REQUIRES(!m_mutex); - std::vector identities() const REQUIRES(!m_mutex); - - // Custom user data embedded in the access token. - std::optional custom_data() const REQUIRES(!m_mutex); - - // Get the app instance that this user belongs to. - std::shared_ptr app() const REQUIRES(!m_mutex); - - /// Retrieves a general-purpose service client for the Realm Cloud service - /// @param service_name The name of the cluster - app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex); - - // Log the user out and mark it as such. This will also close its associated Sessions. - void log_out() REQUIRES(!m_mutex); - - // Get the default path for a Realm for the given configuration. - // The default value is `///.realm`. - // If the file cannot be created at this location, for example due to path length restrictions, - // this function may pass back `/.realm` - std::string path_for_realm(const SyncConfig& config, - std::optional custom_file_name = std::nullopt) const REQUIRES(!m_mutex); - - // ------------------------------------------------------------------------ - // All of the following are called by `RealmMetadataStore` and are public only for - // testing purposes. SDKs should not call these directly in non-test code - // or expose them in the public API. - - static std::shared_ptr make(std::shared_ptr app, std::string_view user_id) - { - return std::make_shared(Private(), std::move(app), user_id); - } - - User(Private, std::shared_ptr app, std::string_view user_id); - ~User(); - - void update_backing_data(std::optional&& data) REQUIRES(!m_mutex); - void update_data_for_testing(util::FunctionRef) REQUIRES(!m_mutex); - void detach_and_tear_down() REQUIRES(!m_mutex); - - /// Refreshes the custom data for this user - /// If `update_location` is true, the location metadata will be queried before the request - void refresh_custom_data(bool update_location, - util::UniqueFunction)> completion_block) - REQUIRES(!m_mutex); - void refresh_custom_data(util::UniqueFunction)> completion_block) - REQUIRES(!m_mutex); - - // Hook for testing access token timeouts - void set_seconds_to_adjust_time_for_testing(int seconds) - { - m_seconds_to_adjust_time_for_testing.store(seconds); - } - -private: - util::CheckedMutex m_mutex; - std::shared_ptr m_app GUARDED_BY(m_mutex); - const std::string m_app_id; - const std::string m_user_id; - UserData m_data GUARDED_BY(m_mutex); - std::atomic m_seconds_to_adjust_time_for_testing = 0; - - bool do_is_anonymous() const REQUIRES(m_mutex); -}; - -} // namespace realm::app - -namespace std { -template <> -struct hash { - size_t operator()(realm::app::UserIdentity const&) const; -}; -} // namespace std - -#endif // REALM_OS_SYNC_USER_HPP diff --git a/src/realm/object-store/sync/app_utils.cpp b/src/realm/object-store/sync/app_utils.cpp deleted file mode 100644 index 2226496ea87..00000000000 --- a/src/realm/object-store/sync/app_utils.cpp +++ /dev/null @@ -1,182 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -#include -#include -#include - -#include - -#include - -namespace realm::app { - -const std::pair* -AppUtils::find_header(const std::string& key_name, const std::map& search_map) -{ - for (auto&& current : search_map) { -#ifdef _MSC_VER - if (key_name.size() == current.first.size() && _stricmp(key_name.c_str(), current.first.c_str()) == 0) { - return ¤t; - } -#else - if (key_name.size() == current.first.size() && strcasecmp(key_name.c_str(), current.first.c_str()) == 0) { - return ¤t; - } -#endif - } - return nullptr; -} - -bool AppUtils::is_success_status_code(int status_code) -{ - return status_code == 0 || (status_code < 300 && status_code >= 200); -} - -bool AppUtils::is_redirect_status_code(int status_code) -{ - using namespace realm::sync; - // If the response contains a redirection, then return true - if (auto code = HTTPStatus(status_code); - code == HTTPStatus::MovedPermanently || code == HTTPStatus::PermanentRedirect) { - return true; - } - return false; -} - -std::optional AppUtils::extract_redir_location(const std::map& headers) -{ - // Look for case insensitive redirect "location" in headers - auto location = AppUtils::find_header("location", headers); - if (location && !location->second.empty() && util::Uri::try_parse(location->second).is_ok()) { - // If the location is valid, return it wholesale (e.g., it could include a path for API proxies) - return location->second; - } - return std::nullopt; -} - -// Create a Response object with the given client error, message and optional http status code -Response AppUtils::make_clienterror_response(ErrorCodes::Error code, const std::string_view message, - std::optional http_status) -{ - return Response{http_status ? *http_status : 0, 0, {}, std::string(message), code}; -} - -#if REALM_APP_SERVICES -std::optional AppUtils::check_for_errors(const Response& response) -{ - std::string error_msg; - bool http_status_code_is_fatal = !AppUtils::is_success_status_code(response.http_status_code); - - try { - auto ct = find_header("content-type", response.headers); - if (ct && ct->second == "application/json" && !response.body.empty()) { - auto body = nlohmann::json::parse(response.body); - auto message = body.find("error"); - auto link = body.find("link"); - std::string parsed_link = link == body.end() ? "" : link->get(); - - if (auto error_code = body.find("error_code"); - error_code != body.end() && !error_code->get().empty()) { - auto server_error = error_code->get(); - auto code = ErrorCodes::from_string(server_error); - auto error_stg = message != body.end() ? message->get() : "no error message"; - // If the err_code is not found or not an app error, create a generic AppError with - // ErrorCodes::AppServerError "error_code" value from server response will be in the `server_error` - // property - if (code == ErrorCodes::UnknownError || - !ErrorCodes::error_categories(code).test(ErrorCategory::app_error)) { - code = ErrorCodes::AppServerError; - } - return AppError(code, std::move(error_stg), std::move(parsed_link), response.http_status_code, - std::move(server_error)); - } - // If the response only contains an error string, create a generic AppError with - // ErrorCodes::AppUnknownError - else if (message != body.end()) { - return AppError(ErrorCodes::AppUnknownError, message->get(), std::move(parsed_link), - response.http_status_code); - } - } - } - catch (const std::exception&) { - // ignore parse errors from our attempt to read the error from json - } - - if (response.client_error_code) { - error_msg = response.body.empty() ? "client error code value considered fatal" : response.body; - return AppError(*(response.client_error_code), error_msg, {}, response.http_status_code); - } - - if (response.custom_status_code != 0) { - error_msg = response.body.empty() ? "non-zero custom status code considered fatal" : response.body; - return AppError(ErrorCodes::CustomError, error_msg, {}, response.custom_status_code); - } - - if (http_status_code_is_fatal) { - error_msg = response.body.empty() ? "http error code considered fatal" - : "http error code considered fatal: " + response.body; - return AppError(ErrorCodes::HTTPError, error_msg, {}, response.http_status_code); - } - - return std::nullopt; -} - -// Convert an AppError object into a Response object -Response AppUtils::make_apperror_response(const AppError& error) -{ - if (!error.server_error.empty() || error.code() == ErrorCodes::AppUnknownError) { - auto body = nlohmann::json(); - body["error"] = error.reason(); - if (!error.server_error.empty()) { - body["error_code"] = error.server_error; - } - if (!error.link_to_server_logs.empty()) { - body["link"] = error.link_to_server_logs; - } - return {error.additional_status_code.value_or(0), 0, {{"content-type", "application/json"}}, body.dump()}; - } - - if (ErrorCodes::error_categories(error.code()).test(ErrorCategory::http_error)) { - std::string message; - // Try to extract the original body from the reason code - static const char* match = "http error code considered fatal: "; - if (auto pos = error.reason().find(match); pos != std::string::npos) { - message = error.reason().substr(pos + std::char_traits::length(match)); - // Remove the text added by AppError - pos = message.find_last_of("."); - if (pos != std::string::npos) { - message.erase(pos); - } - } - // Otherwise, body was originally empty - return {error.additional_status_code.value_or(0), 0, {}, message}; - } - if (ErrorCodes::error_categories(error.code()).test(ErrorCategory::custom_error)) { - return {0, error.additional_status_code.value_or(0), {}, std::string(error.reason())}; - } - - // For other cases, put the error code in client_error_code field (client error or otherwise) - return {error.additional_status_code.value_or(0), 0, {}, std::string(error.reason()), error.code()}; -} - -#endif // REALM_APP_SERVICES - -} // namespace realm::app diff --git a/src/realm/object-store/sync/app_utils.hpp b/src/realm/object-store/sync/app_utils.hpp deleted file mode 100644 index 2f6d1e4f666..00000000000 --- a/src/realm/object-store/sync/app_utils.hpp +++ /dev/null @@ -1,47 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef APP_UTILS_HPP -#define APP_UTILS_HPP - -#include -#include -#include - -#include - -namespace realm::app { -struct AppError; -struct Response; - -class AppUtils { -public: - static std::optional check_for_errors(const Response& response); - static Response make_apperror_response(const AppError& error); - static Response make_clienterror_response(ErrorCodes::Error code, const std::string_view message, - std::optional http_status); - static const std::pair* - find_header(const std::string& key_name, const std::map& search_map); - static bool is_success_status_code(int status_code); - static bool is_redirect_status_code(int status_code); - static std::optional extract_redir_location(const std::map& headers); -}; - -} // namespace realm::app - -#endif /* APP_UTILS_HPP */ diff --git a/src/realm/object-store/sync/async_open_task.cpp b/src/realm/object-store/sync/async_open_task.cpp deleted file mode 100644 index 8d40682ff9a..00000000000 --- a/src/realm/object-store/sync/async_open_task.cpp +++ /dev/null @@ -1,245 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// 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 - -#include -#include -#include -#include -#include -#include - -namespace realm { - -AsyncOpenTask::AsyncOpenTask(Private, std::shared_ptr<_impl::RealmCoordinator> coordinator, - std::shared_ptr session, bool db_first_open) - : m_coordinator(coordinator) - , m_session(session) - , m_db_first_open(db_first_open) -{ -} - -void AsyncOpenTask::start(AsyncOpenCallback callback) -{ - util::CheckedUniqueLock lock(m_mutex); - if (!m_session) - return; - auto session = m_session; - lock.unlock(); - - std::shared_ptr self(shared_from_this()); - session->wait_for_download_completion([callback = std::move(callback), self, this](Status status) mutable { - std::shared_ptr<_impl::RealmCoordinator> coordinator; - { - util::CheckedLockGuard lock(m_mutex); - if (!m_session) - return; // Swallow all events if the task has been cancelled. - - // Hold on to the coordinator until after we've called the callback - coordinator = std::move(m_coordinator); - } - - if (!status.is_ok()) { - self->async_open_complete(std::move(callback), coordinator, status); - return; - } - - self->migrate_schema_or_complete(std::move(callback), coordinator); - }); - session->revive_if_needed(); -} - -util::Future AsyncOpenTask::start() -{ - auto pf = util::make_promise_future(); - start([promise = std::move(pf.promise)](ThreadSafeReference&& ref, std::exception_ptr e) mutable { - if (e) { - try { - std::rethrow_exception(e); - } - catch (...) { - promise.set_error(exception_to_status()); - } - } - else { - promise.emplace_value(std::move(ref)); - } - }); - return std::move(pf.future); -} - -void AsyncOpenTask::cancel() -{ - std::shared_ptr session; - { - util::CheckedLockGuard lock(m_mutex); - if (!m_session) - return; - - for (auto token : m_registered_callbacks) { - m_session->unregister_progress_notifier(token); - } - - session = std::move(m_session); - m_coordinator = nullptr; - } - - // We need to release the mutex before we log the session out as that will invoke the - // wait_for_download_completion callback which will also attempt to acquire the mutex - // thus deadlocking. - if (session) { - // Does a better way exists for canceling the download? - session->force_close(); - } -} - -uint64_t AsyncOpenTask::register_download_progress_notifier(std::function&& callback) -{ - util::CheckedLockGuard lock(m_mutex); - if (m_session) { - auto token = m_session->register_progress_notifier(std::move(callback), - SyncSession::ProgressDirection::download, true); - m_registered_callbacks.emplace_back(token); - return token; - } - return 0; -} - -void AsyncOpenTask::unregister_download_progress_notifier(uint64_t token) -{ - util::CheckedLockGuard lock(m_mutex); - if (m_session) - m_session->unregister_progress_notifier(token); -} - -void AsyncOpenTask::wait_for_bootstrap_or_complete(AsyncOpenCallback&& callback, - std::shared_ptr<_impl::RealmCoordinator> coordinator, - Status status) -{ - if (!status.is_ok()) { - async_open_complete(std::move(callback), coordinator, status); - return; - } - - auto config = coordinator->get_config(); - // FlX sync is not used so there is nothing to bootstrap. - if (!config.sync_config || !config.sync_config->flx_sync_requested) { - async_open_complete(std::move(callback), coordinator, status); - return; - } - - SharedRealm shared_realm; - try { - shared_realm = coordinator->get_realm(util::Scheduler::make_dummy(), m_db_first_open); - } - catch (...) { - async_open_complete(std::move(callback), coordinator, exception_to_status()); - return; - } - const auto subscription_set = shared_realm->get_latest_subscription_set(); - const auto sub_state = subscription_set.state(); - - if (sub_state != sync::SubscriptionSet::State::Complete) { - // We need to wait until subscription initializer completes - std::shared_ptr self(shared_from_this()); - subscription_set.get_state_change_notification(sync::SubscriptionSet::State::Complete) - .get_async([self, coordinator, callback = std::move(callback)]( - StatusWith state) mutable { - self->async_open_complete(std::move(callback), coordinator, state.get_status()); - }); - } - else { - async_open_complete(std::move(callback), coordinator, Status::OK()); - } -} - -void AsyncOpenTask::async_open_complete(AsyncOpenCallback&& callback, - std::shared_ptr<_impl::RealmCoordinator> coordinator, Status status) -{ - { - util::CheckedLockGuard lock(m_mutex); - // 'Cancel' may have been called just before 'async_open_complete' is invoked. - if (!m_session) - return; - - for (auto token : m_registered_callbacks) { - m_session->unregister_progress_notifier(token); - } - m_session = nullptr; - } - - if (status.is_ok()) { - ThreadSafeReference realm; - try { - realm = coordinator->get_unbound_realm(); - } - catch (...) { - return callback({}, std::current_exception()); - } - return callback(std::move(realm), nullptr); - } - return callback({}, std::make_exception_ptr(Exception(status))); -} - -void AsyncOpenTask::migrate_schema_or_complete(AsyncOpenCallback&& callback, - std::shared_ptr<_impl::RealmCoordinator> coordinator) -{ - util::CheckedUniqueLock lock(m_mutex); - if (!m_session) - return; - auto session = m_session; - lock.unlock(); - - auto pending_migration = [&] { - auto rt = coordinator->begin_read(); - return _impl::sync_schema_migration::has_pending_migration(*rt); - }(); - - if (!pending_migration) { - wait_for_bootstrap_or_complete(std::move(callback), coordinator, Status::OK()); - return; - } - - // Migrate the schema. - // * First upload the changes at the old schema version - // * Then, pause the session, delete all tables, re-initialize the metadata, and finally restart the session. - // The lifetime of the task is extended until the bootstrap completes. - std::shared_ptr self(shared_from_this()); - session->wait_for_upload_completion( - [callback = std::move(callback), coordinator, session, self, this](Status status) mutable { - { - util::CheckedLockGuard lock(m_mutex); - if (!m_session) - return; // Swallow all events if the task has been cancelled. - } - - if (!status.is_ok()) { - self->async_open_complete(std::move(callback), coordinator, status); - return; - } - - auto migration_completed_callback = [callback = std::move(callback), coordinator = std::move(coordinator), - self](Status status) mutable { - self->wait_for_bootstrap_or_complete(std::move(callback), coordinator, status); - }; - SyncSession::Internal::migrate_schema(*session, std::move(migration_completed_callback)); - }); -} - -} // namespace realm diff --git a/src/realm/object-store/sync/async_open_task.hpp b/src/realm/object-store/sync/async_open_task.hpp deleted file mode 100644 index bca23e68d2b..00000000000 --- a/src/realm/object-store/sync/async_open_task.hpp +++ /dev/null @@ -1,93 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef ASYNC_OPEN_TASK_HPP -#define ASYNC_OPEN_TASK_HPP - -#include -#include -#include - -#include -#include - -namespace realm { -class Realm; -class SyncSession; -class ThreadSafeReference; -class Status; -namespace _impl { -class RealmCoordinator; -} - -// Class used to wrap the intent of opening a new Realm or fully synchronize it before returning it to the user -// Timeouts are not handled by this class but must be handled by each binding. -class AsyncOpenTask : public std::enable_shared_from_this { - struct Private {}; - -public: - using AsyncOpenCallback = util::UniqueFunction; - using SubscriptionCallback = util::UniqueFunction)>; - - explicit AsyncOpenTask(Private, std::shared_ptr<_impl::RealmCoordinator> coordinator, - std::shared_ptr session, bool db_open_for_the_first_time); - AsyncOpenTask(const AsyncOpenTask&) = delete; - AsyncOpenTask& operator=(const AsyncOpenTask&) = delete; - - // Starts downloading the Realm. The callback will be triggered either when the download completes - // or an error is encountered. - // - // If multiple AsyncOpenTasks all attempt to download the same Realm and one of them is canceled, - // the other tasks will receive a "Cancelled" exception. - void start(AsyncOpenCallback callback) REQUIRES(!m_mutex); - - // Starts downloading the Realm. The future will be fulfilled either when the download completes - // or an error is encountered. - // - // If multiple AsyncOpenTasks all attempt to download the same Realm and one of them is canceled, - // the other tasks will receive a cancelled Status - util::Future start() REQUIRES(!m_mutex); - - // Cancels the download and stops the session. No further functions should be called on this class. - void cancel() REQUIRES(!m_mutex); - - using ProgressNotifierCallback = void(uint64_t transferred_bytes, uint64_t transferrable_bytes, - double progress_estimate); - uint64_t register_download_progress_notifier(std::function&& callback) - REQUIRES(!m_mutex); - void unregister_download_progress_notifier(uint64_t token) REQUIRES(!m_mutex); - -private: - friend _impl::RealmCoordinator; - - void async_open_complete(AsyncOpenCallback&&, std::shared_ptr<_impl::RealmCoordinator>, Status) - REQUIRES(!m_mutex); - void migrate_schema_or_complete(AsyncOpenCallback&&, std::shared_ptr<_impl::RealmCoordinator>) REQUIRES(!m_mutex); - void wait_for_bootstrap_or_complete(AsyncOpenCallback&&, std::shared_ptr<_impl::RealmCoordinator>, Status) - REQUIRES(!m_mutex); - - std::shared_ptr<_impl::RealmCoordinator> m_coordinator GUARDED_BY(m_mutex); - std::shared_ptr m_session GUARDED_BY(m_mutex); - std::vector m_registered_callbacks GUARDED_BY(m_mutex); - util::CheckedMutex m_mutex; - const bool m_db_first_open; -}; - -} // namespace realm - -#endif // // ASYNC_OPEN_TASK_HPP diff --git a/src/realm/object-store/sync/auth_request_client.hpp b/src/realm/object-store/sync/auth_request_client.hpp deleted file mode 100644 index 58cbd905d75..00000000000 --- a/src/realm/object-store/sync/auth_request_client.hpp +++ /dev/null @@ -1,41 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_AUTH_REQUEST_CLIENT_HPP -#define REALM_OS_AUTH_REQUEST_CLIENT_HPP - -#include - -namespace realm::app { -class User; -struct Response; - -class AuthRequestClient { -public: - virtual ~AuthRequestClient() = default; - - virtual std::string url_for_path(const std::string& path) const = 0; - - virtual void do_authenticated_request(HttpMethod, std::string&& route, std::string&& body, - const std::shared_ptr& user, RequestTokenType, - util::UniqueFunction&&) = 0; -}; - -} // namespace realm::app - -#endif /* REALM_OS_AUTH_REQUEST_CLIENT_HPP */ diff --git a/src/realm/object-store/sync/generic_network_transport.cpp b/src/realm/object-store/sync/generic_network_transport.cpp deleted file mode 100644 index 982fb5680e3..00000000000 --- a/src/realm/object-store/sync/generic_network_transport.cpp +++ /dev/null @@ -1,82 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -#include - -namespace realm::app { - -namespace { -std::string http_message(const std::string& prefix, int code) -{ - if (code >= 100 && code < 200) { - return util::format("%1. Informational: %2", prefix, code); - } - else if (code >= 200 && code < 300) { - return util::format("%1. Success: %2", prefix, code); - } - else if (code >= 300 && code < 400) { - return util::format("%1. Redirection: %2", prefix, code); - } - else if (code >= 400 && code < 500) { - return util::format("%1. Client Error: %2", prefix, code); - } - else if (code >= 500 && code < 600) { - return util::format("%1. Server Error: %2", prefix, code); - } - return util::format("%1. Unknown HTTP Error: %2", prefix, code); -} -} // anonymous namespace - -std::ostream& operator<<(std::ostream& os, HttpMethod method) -{ - switch (method) { - case HttpMethod::get: - return os << "GET"; - case HttpMethod::post: - return os << "POST"; - case HttpMethod::patch: - return os << "PATCH"; - case HttpMethod::put: - return os << "PUT"; - case HttpMethod::del: - return os << "DEL"; - } - return os << "UNKNOWN"; -} - -AppError::AppError(ErrorCodes::Error ec, std::string message, std::string link, - std::optional additional_error_code, std::optional server_err) - : Exception(ec, ec == ErrorCodes::HTTPError && additional_error_code - ? http_message(message, *additional_error_code) - : message) - , additional_status_code(additional_error_code) - , link_to_server_logs(link) - , server_error(server_err ? *server_err : "") -{ - // For these errors, the server_error string is empty - REALM_ASSERT(ErrorCodes::error_categories(ec).test(ErrorCategory::app_error)); -} - -std::ostream& operator<<(std::ostream& os, AppError error) -{ - return os << error.server_error << ": " << error.what(); -} - -} // namespace realm::app diff --git a/src/realm/object-store/sync/generic_network_transport.hpp b/src/realm/object-store/sync/generic_network_transport.hpp deleted file mode 100644 index f7d209c2fab..00000000000 --- a/src/realm/object-store/sync/generic_network_transport.hpp +++ /dev/null @@ -1,161 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_GENERIC_NETWORK_TRANSPORT_HPP -#define REALM_GENERIC_NETWORK_TRANSPORT_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace realm::app { - -struct AppError : public Exception { - std::optional additional_status_code; - - std::string link_to_server_logs; - std::string server_error; - - AppError(ErrorCodes::Error ec, std::string message, std::string link = "", - std::optional additional_error_code = std::nullopt, - std::optional server_err = std::nullopt); - - bool is_json_error() const - { - return ErrorCodes::error_categories(code()).test(ErrorCategory::json_error); - } - - bool is_service_error() const - { - return ErrorCodes::error_categories(code()).test(ErrorCategory::service_error); - } - - bool is_http_error() const - { - return ErrorCodes::error_categories(code()).test(ErrorCategory::http_error); - } - - bool is_custom_error() const - { - return ErrorCodes::error_categories(code()).test(ErrorCategory::custom_error); - } - - bool is_client_error() const - { - return ErrorCodes::error_categories(code()).test(ErrorCategory::client_error); - } - - bool is_websocket_error() const - { - return ErrorCodes::error_categories(code()).test(ErrorCategory::websocket_error); - } -}; - -std::ostream& operator<<(std::ostream& os, AppError error); - -/** - * An HTTP method type. - */ -enum class HttpMethod { get, post, patch, put, del }; -std::ostream& operator<<(std::ostream&, HttpMethod); - -/** - * Request/Response headers type - */ -using HttpHeaders = std::map; - -/** - * An HTTP request that can be made to an arbitrary server. - */ -struct Request { - /** - * The HTTP method of this request. - */ - HttpMethod method = HttpMethod::get; - - /** - * The URL to which this request will be made. - */ - std::string url; - - /** - * The number of milliseconds that the underlying transport should spend on an HTTP round trip before failing with - * an error. - */ - uint64_t timeout_ms = 0; - - /** - * The HTTP headers of this request - keys are case insensitive. - */ - HttpHeaders headers; - - /** - * The body of the request. - */ - std::string body; -}; - -/// What type of auth token should be used for a HTTP request. -enum class RequestTokenType { NoAuth, AccessToken, RefreshToken }; - -/** - * The contents of an HTTP response. - */ -struct Response { - /** - * The status code of the HTTP response. - */ - int http_status_code; - - /** - * A custom status code provided by the language binding (SDK). - */ - int custom_status_code; - - /** - * The headers of the HTTP response - keys are case insensitive. - */ - HttpHeaders headers; - - /** - * The body of the HTTP response. - */ - std::string body; - - /** - * An error code used by the client to report http processing errors. - */ - util::Optional client_error_code; -}; - -/// Generic network transport for foreign interfaces. -struct GenericNetworkTransport { - virtual ~GenericNetworkTransport() = default; - virtual void send_request_to_server(const Request& request, - util::UniqueFunction&& completion) = 0; -}; - -} // namespace realm::app - -#endif /* REALM_GENERIC_NETWORK_TRANSPORT_HPP */ diff --git a/src/realm/object-store/sync/impl/app_metadata.cpp b/src/realm/object-store/sync/impl/app_metadata.cpp deleted file mode 100644 index 2ccb4295027..00000000000 --- a/src/realm/object-store/sync/impl/app_metadata.cpp +++ /dev/null @@ -1,1027 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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 - -#include -#include -#include -#include -#include -#include -#include -#include - -#if REALM_PLATFORM_APPLE -#include -#endif - -#include -#include -#include - -using namespace realm; -using realm::app::UserData; - -template <> -inline SyncUser::State Obj::get(ColKey ck) const -{ - return static_cast(get(ck)); -} - -namespace { - -struct CurrentUserSchema { - TableKey table_key; - ColKey user_id; - - static constexpr const char* table_name = "current_user_identity"; - - void read(Realm& realm) - { - auto object_schema = realm.schema().find(table_name); - table_key = object_schema->table_key; - user_id = object_schema->persisted_properties[0].column_key; - } - - static ObjectSchema object_schema() - { - return {table_name, {{table_name, PropertyType::String}}}; - } -}; - -struct UserIdentitySchema { - TableKey table_key; - ColKey user_id; - ColKey provider_id; - - static constexpr const char* table_name = "UserIdentity"; - - void read(Realm& realm) - { - auto object_schema = realm.schema().find(table_name); - table_key = object_schema->table_key; - user_id = object_schema->persisted_properties[0].column_key; - provider_id = object_schema->persisted_properties[1].column_key; - } - - static ObjectSchema object_schema() - { - return {table_name, - ObjectSchema::ObjectType::Embedded, - { - {"id", PropertyType::String}, - {"provider_type", PropertyType::String}, - }}; - } -}; - -struct SyncUserSchema { - TableKey table_key; - - // The server-supplied user_id for the user. Unique per server instance. - ColKey user_id_col; - // Locally generated UUIDs for the user. These are tracked to be able - // to open pre-existing Realm files, but are no longer generated or - // used for anything else. - ColKey legacy_uuids_col; - // The cached refresh token for this user. - ColKey refresh_token_col; - // The cached access token for this user. - ColKey access_token_col; - // The identities for this user. - ColKey identities_col; - // The current state of this user. - ColKey state_col; - // The device id of this user. - ColKey device_id_col; - // Any additional profile attributes, formatted as a bson string. - ColKey profile_dump_col; - // The set of absolute file paths to Realms belonging to this user. - ColKey realm_file_paths_col; - - static constexpr const char* table_name = "UserMetadata"; - - void read(Realm& realm) - { - auto object_schema = realm.schema().find(table_name); - table_key = object_schema->table_key; - user_id_col = object_schema->persisted_properties[0].column_key; - legacy_uuids_col = object_schema->persisted_properties[1].column_key; - refresh_token_col = object_schema->persisted_properties[2].column_key; - access_token_col = object_schema->persisted_properties[3].column_key; - identities_col = object_schema->persisted_properties[4].column_key; - state_col = object_schema->persisted_properties[5].column_key; - device_id_col = object_schema->persisted_properties[6].column_key; - profile_dump_col = object_schema->persisted_properties[7].column_key; - realm_file_paths_col = object_schema->persisted_properties[8].column_key; - } - - static ObjectSchema object_schema() - { - return {table_name, - {{"identity", PropertyType::String}, - {"legacy_uuids", PropertyType::String | PropertyType::Array}, - {"refresh_token", PropertyType::String | PropertyType::Nullable}, - {"access_token", PropertyType::String | PropertyType::Nullable}, - {"identities", PropertyType::Object | PropertyType::Array, UserIdentitySchema::table_name}, - {"state", PropertyType::Int}, - {"device_id", PropertyType::String}, - {"profile_data", PropertyType::String}, - {"local_realm_paths", PropertyType::Set | PropertyType::String}}}; - } -}; - -struct FileActionSchema { - TableKey table_key; - - // The original path on disk of the file (generally, the main file for an on-disk Realm). - ColKey idx_original_name; - // A new path on disk for a file to be written to. Context-dependent. - ColKey idx_new_name; - // An enum describing the action to take. - ColKey idx_action; - // The partition key of the Realm. - ColKey idx_partition; - // The user_id of the user to whom the file action applies (despite the internal column name). - ColKey idx_user_identity; - - static constexpr const char* table_name = "FileActionMetadata"; - - void read(Realm& realm) - { - auto object_schema = realm.schema().find(table_name); - table_key = object_schema->table_key; - idx_original_name = object_schema->persisted_properties[0].column_key; - idx_new_name = object_schema->persisted_properties[1].column_key; - idx_action = object_schema->persisted_properties[2].column_key; - idx_partition = object_schema->persisted_properties[3].column_key; - idx_user_identity = object_schema->persisted_properties[4].column_key; - } - - static ObjectSchema object_schema() - { - return {table_name, - { - {"original_name", PropertyType::String, Property::IsPrimary{true}}, - {"new_name", PropertyType::String | PropertyType::Nullable}, - {"action", PropertyType::Int}, - {"url", PropertyType::String}, // actually partition key - {"identity", PropertyType::String}, // actually user id - }}; - } -}; - -void migrate_to_v7(std::shared_ptr old_realm, std::shared_ptr realm) -{ - // Before schema version 7 there may have been multiple UserMetadata entries - // for a single user_id with different provider types, so we need to merge - // any duplicates together - - SyncUserSchema schema; - schema.read(*realm); - - TableRef table = realm->read_group().get_table(schema.table_key); - TableRef old_table = ObjectStore::table_for_object_type(old_realm->read_group(), SyncUserSchema::table_name); - if (table->is_empty()) - return; - REALM_ASSERT(table->size() == old_table->size()); - - ColKey old_uuid_col = old_table->get_column_key("local_uuid"); - - std::unordered_map users; - for (size_t i = 0, j = 0; i < table->size(); ++j) { - auto obj = table->get_object(i); - - // Move the local uuid from the old column to the list - auto old_obj = old_table->get_object(j); - obj.get_list(schema.legacy_uuids_col).add(old_obj.get(old_uuid_col)); - - // Check if we've already seen an object with the same id. If not, store - // this one and move on - std::string user_id = obj.get(schema.user_id_col); - auto& existing = users[obj.get(schema.user_id_col)]; - if (!existing.is_valid()) { - existing = obj; - ++i; - continue; - } - - // We have a second object for the same id, so we need to merge them. - // First we merge the state: if one is logged in and the other isn't, - // we'll use the logged-in state and tokens. If both are logged in, we'll - // use the more recent login. If one is logged out and the other is - // removed we'll use the logged out state. If both are logged out or - // both are removed then it doesn't matter which we pick. - using State = SyncUser::State; - auto state = obj.get(schema.state_col); - auto existing_state = State(existing.get(schema.state_col)); - if (state == existing_state) { - if (state == State::LoggedIn) { - RealmJWT token_1(existing.get(schema.access_token_col)); - RealmJWT token_2(obj.get(schema.access_token_col)); - if (token_1.issued_at < token_2.issued_at) { - existing.set(schema.refresh_token_col, obj.get(schema.refresh_token_col)); - existing.set(schema.access_token_col, obj.get(schema.access_token_col)); - } - } - } - else if (state == State::LoggedIn || existing_state == State::Removed) { - existing.set(schema.state_col, int64_t(state)); - existing.set(schema.refresh_token_col, obj.get(schema.refresh_token_col)); - existing.set(schema.access_token_col, obj.get(schema.access_token_col)); - } - - // Next we merge the list properties (identities, legacy uuids, realm file paths) - { - auto dest = existing.get_linklist(schema.identities_col); - auto src = obj.get_linklist(schema.identities_col); - for (size_t i = 0, size = src.size(); i < size; ++i) { - if (dest.find_first(src.get(i)) == npos) { - dest.add(src.get(i)); - } - } - } - { - auto dest = existing.get_list(schema.legacy_uuids_col); - auto src = obj.get_list(schema.legacy_uuids_col); - for (size_t i = 0, size = src.size(); i < size; ++i) { - if (dest.find_first(src.get(i)) == npos) { - dest.add(src.get(i)); - } - } - } - { - auto dest = existing.get_set(schema.realm_file_paths_col); - auto src = obj.get_set(schema.realm_file_paths_col); - for (size_t i = 0, size = src.size(); i < size; ++i) { - dest.insert(src.get(i)); - } - } - - // Finally we delete the duplicate object. We don't increment `i` as it's - // now the index of the object just after the one we're deleting. - obj.remove(); - } -} - -std::shared_ptr open_realm(RealmConfig& config, const app::AppConfig& app_config) -{ - bool should_encrypt = app_config.metadata_mode == app::AppConfig::MetadataMode::Encryption; - if (!REALM_PLATFORM_APPLE && should_encrypt && !app_config.custom_encryption_key) - throw InvalidArgument("Metadata Realm encryption was specified, but no encryption key was provided."); - - if (app_config.custom_encryption_key && should_encrypt) - config.encryption_key = *app_config.custom_encryption_key; - if (app_config.custom_encryption_key || !should_encrypt || !REALM_PLATFORM_APPLE) { - config.clear_on_invalid_file = true; - return Realm::get_shared_realm(config); - } - -#if REALM_PLATFORM_APPLE - auto try_get_realm = [&]() -> std::shared_ptr { - try { - return Realm::get_shared_realm(config); - } - catch (const InvalidDatabase&) { - return nullptr; - } - }; - - // First try to open the Realm with a key already stored in the keychain. - // This works for both the case where everything is sensible and valid and - // when we have a key but no metadata Realm. - auto key = keychain::get_existing_metadata_realm_key(app_config.app_id, app_config.security_access_group); - if (key) { - config.encryption_key = *key; - if (auto realm = try_get_realm()) - return realm; - } - - // If we have an existing file and either no key or the key didn't work to - // decrypt it, then we might have an unencrypted metadata Realm resulting - // from a previous run being unable to access the keychain. - if (util::File::exists(config.path)) { - config.encryption_key.clear(); - if (auto realm = try_get_realm()) - return realm; - - // We weren't able to open the existing file with either the stored key - // or no key, so just recreate it - config.clear_on_invalid_file = true; - } - - // We now have no metadata Realm. If we don't have an existing stored key, - // try to create and store a new one. This might fail, in which case we - // just create an unencrypted Realm file. - if (!key) - key = keychain::create_new_metadata_realm_key(app_config.app_id, app_config.security_access_group); - if (key) - config.encryption_key = std::move(*key); - return try_get_realm(); -#else // REALM_PLATFORM_APPLE - REALM_UNREACHABLE(); -#endif // REALM_PLATFORM_APPLE -} - -struct PersistedSyncMetadataManager : public app::MetadataStore { - std::shared_ptr<_impl::RealmCoordinator> m_coordinator; - SyncUserSchema m_user_schema; - FileActionSchema m_file_action_schema; - UserIdentitySchema m_user_identity_schema; - CurrentUserSchema m_current_user_schema; - - using UserState = SyncUser::State; - - PersistedSyncMetadataManager(const app::AppConfig& app_config, SyncFileManager& file_manager) - { - // Note that there are several deferred schema changes which don't - // justify bumping the schema version by themself, but should be done - // the next time something does justify a migration. - // These include: - // - remove FileActionSchema url and identity columns - // - rename current_user_identity to CurrentUserId - // - change most of the nullable columns to non-nullable - constexpr uint64_t SCHEMA_VERSION = 7; - - RealmConfig config; - config.automatic_change_notifications = false; - config.path = file_manager.metadata_path(); - config.schema = Schema{ - UserIdentitySchema::object_schema(), - SyncUserSchema::object_schema(), - FileActionSchema::object_schema(), - CurrentUserSchema::object_schema(), - }; - - config.schema_version = SCHEMA_VERSION; - config.schema_mode = SchemaMode::Automatic; - config.scheduler = util::Scheduler::make_dummy(); - config.automatically_handle_backlinks_in_migrations = true; - config.migration_function = [](std::shared_ptr old_realm, std::shared_ptr realm, Schema&) { - if (old_realm->schema_version() < 7) { - migrate_to_v7(old_realm, realm); - } - }; - - auto realm = open_realm(config, app_config); - m_user_schema.read(*realm); - m_file_action_schema.read(*realm); - m_user_identity_schema.read(*realm); - m_current_user_schema.read(*realm); - - m_coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path); - - // When App::remove_user() is called, we mark the user as "removed" but - // don't actually delete the UserMetadata object or the files on disk - // immediately, and instead defer the actual removal until the next - // launch of the application. This makes it so that we don't have to - // require developers to ensure that all Realms associated with a user - // are closed before removing the user, as that can be a difficult thing - // to do. - // - // The "next launch" in a multiprocess scenario can be a somewhat - // complicated concept. If one process calls remove_user(), exits, and - // then is restarted, it still isn't safe to delete the user's files yet - // if another process has also been running the whole time. Instead, we - // need to wait for *all* of the processes which share a metadata Realm - // to exit, and perform the cleanup actions only when one is launching - // in a fresh state with no other processes running (but also we need to - // work if multiple processes launch at once). - // - // The lock file management code already solves this problem - using a - // mix of exclusive and shared locks on the lock file, opening a Realm - // will reinitialize the lock file from scratch only if it is the - // "session initiator" and no one already has the file open. We therefore - // can detect the scenario where we want to perform launch actions by - // using a flag in the lock file: we begin a frozen read transaction, - // attempt to atomically set the flag, and if we were able we proceeed - // to perform the launch actions present in that frozen version. - // - // In the simple scenario of an unshared metadata Realm which is only - // ever accessed by a single process, this all behaves identically to - // the naive solution of always processing all launch actions on launch. - // If the metadata Realm was already open in another process, the flag - // will already be set and we'll skip performing launch actions. If two - // processes open the metadata Realm at once we get to the complicated - // scenario: one of them will successfully set the flag and the other - // will fail. The one which failed may then go on to perform writes on - // the metadata Realm, possibly creating new launch actions. This is why - // we need the frozen read transaction created *before* trying to set - // the flag. The process which does successfully set the flag will only - // process launch actions present in that frozen read, and thus only ones - // which already existing before it set the flag. Any new actions created - // by the second process won't be visible and will wait for the next - // launch. - // - // User cleanup is made slightly more complicated by that users can be - // logged back in after being removed. To handle this, we only delete - // users (and their files) if the user is Removed in both the frozen - // transaction and inside the write transaction. If a second process - // logs the user back in between when we start the frozen read and when - // we acquire the write lock we'll skip it, and if it tries to log in - // after we acquire the write lock it'll be blocked by that and only - // log in after we have completed cleanup. - // - // This scheme is only fully safe if synchronized Realms are only ever - // opened using a non-Removed user, and not by using the fake sync history - // mode where no user is provided. That mode is hopefully only ever used - // by Realm Studio. - // - // To avoid a lockfile format change, the flag used happens to be the - // sync agent flag, which is otherwise unused for the metadata Realm - // (which is a local unsynchronized Realm). The use of this flag should - // not be confused for there actually being a sync agent for the - // metadata Realm. - auto frozen = realm->freeze(); - if (m_coordinator->try_claim_sync_agent()) { - realm->begin_transaction(); - perform_file_actions(frozen->read_group(), realm->read_group(), file_manager); - remove_dead_users(frozen->read_group(), realm->read_group(), file_manager); - realm->commit_transaction(); - } - } - - std::shared_ptr get_realm() const - { - return m_coordinator->get_realm(util::Scheduler::make_dummy()); - } - - void for_each_obj(Group& frozen, Group& live, TableKey tk, util::FunctionRef fn) - { - auto frozen_table = frozen.get_table(tk); - if (frozen_table->is_empty()) - return; - - // We want to iterate the objects present in both the before and after - // realms. Any other objects are either no longer relevant or too new. - TableRef table = live.get_table(tk); - for (auto frozen_obj : *frozen_table) { - if (auto obj = table->get_object(frozen_obj.get_key())) { - fn(frozen_obj, obj); - } - } - } - - void remove_dead_users(Group& frozen, Group& live, SyncFileManager& file_manager) - { - auto& schema = m_user_schema; - for_each_obj(frozen, live, schema.table_key, [&](Obj& frozen_obj, Obj& live_obj) { - // The frozen object being removed but not the live object means that - // another process logged the user back in. The live object being - // removed but not the frozen one means that the removal happened - // after we acquired the sync agent, and so we shouldn't process - // the user yet. - if (frozen_obj.get(schema.state_col) != UserState::Removed) - return; - if (live_obj.get(schema.state_col) != UserState::Removed) - return; - delete_user_realms(file_manager, live_obj); - }); - } - - void delete_user_realms(SyncFileManager& file_manager, Obj& obj) - { - Set paths = obj.get_set(m_user_schema.realm_file_paths_col); - bool any_failed = false; - for (auto path : paths) { - if (!file_manager.remove_realm(path)) - any_failed = true; - } - try { - file_manager.remove_user_realms(obj.get(m_user_schema.user_id_col)); - } - catch (FileAccessError const&) { - any_failed = true; - } - - // Only remove the object if all of the tracked realms no longer exist, - // and otherwise try again to delete them on the next launch - if (!any_failed) { - obj.remove(); - } - } - - bool perform_file_action(SyncFileManager& file_manager, Obj& obj) - { - auto& schema = m_file_action_schema; - switch (static_cast(obj.get(schema.idx_action))) { - case SyncFileAction::DeleteRealm: - // Delete all the files for the given Realm. - return file_manager.remove_realm(obj.get(schema.idx_original_name)); - - case SyncFileAction::BackUpThenDeleteRealm: - // Copy the primary Realm file to the recovery dir, and then delete the Realm. - auto new_name = obj.get(schema.idx_new_name); - auto original_name = obj.get(schema.idx_original_name); - if (!util::File::exists(original_name)) { - // The Realm file doesn't exist anymore, which is fine - return true; - } - - if (new_name && file_manager.copy_realm_file(original_name, new_name)) { - // We successfully copied the Realm file to the recovery directory. - bool did_remove = file_manager.remove_realm(original_name); - // if the copy succeeded but not the delete, then running BackupThenDelete - // a second time would fail, so change this action to just delete the original file. - if (did_remove) { - return true; - } - obj.set(schema.idx_action, static_cast(SyncFileAction::DeleteRealm)); - } - } - return false; - } - - void perform_file_actions(Group& frozen, Group& live, SyncFileManager& file_manager) - { - for_each_obj(frozen, live, m_file_action_schema.table_key, [&](Obj&, Obj& obj) { - if (perform_file_action(file_manager, obj)) - obj.remove(); - }); - } - - bool immediately_run_file_actions(SyncFileManager& file_manager, std::string_view realm_path) override - { - auto realm = get_realm(); - realm->begin_transaction(); - TableRef table = realm->read_group().get_table(m_file_action_schema.table_key); - auto key = table->where().equal(m_file_action_schema.idx_original_name, StringData(realm_path)).find(); - if (!key) { - return false; - } - auto obj = table->get_object(key); - bool did_run = perform_file_action(file_manager, obj); - if (did_run) - obj.remove(); - realm->commit_transaction(); - return did_run; - } - - bool has_logged_in_user(std::string_view user_id) override - { - auto realm = get_realm(); - auto obj = find_user(*realm, user_id); - return is_valid_user(obj); - } - - std::optional get_user(std::string_view user_id) override - { - auto realm = get_realm(); - return read_user(find_user(*realm, user_id)); - } - - void create_user(std::string_view user_id, std::string_view refresh_token, std::string_view access_token, - std::string_view device_id) override - { - auto realm = get_realm(); - realm->begin_transaction(); - - auto& schema = m_user_schema; - Obj obj = find_user(*realm, user_id); - if (!obj) { - obj = realm->read_group().get_table(m_user_schema.table_key)->create_object(); - obj.set(schema.user_id_col, user_id); - - // Mark the user we just created as the current user - Obj current_user = current_user_obj(*realm); - current_user.set(m_current_user_schema.user_id, user_id); - } - - obj.set(schema.state_col, (int64_t)UserState::LoggedIn); - obj.set(schema.refresh_token_col, refresh_token); - obj.set(schema.access_token_col, access_token); - obj.set(schema.device_id_col, device_id); - - realm->commit_transaction(); - } - - void update_user(std::string_view user_id, util::FunctionRef update_fn) override - { - auto realm = get_realm(); - realm->begin_transaction(); - auto& schema = m_user_schema; - Obj obj = find_user(*realm, user_id); - auto opt_data = read_user(obj); - if (!opt_data) { - realm->cancel_transaction(); - return; - } - - auto& data = *opt_data; - update_fn(data); - - obj.set(schema.state_col, int64_t(data.access_token ? UserState::LoggedIn : UserState::LoggedOut)); - obj.set(schema.refresh_token_col, data.refresh_token.token); - obj.set(schema.access_token_col, data.access_token.token); - obj.set(schema.device_id_col, data.device_id); - - std::stringstream profile; - profile << data.profile.data(); - obj.set(schema.profile_dump_col, profile.str()); - - auto identities_list = obj.get_linklist(schema.identities_col); - identities_list.clear(); - - for (auto& ident : data.identities) { - auto obj = identities_list.create_and_insert_linked_object(identities_list.size()); - obj.set(m_user_identity_schema.user_id, ident.id); - obj.set(m_user_identity_schema.provider_id, ident.provider_type); - } - - // intentionally does not update `legacy_identities` as that field is - // read-only and no longer used - - realm->commit_transaction(); - } - - Obj current_user_obj(Realm& realm) const - { - TableRef current_user_table = realm.read_group().get_table(m_current_user_schema.table_key); - Obj obj; - if (!current_user_table->is_empty()) - obj = *current_user_table->begin(); - else if (realm.is_in_transaction()) - obj = current_user_table->create_object(); - return obj; - } - - // Some of our string columns are nullable. They never should actually be - // null as we store "" rather than null when the value isn't present, but - // be safe and handle it anyway. - static std::string get_string(const Obj& obj, ColKey col) - { - auto str = obj.get(col); - return str.is_null() ? "" : str; - } - - std::optional read_user(const Obj& obj) const - { - if (!obj) { - return {}; - } - auto state = obj.get(m_user_schema.state_col); - if (state == UserState::Removed) { - return {}; - } - - UserData data; - if (state == UserState::LoggedIn) { - try { - data.access_token = RealmJWT(get_string(obj, m_user_schema.access_token_col)); - data.refresh_token = RealmJWT(get_string(obj, m_user_schema.refresh_token_col)); - } - catch (...) { - // Invalid stored token results in a logged-out user - data.access_token = {}; - data.refresh_token = {}; - } - } - - data.device_id = get_string(obj, m_user_schema.device_id_col); - if (auto profile = obj.get(m_user_schema.profile_dump_col); profile.size()) { - data.profile = static_cast(bson::parse(std::string_view(profile))); - } - - auto identities_list = obj.get_linklist(m_user_schema.identities_col); - auto identities_table = identities_list.get_target_table(); - data.identities.reserve(identities_list.size()); - for (size_t i = 0, size = identities_list.size(); i < size; ++i) { - auto obj = identities_table->get_object(identities_list.get(i)); - data.identities.push_back({obj.get(m_user_identity_schema.user_id), - obj.get(m_user_identity_schema.provider_id)}); - } - - auto legacy_identities = obj.get_list(m_user_schema.legacy_uuids_col); - data.legacy_identities.reserve(legacy_identities.size()); - for (size_t i = 0, size = legacy_identities.size(); i < size; ++i) { - data.legacy_identities.push_back(legacy_identities.get(i)); - } - - return data; - } - - void update_current_user(Realm& realm, std::string_view removed_user_id) - { - auto current_user = current_user_obj(realm); - if (current_user.get(m_current_user_schema.user_id) == removed_user_id) { - // Set to either empty or the first still logged in user - current_user.set(m_current_user_schema.user_id, get_current_user()); - } - } - - void log_out(std::string_view user_id, UserState new_state) override - { - REALM_ASSERT(new_state != UserState::LoggedIn); - auto realm = get_realm(); - realm->begin_transaction(); - if (auto obj = find_user(*realm, user_id)) { - obj.set(m_user_schema.state_col, (int64_t)new_state); - obj.set(m_user_schema.access_token_col, ""); - obj.set(m_user_schema.refresh_token_col, ""); - update_current_user(*realm, user_id); - } - realm->commit_transaction(); - } - - void delete_user(SyncFileManager& file_manager, std::string_view user_id) override - { - auto realm = get_realm(); - realm->begin_transaction(); - if (auto obj = find_user(*realm, user_id)) { - delete_user_realms(file_manager, obj); // also removes obj - update_current_user(*realm, user_id); - } - realm->commit_transaction(); - } - - void add_realm_path(std::string_view user_id, std::string_view path) override - { - auto realm = get_realm(); - realm->begin_transaction(); - if (auto obj = find_user(*realm, user_id)) { - obj.get_set(m_user_schema.realm_file_paths_col).insert(path); - } - realm->commit_transaction(); - } - - bool is_valid_user(Obj& obj) - { - // This is overly cautious and merely checking the state should suffice, - // but because this is a persisted file that can be modified it's possible - // to get invalid combinations of data. - return obj && obj.get(m_user_schema.state_col) == UserState::LoggedIn && - RealmJWT::validate(get_string(obj, m_user_schema.access_token_col)) && - RealmJWT::validate(get_string(obj, m_user_schema.refresh_token_col)); - } - - std::vector get_all_users() override - { - auto realm = get_realm(); - auto table = realm->read_group().get_table(m_user_schema.table_key); - std::vector users; - users.reserve(table->size()); - for (auto& obj : *table) { - if (obj.get(m_user_schema.state_col) != UserState::Removed) { - users.emplace_back(obj.get(m_user_schema.user_id_col)); - } - } - return users; - } - - std::string get_current_user() override - { - auto realm = get_realm(); - if (auto obj = current_user_obj(*realm)) { - auto user_id = obj.get(m_current_user_schema.user_id); - auto user_obj = find_user(*realm, user_id); - if (is_valid_user(user_obj)) { - return user_id; - } - } - - auto table = realm->read_group().get_table(m_user_schema.table_key); - for (auto& obj : *table) { - if (is_valid_user(obj)) { - return obj.get(m_user_schema.user_id_col); - } - } - - return ""; - } - - void set_current_user(std::string_view user_id) override - { - auto realm = get_realm(); - realm->begin_transaction(); - current_user_obj(*realm).set(m_current_user_schema.user_id, user_id); - realm->commit_transaction(); - } - - void create_file_action(SyncFileAction action, std::string_view original_path, - std::string_view recovery_path) override - { - REALM_ASSERT(action != SyncFileAction::BackUpThenDeleteRealm || !recovery_path.empty()); - - auto realm = get_realm(); - realm->begin_transaction(); - TableRef table = realm->read_group().get_table(m_file_action_schema.table_key); - Obj obj = table->create_object_with_primary_key(original_path); - obj.set(m_file_action_schema.idx_new_name, recovery_path); - obj.set(m_file_action_schema.idx_action, static_cast(action)); - // There's also partition and user_id fields in the schema, but they - // aren't actually used for anything and are never read - realm->commit_transaction(); - } - - Obj find_user(Realm& realm, StringData user_id) const - { - Obj obj; - if (user_id.size() == 0) - return obj; - - auto table = realm.read_group().get_table(m_user_schema.table_key); - Query q = table->where().equal(m_user_schema.user_id_col, user_id); - REALM_ASSERT_DEBUG(q.count() < 2); // user_id_col ought to be a primary key - if (auto key = q.find()) - obj = table->get_object(key); - return obj; - } -}; - -class InMemoryMetadataStorage : public app::MetadataStore { - std::mutex m_mutex; - std::map> m_users; - std::map, std::less<>> m_realm_paths; - std::string m_active_user; - struct FileAction { - SyncFileAction action; - std::string backup_path; - }; - std::map> m_file_actions; - - bool has_logged_in_user(std::string_view user_id) override - { - std::lock_guard lock(m_mutex); - auto it = m_users.find(user_id); - return it != m_users.end() && it->second.access_token; - } - - std::optional get_user(std::string_view user_id) override - { - std::lock_guard lock(m_mutex); - if (auto it = m_users.find(user_id); it != m_users.end()) { - return it->second; - } - return {}; - } - - void create_user(std::string_view user_id, std::string_view refresh_token, std::string_view access_token, - std::string_view device_id) override - { - std::lock_guard lock(m_mutex); - auto it = m_users.find(user_id); - if (it == m_users.end()) { - it = m_users.insert({std::string(user_id), UserData{}}).first; - m_active_user = user_id; - } - auto& user = it->second; - user.device_id = device_id; - try { - user.refresh_token = RealmJWT(refresh_token); - user.access_token = RealmJWT(access_token); - } - catch (...) { - user.refresh_token = {}; - user.access_token = {}; - } - } - - void update_user(std::string_view user_id, util::FunctionRef update_fn) override - { - std::lock_guard lock(m_mutex); - auto it = m_users.find(user_id); - if (it == m_users.end()) { - return; - } - - update_fn(it->second); - it->second.legacy_identities.clear(); - } - - void log_out(std::string_view user_id, SyncUser::State new_state) override - { - std::lock_guard lock(m_mutex); - if (auto it = m_users.find(user_id); it != m_users.end()) { - if (new_state == SyncUser::State::Removed) { - m_users.erase(it); - } - else { - auto& user = it->second; - user.access_token = {}; - user.refresh_token = {}; - user.device_id.clear(); - } - } - } - - void delete_user(SyncFileManager& file_manager, std::string_view user_id) override - { - std::lock_guard lock(m_mutex); - if (auto it = m_users.find(user_id); it != m_users.end()) { - m_users.erase(it); - } - if (auto it = m_realm_paths.find(user_id); it != m_realm_paths.end()) { - for (auto& path : it->second) { - file_manager.remove_realm(path); - } - } - } - - std::string get_current_user() override - { - std::lock_guard lock(m_mutex); - if (auto it = m_users.find(m_active_user); it != m_users.end() && it->second.access_token) { - return m_active_user; - } - - for (auto& [user_id, data] : m_users) { - if (data.access_token) { - m_active_user = user_id; - return user_id; - } - } - - return ""; - } - - void set_current_user(std::string_view user_id) override - { - std::lock_guard lock(m_mutex); - m_active_user = user_id; - } - - std::vector get_all_users() override - { - std::lock_guard lock(m_mutex); - std::vector users; - for (auto& [user_id, _] : m_users) { - users.push_back(user_id); - } - return users; - } - - void add_realm_path(std::string_view user_id, std::string_view path) override - { - std::lock_guard lock(m_mutex); - m_realm_paths[std::string(user_id)].insert(std::string(path)); - } - - bool immediately_run_file_actions(SyncFileManager& file_manager, std::string_view path) override - { - std::lock_guard lock(m_mutex); - auto it = m_file_actions.find(path); - if (it == m_file_actions.end()) - return false; - auto& old_path = it->first; - switch (it->second.action) { - case SyncFileAction::DeleteRealm: - if (file_manager.remove_realm(old_path)) { - m_file_actions.erase(it); - return true; - } - return false; - - case SyncFileAction::BackUpThenDeleteRealm: - if (!util::File::exists(old_path)) { - m_file_actions.erase(it); - return true; - } - auto& new_path = it->second.backup_path; - if (!file_manager.copy_realm_file(old_path, new_path)) { - return false; - } - if (file_manager.remove_realm(old_path)) { - m_file_actions.erase(it); - return true; - } - it->second.action = SyncFileAction::DeleteRealm; - return false; - } - return false; - } - - void create_file_action(SyncFileAction action, std::string_view path, std::string_view backup_path) override - { - std::lock_guard lock(m_mutex); - REALM_ASSERT(action != SyncFileAction::BackUpThenDeleteRealm || !backup_path.empty()); - m_file_actions[std::string(path)] = FileAction{action, std::string(backup_path)}; - } -}; - -} // anonymous namespace - -app::MetadataStore::~MetadataStore() = default; - -std::unique_ptr app::create_metadata_store(const AppConfig& config, SyncFileManager& file_manager) -{ - if (config.metadata_mode == AppConfig::MetadataMode::InMemory) { - return std::make_unique(); - } - return std::make_unique(config, file_manager); -} diff --git a/src/realm/object-store/sync/impl/app_metadata.hpp b/src/realm/object-store/sync/impl/app_metadata.hpp deleted file mode 100644 index 7254370f18f..00000000000 --- a/src/realm/object-store/sync/impl/app_metadata.hpp +++ /dev/null @@ -1,91 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_APP_BACKING_STORE_HPP -#define REALM_OS_APP_BACKING_STORE_HPP - -#include -#include -#include - -#include -#include -#include -#include - -namespace realm { -class SyncFileManager; - -namespace app { -class App; - -class MetadataStore { -public: - virtual ~MetadataStore(); - - // Attempt to perform all pending file actions for the given path. Returns - // true if any were performed. - virtual bool immediately_run_file_actions(SyncFileManager& fm, std::string_view realm_path) = 0; - - virtual void create_file_action(SyncFileAction action, std::string_view original_path, - std::string_view recovery_path) = 0; - - virtual bool has_logged_in_user(std::string_view user_id) = 0; - // Get the user data for the given user if it exists and is not Removed, - // or nullopt otherwise. - virtual std::optional get_user(std::string_view user_id) = 0; - - // Create a user if no user with this id exists, or update only the given - // fields if one does - virtual void create_user(std::string_view user_id, std::string_view refresh_token, std::string_view access_token, - std::string_view device_id) = 0; - - // Update the stored data for an existing user - virtual void update_user(std::string_view user_id, util::FunctionRef) = 0; - - // Discard the given user's tokens and set its state to the given one (LoggedOut or Removed). - // If the user was the active user, a new active user is selected from the - // other logged in users, or set to null if there are none. If the new state - // is Removed, the user and their local Realm files are scheduled for deletion - // on next launch. - virtual void log_out(std::string_view user_id, SyncUser::State new_state) = 0; - // As log_out(user_id, State::Removed), but also attempt to immediately - // delete all of the user's local Realm files and only create file actions - // for ones which cannot be deleted immediately. - virtual void delete_user(SyncFileManager& file_manager, std::string_view user_id) = 0; - - // Get the user_id of the designated active user, or empty string if there - // are none. The active user will always be logged in, and there will always - // be an active user if any users are logged in. - virtual std::string get_current_user() = 0; - // Select the new active user. If the given user_id does not exist or is not - // a logged in user an arbitrary logged-in user will be used instead. - virtual void set_current_user(std::string_view user_id) = 0; - - // Get all non-Removed users, including ones which are currently logged out - virtual std::vector get_all_users() = 0; - - virtual void add_realm_path(std::string_view user_id, std::string_view path) = 0; -}; - -std::unique_ptr create_metadata_store(const AppConfig& config, SyncFileManager& file_manager); - -} // namespace app -} // namespace realm - -#endif // REALM_OS_APP_BACKING_STORE_HPP diff --git a/src/realm/object-store/sync/impl/apple/network_reachability_observer.cpp b/src/realm/object-store/sync/impl/apple/network_reachability_observer.cpp deleted file mode 100644 index 94d35b7259d..00000000000 --- a/src/realm/object-store/sync/impl/apple/network_reachability_observer.cpp +++ /dev/null @@ -1,131 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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 - -#if NETWORK_REACHABILITY_AVAILABLE - -using namespace realm; -using namespace realm::_impl; - -namespace { - -NetworkReachabilityStatus reachability_status_for_flags(SCNetworkReachabilityFlags flags) -{ - if (!(flags & kSCNetworkReachabilityFlagsReachable)) - return NotReachable; - - if (flags & kSCNetworkReachabilityFlagsConnectionRequired) { - if (!(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) || - (flags & kSCNetworkReachabilityFlagsInterventionRequired)) - return NotReachable; - } - - NetworkReachabilityStatus status = ReachableViaWiFi; - -#if TARGET_OS_IPHONE - if (flags & kSCNetworkReachabilityFlagsIsWWAN) - status = ReachableViaWWAN; -#endif - - return status; -} - -} // namespace - -NetworkReachabilityObserver::NetworkReachabilityObserver( - util::Optional hostname, util::UniqueFunction handler) - : m_callback_queue(dispatch_queue_create("io.realm.sync.reachability", DISPATCH_QUEUE_SERIAL)) - , m_change_handler(std::move(handler)) -{ - if (hostname) { - m_reachability_ref = util::adoptCF( - SystemConfiguration::shared().network_reachability_create_with_name(nullptr, hostname->c_str())); - } - else { - struct sockaddr zeroAddress = {}; - zeroAddress.sa_len = sizeof(zeroAddress); - zeroAddress.sa_family = AF_INET; - - m_reachability_ref = util::adoptCF( - SystemConfiguration::shared().network_reachability_create_with_address(nullptr, &zeroAddress)); - } -} - -NetworkReachabilityObserver::~NetworkReachabilityObserver() -{ - stop_observing(); - dispatch_release(m_callback_queue); -} - -NetworkReachabilityStatus NetworkReachabilityObserver::reachability_status() const -{ - SCNetworkReachabilityFlags flags; - - if (SystemConfiguration::shared().network_reachability_get_flags(m_reachability_ref.get(), &flags)) - return reachability_status_for_flags(flags); - - return NotReachable; -} - -bool NetworkReachabilityObserver::start_observing() -{ - m_previous_status = reachability_status(); - - auto callback = [](SCNetworkReachabilityRef, SCNetworkReachabilityFlags, void* self) { - static_cast(self)->reachability_changed(); - }; - - SCNetworkReachabilityContext context = {0, this, nullptr, nullptr, nullptr}; - - if (!SystemConfiguration::shared().network_reachability_set_callback(m_reachability_ref.get(), callback, - &context)) - return false; - - if (!SystemConfiguration::shared().network_reachability_set_dispatch_queue(m_reachability_ref.get(), - m_callback_queue)) - return false; - - return true; -} - -void NetworkReachabilityObserver::stop_observing() -{ - SystemConfiguration::shared().network_reachability_set_dispatch_queue(m_reachability_ref.get(), nullptr); - SystemConfiguration::shared().network_reachability_set_callback(m_reachability_ref.get(), nullptr, nullptr); - - // Wait for all previously-enqueued blocks to execute to guarantee that - // no callback will be called after returning from this method - dispatch_sync(m_callback_queue, ^{ - }); -} - -void NetworkReachabilityObserver::reachability_changed() -{ - auto current_status = reachability_status(); - - // When observing reachability of the specific host the callback might be called - // several times (because of DNS queries) with the same reachability flags while - // the caller should be notified only when the reachability status is really changed. - if (current_status != m_previous_status) { - m_change_handler(current_status); - m_previous_status = current_status; - } -} - -#endif diff --git a/src/realm/object-store/sync/impl/apple/network_reachability_observer.hpp b/src/realm/object-store/sync/impl/apple/network_reachability_observer.hpp deleted file mode 100644 index 7bc1b3d8ce6..00000000000 --- a/src/realm/object-store/sync/impl/apple/network_reachability_observer.hpp +++ /dev/null @@ -1,65 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_NETWORK_REACHABILITY_OBSERVER_HPP -#define REALM_OS_NETWORK_REACHABILITY_OBSERVER_HPP - -#include - -#include -#include -#include - -#include - -#if NETWORK_REACHABILITY_AVAILABLE - -#include - -namespace realm { -namespace _impl { - -enum NetworkReachabilityStatus { NotReachable, ReachableViaWiFi, ReachableViaWWAN }; - -class NetworkReachabilityObserver { -public: - NetworkReachabilityObserver(util::Optional hostname, - util::UniqueFunction handler); - - ~NetworkReachabilityObserver(); - - NetworkReachabilityStatus reachability_status() const; - - bool start_observing(); - void stop_observing(); - -private: - void reachability_changed(); - - util::CFPtr m_reachability_ref; - NetworkReachabilityStatus m_previous_status; - dispatch_queue_t m_callback_queue; - util::UniqueFunction m_change_handler; -}; - -} // namespace _impl -} // namespace realm - -#endif // NETWORK_REACHABILITY_AVAILABLE - -#endif // REALM_OS_NETWORK_REACHABILITY_OBSERVER_HPP diff --git a/src/realm/object-store/sync/impl/apple/system_configuration.cpp b/src/realm/object-store/sync/impl/apple/system_configuration.cpp deleted file mode 100644 index 3ea8d17fd82..00000000000 --- a/src/realm/object-store/sync/impl/apple/system_configuration.cpp +++ /dev/null @@ -1,104 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// 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 - -#if NETWORK_REACHABILITY_AVAILABLE - -#include -#include - -using namespace realm; -using namespace realm::_impl; - -SystemConfiguration::SystemConfiguration() -{ - m_framework_handle = - dlopen("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration", RTLD_LAZY); - - if (m_framework_handle) { - m_create_with_name = (create_with_name_t)dlsym(m_framework_handle, "SCNetworkReachabilityCreateWithName"); - m_create_with_address = - (create_with_address_t)dlsym(m_framework_handle, "SCNetworkReachabilityCreateWithAddress"); - m_set_dispatch_queue = - (set_dispatch_queue_t)dlsym(m_framework_handle, "SCNetworkReachabilitySetDispatchQueue"); - m_set_callback = (set_callback_t)dlsym(m_framework_handle, "SCNetworkReachabilitySetCallback"); - m_get_flags = (get_flags_t)dlsym(m_framework_handle, "SCNetworkReachabilityGetFlags"); - } - else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - asl_log(nullptr, nullptr, ASL_LEVEL_WARNING, "network reachability is not available"); -#pragma clang diagnostic pop - } -} - -SystemConfiguration& SystemConfiguration::shared() -{ - static SystemConfiguration system_configuration; - - return system_configuration; -} - -SCNetworkReachabilityRef SystemConfiguration::network_reachability_create_with_name(CFAllocatorRef allocator, - const char* hostname) -{ - if (m_create_with_name) - return m_create_with_name(allocator, hostname); - - return nullptr; -} - -SCNetworkReachabilityRef SystemConfiguration::network_reachability_create_with_address(CFAllocatorRef allocator, - const sockaddr* address) -{ - if (m_create_with_address) - return m_create_with_address(allocator, address); - - return nullptr; -} - -bool SystemConfiguration::network_reachability_set_dispatch_queue(SCNetworkReachabilityRef target, - dispatch_queue_t queue) -{ - if (m_set_dispatch_queue) - return m_set_dispatch_queue(target, queue); - - return false; -} - -bool SystemConfiguration::network_reachability_set_callback(SCNetworkReachabilityRef target, - SCNetworkReachabilityCallBack callback, - SCNetworkReachabilityContext* context) -{ - if (m_set_callback) - return m_set_callback(target, callback, context); - - return false; -} - -bool SystemConfiguration::network_reachability_get_flags(SCNetworkReachabilityRef target, - SCNetworkReachabilityFlags* flags) -{ - if (m_get_flags) - return m_get_flags(target, flags); - - return false; -} - -#endif // NETWORK_REACHABILITY_AVAILABLE diff --git a/src/realm/object-store/sync/impl/apple/system_configuration.hpp b/src/realm/object-store/sync/impl/apple/system_configuration.hpp deleted file mode 100644 index 4a37c75413b..00000000000 --- a/src/realm/object-store/sync/impl/apple/system_configuration.hpp +++ /dev/null @@ -1,67 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_SYSTEM_CONFIGURATION_HPP -#define REALM_OS_SYSTEM_CONFIGURATION_HPP - -#include - -#if NETWORK_REACHABILITY_AVAILABLE - -#include - -namespace realm { -namespace _impl { - -class SystemConfiguration { -public: - static SystemConfiguration& shared(); - - SCNetworkReachabilityRef network_reachability_create_with_name(CFAllocatorRef allocator, const char* hostname); - SCNetworkReachabilityRef network_reachability_create_with_address(CFAllocatorRef allocator, - const sockaddr* address); - bool network_reachability_set_dispatch_queue(SCNetworkReachabilityRef target, dispatch_queue_t queue); - bool network_reachability_set_callback(SCNetworkReachabilityRef target, SCNetworkReachabilityCallBack callback, - SCNetworkReachabilityContext* context); - bool network_reachability_get_flags(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags* flags); - -private: - using create_with_name_t = SCNetworkReachabilityRef (*)(CFAllocatorRef, const char*); - using create_with_address_t = SCNetworkReachabilityRef (*)(CFAllocatorRef, const sockaddr*); - using set_dispatch_queue_t = bool (*)(SCNetworkReachabilityRef, dispatch_queue_t); - using set_callback_t = bool (*)(SCNetworkReachabilityRef, SCNetworkReachabilityCallBack, - SCNetworkReachabilityContext*); - using get_flags_t = bool (*)(SCNetworkReachabilityRef, SCNetworkReachabilityFlags*); - - SystemConfiguration(); - - void* m_framework_handle; - - create_with_name_t m_create_with_name = nullptr; - create_with_address_t m_create_with_address = nullptr; - set_dispatch_queue_t m_set_dispatch_queue = nullptr; - set_callback_t m_set_callback = nullptr; - get_flags_t m_get_flags = nullptr; -}; - -} // namespace _impl -} // namespace realm - -#endif // NETWORK_REACHABILITY_AVAILABLE - -#endif // REALM_OS_SYSTEM_CONFIGURATION_HPP diff --git a/src/realm/object-store/sync/impl/emscripten/network_transport.cpp b/src/realm/object-store/sync/impl/emscripten/network_transport.cpp deleted file mode 100644 index 5616b80b473..00000000000 --- a/src/realm/object-store/sync/impl/emscripten/network_transport.cpp +++ /dev/null @@ -1,130 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// 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 -#include -#include - -using namespace realm; -using namespace realm::app; - -namespace realm::_impl { -struct FetchState { - std::string request_body; - util::UniqueFunction completion_block; -}; - -static std::string_view trim_whitespace(std::string_view str) -{ - auto begin = str.begin(); - auto end = str.end(); - while (end > begin && std::isspace(end[-1])) - --end; - while (begin < end && std::isspace(begin[0])) - ++begin; - return std::string_view(&*begin, end - begin); -} - -static HttpHeaders parse_headers(std::string_view raw_headers) -{ - HttpHeaders ret; - size_t pos; - while ((pos = raw_headers.find("\r\n")) != std::string::npos) { - std::string_view line = raw_headers.substr(0, pos); - raw_headers = raw_headers.substr(pos + 2); - - size_t colon = line.find(":"); - ret.emplace(line.substr(0, colon), trim_whitespace(line.substr(colon + 1))); - } - return ret; -} - -static void success(emscripten_fetch_t* fetch) -{ - auto guard = util::make_scope_exit([&]() noexcept { - emscripten_fetch_close(fetch); - }); - std::unique_ptr state(reinterpret_cast(fetch->userData)); - std::string packed_headers; - packed_headers.resize(emscripten_fetch_get_response_headers_length(fetch)); - emscripten_fetch_get_response_headers(fetch, packed_headers.data(), packed_headers.size()); - state->completion_block( - {fetch->status, 0, parse_headers(packed_headers), std::string(fetch->data, size_t(fetch->numBytes)), {}}); -} - -static void error(emscripten_fetch_t* fetch) -{ - auto guard = util::make_scope_exit([&]() noexcept { - emscripten_fetch_close(fetch); - }); - std::string packed_headers; - packed_headers.resize(emscripten_fetch_get_response_headers_length(fetch)); - emscripten_fetch_get_response_headers(fetch, packed_headers.data(), packed_headers.size()); - - std::unique_ptr state(reinterpret_cast(fetch->userData)); - state->completion_block({fetch->status, 0, parse_headers(packed_headers), - std::string(fetch->data, size_t(fetch->numBytes)), ErrorCodes::HTTPError}); -} - -void EmscriptenNetworkTransport::send_request_to_server( - const Request& request, util::UniqueFunction&& completion_block) -{ - auto state = std::make_unique(FetchState{request.body, std::move(completion_block)}); - - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; - attr.onsuccess = success; - attr.onerror = error; - attr.timeoutMSecs = static_cast(request.timeout_ms); - - if (state->request_body.size()) { - attr.requestData = state->request_body.data(); - attr.requestDataSize = state->request_body.size(); - } - - std::vector request_headers_buf; - for (const auto& header : request.headers) { - request_headers_buf.push_back(header.first.c_str()); - request_headers_buf.push_back(header.second.c_str()); - } - request_headers_buf.push_back(nullptr); - attr.requestHeaders = request_headers_buf.data(); - - switch (request.method) { - case HttpMethod::get: - strncpy(attr.requestMethod, "GET", sizeof(attr.requestMethod)); - break; - case HttpMethod::post: - strncpy(attr.requestMethod, "POST", sizeof(attr.requestMethod)); - break; - case HttpMethod::put: - strncpy(attr.requestMethod, "PUT", sizeof(attr.requestMethod)); - break; - case app::HttpMethod::del: - strncpy(attr.requestMethod, "DELETE", sizeof(attr.requestMethod)); - break; - case app::HttpMethod::patch: - strncpy(attr.requestMethod, "PATCH", sizeof(attr.requestMethod)); - break; - } - - attr.userData = state.release(); - emscripten_fetch(&attr, request.url.c_str()); -} -} // namespace realm::_impl diff --git a/src/realm/object-store/sync/impl/emscripten/network_transport.hpp b/src/realm/object-store/sync/impl/emscripten/network_transport.hpp deleted file mode 100644 index b8bc1584fe6..00000000000 --- a/src/realm/object-store/sync/impl/emscripten/network_transport.hpp +++ /dev/null @@ -1,29 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// 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 - -namespace realm::_impl { - -class EmscriptenNetworkTransport final : public app::GenericNetworkTransport { -public: - virtual void send_request_to_server(const app::Request& request, - util::UniqueFunction&& completion_block) final; -}; - -} // namespace realm::_impl diff --git a/src/realm/object-store/sync/impl/emscripten/socket_provider.cpp b/src/realm/object-store/sync/impl/emscripten/socket_provider.cpp deleted file mode 100644 index 6df6e77b61d..00000000000 --- a/src/realm/object-store/sync/impl/emscripten/socket_provider.cpp +++ /dev/null @@ -1,239 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// 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 -#include - -using namespace realm; -using namespace realm::sync; - -namespace realm::_impl { -#define check_result(expr) \ - do { \ - [[maybe_unused]] auto result = expr; \ - REALM_ASSERT_3(result, ==, EMSCRIPTEN_RESULT_SUCCESS); \ - } while (0) - -struct EmscriptenTimer final : SyncSocketProvider::Timer { - using DoubleMiliseconds = std::chrono::duration; - -public: - EmscriptenTimer(std::chrono::milliseconds delay, SyncSocketProvider::FunctionHandler&& handler, - util::EmscriptenScheduler& scheduler) - : m_handler(std::move(handler)) - , m_timeout(emscripten_set_timeout(timeout_callback, DoubleMiliseconds(delay).count(), this)) - , m_scheduler(scheduler) - { - } - ~EmscriptenTimer() final - { - cancel(); - } - void cancel() final - { - if (m_timeout) { - emscripten_clear_timeout(*m_timeout); - m_timeout.reset(); - m_scheduler.invoke([handler = std::move(m_handler)]() { - handler(Status(ErrorCodes::OperationAborted, "Timer canceled")); - }); - } - } - -private: - SyncSocketProvider::FunctionHandler m_handler; - std::optional m_timeout; - util::EmscriptenScheduler& m_scheduler; - - static void timeout_callback(void* user_data) - { - auto timer = reinterpret_cast(user_data); - timer->m_timeout.reset(); - std::exchange(timer->m_handler, {})(Status::OK()); - } -}; - -struct EmscriptenWebSocket final : public WebSocketInterface { -public: - EmscriptenWebSocket(EMSCRIPTEN_WEBSOCKET_T socket, std::unique_ptr observer) - : m_socket(socket) - , m_sentinel(std::make_shared()) - , m_observer(std::move(observer)) - { - check_result(emscripten_websocket_set_onopen_callback(m_socket, m_observer.get(), open_callback)); - check_result(emscripten_websocket_set_onmessage_callback(m_socket, m_observer.get(), message_callback)); - check_result(emscripten_websocket_set_onerror_callback(m_socket, m_observer.get(), error_callback)); - check_result(emscripten_websocket_set_onclose_callback(m_socket, m_observer.get(), close_callback)); - } - - ~EmscriptenWebSocket() final - { - m_sentinel.reset(); - check_result(emscripten_websocket_close(m_socket, 0, nullptr)); - check_result(emscripten_websocket_delete(m_socket)); - } - - // Adapted from - // https://github.com/dotnet/runtime/blob/60b480424d51f42dfd66e09b010297dc041602f2/src/mono/wasm/runtime/web-socket.ts#L187: - // The WebSocket.send method doesn't provide a done callback, so we need to guess when the operation is done - // by observing the outgoing buffer on the websocket. - void async_write_binary(util::Span data, SyncSocketProvider::FunctionHandler&& handler) final - { - check_result(emscripten_websocket_send_binary(m_socket, const_cast(data.data()), data.size())); - - // If the buffered amount is small enough we can just run the handler right away. - size_t buffered_amount; - check_result(emscripten_websocket_get_buffered_amount(m_socket, &buffered_amount)); - constexpr size_t blocking_send_threshold = 65536; - if (buffered_amount < blocking_send_threshold) { - emscripten_set_timeout( - [](void* user_data) { - std::unique_ptr handler( - reinterpret_cast(user_data)); - (*handler)(Status::OK()); - }, - 0, new SyncSocketProvider::FunctionHandler(std::move(handler))); - } - else { - // Otherwise we start polling the buffer in a recursive timeout (see sending_poll_check below). - emscripten_set_timeout( - sending_poll_check, 0, - new PollCheckState{ - m_socket, [handler = std::move(handler), sentinel = std::weak_ptr(m_sentinel)](Status status) { - if (sentinel.lock()) { - // only call the real handler if the socket object is still alive - handler(status); - } - }}); - } - } - -private: - struct PollCheckState { - EMSCRIPTEN_WEBSOCKET_T socket; - SyncSocketProvider::FunctionHandler handler; - double next_delay = 1; - }; - - struct LivenessSentinel {}; - - static void sending_poll_check(void* user_data) - { - std::unique_ptr state(reinterpret_cast(user_data)); - - size_t buffered_amount; - check_result(emscripten_websocket_get_buffered_amount(state->socket, &buffered_amount)); - if (buffered_amount == 0) { - state->handler(Status::OK()); - return; - } - - unsigned short ready_state; - check_result(emscripten_websocket_get_ready_state(state->socket, &ready_state)); - if (ready_state != 1 /* OPEN */) { - // TODO: what's the right error code for websocket was closed while sending? - } - else { - auto delay = state->next_delay; - state->next_delay = std::min(delay * 1.5, 1000.0); - emscripten_set_timeout(sending_poll_check, delay, state.release()); - } - } - - static EM_BOOL open_callback(int, const EmscriptenWebSocketOpenEvent* event, void* user_data) - { - auto observer = reinterpret_cast(user_data); - int length; - check_result(emscripten_websocket_get_protocol_length(event->socket, &length)); - std::string protocol; - protocol.resize(length - 1); - check_result(emscripten_websocket_get_protocol(event->socket, protocol.data(), length)); - observer->websocket_connected_handler(protocol); - return EM_TRUE; - } - - static EM_BOOL message_callback(int, const EmscriptenWebSocketMessageEvent* event, void* user_data) - { - auto observer = reinterpret_cast(user_data); - REALM_ASSERT(!event->isText); - observer->websocket_binary_message_received( - util::Span(reinterpret_cast(event->data), event->numBytes)); - return EM_TRUE; - } - - static EM_BOOL error_callback(int, const EmscriptenWebSocketErrorEvent*, void* user_data) - { - auto observer = reinterpret_cast(user_data); - observer->websocket_error_handler(); - return EM_TRUE; - } - - static EM_BOOL close_callback(int, const EmscriptenWebSocketCloseEvent* event, void* user_data) - { - auto observer = reinterpret_cast(user_data); - REALM_ASSERT(event->code >= 1000 && event->code < 5000); - observer->websocket_closed_handler(event->wasClean, static_cast(event->code), - event->reason); - return EM_TRUE; - } - - EMSCRIPTEN_WEBSOCKET_T m_socket; - std::shared_ptr m_sentinel; - std::unique_ptr m_observer; -}; - -EmscriptenSocketProvider::EmscriptenSocketProvider() = default; - -EmscriptenSocketProvider::~EmscriptenSocketProvider() = default; - -SyncSocketProvider::SyncTimer EmscriptenSocketProvider::create_timer(std::chrono::milliseconds delay, - FunctionHandler&& handler) -{ - return std::make_unique(delay, std::move(handler), m_scheduler); -} - -std::unique_ptr EmscriptenSocketProvider::connect(std::unique_ptr observer, - WebSocketEndpoint&& endpoint) -{ - std::string protocols; - for (size_t i = 0; i < endpoint.protocols.size(); i++) { - if (i > 0) - protocols += ','; - protocols += endpoint.protocols[i]; - } - - // The `/` delimiter character is not allowed in the protocol list. Replace it with #. - // TODO: Remove once RCORE-1427 is resolved. - for (size_t i = 0; i < protocols.size(); i++) { - if (protocols[i] == '/') - protocols[i] = '#'; - } - - std::string url = - util::format("%1://%2:%3%4", endpoint.is_ssl ? "wss" : "ws", endpoint.address, endpoint.port, endpoint.path); - - EmscriptenWebSocketCreateAttributes attr; - emscripten_websocket_init_create_attributes(&attr); - attr.protocols = protocols.c_str(); - attr.url = url.c_str(); - attr.createOnMainThread = EM_FALSE; - int result = emscripten_websocket_new(&attr); - REALM_ASSERT(result > 0); - return std::make_unique(result, std::move(observer)); -} -} // namespace realm::_impl diff --git a/src/realm/object-store/sync/impl/emscripten/socket_provider.hpp b/src/realm/object-store/sync/impl/emscripten/socket_provider.hpp deleted file mode 100644 index 2feb358763f..00000000000 --- a/src/realm/object-store/sync/impl/emscripten/socket_provider.hpp +++ /dev/null @@ -1,47 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2023 Realm Inc. -// -// 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 - -namespace realm::_impl { - -class EmscriptenSocketProvider final : public sync::SyncSocketProvider { -public: - EmscriptenSocketProvider(); - ~EmscriptenSocketProvider() final; - - SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) final; - - void post(FunctionHandler&& handler) final - { - m_scheduler.invoke([handler = std::move(handler)]() { - handler(Status::OK()); - }); - } - - std::unique_ptr connect(std::unique_ptr observer, - sync::WebSocketEndpoint&& endpoint) final; - -private: - util::EmscriptenScheduler m_scheduler; -}; - -} // namespace realm::_impl diff --git a/src/realm/object-store/sync/impl/network_reachability.hpp b/src/realm/object-store/sync/impl/network_reachability.hpp deleted file mode 100644 index 8dbf5589735..00000000000 --- a/src/realm/object-store/sync/impl/network_reachability.hpp +++ /dev/null @@ -1,30 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_NETWORK_REACHABILITY_HPP -#define REALM_OS_NETWORK_REACHABILITY_HPP - -#include - -#if REALM_PLATFORM_APPLE -#define NETWORK_REACHABILITY_AVAILABLE !REALM_WATCHOS -#else -#define NETWORK_REACHABILITY_AVAILABLE 0 -#endif - -#endif // REALM_OS_NETWORK_REACHABILITY_HPP diff --git a/src/realm/object-store/sync/impl/sync_client.hpp b/src/realm/object-store/sync/impl/sync_client.hpp deleted file mode 100644 index 941f4db7da2..00000000000 --- a/src/realm/object-store/sync/impl/sync_client.hpp +++ /dev/null @@ -1,161 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_SYNC_CLIENT_HPP -#define REALM_OS_SYNC_CLIENT_HPP - -#include -#include -#include -#include - -#include - -#include -#include - -#if NETWORK_REACHABILITY_AVAILABLE -#include -#endif - -#ifdef __EMSCRIPTEN__ -#include -#endif - -namespace realm::_impl { - -struct SyncClient { - SyncClient(const std::shared_ptr& logger, SyncClientConfig const& config, - std::weak_ptr weak_sync_manager) - : m_socket_provider([&]() -> std::shared_ptr { - if (config.socket_provider) { - return config.socket_provider; - } -#ifdef __EMSCRIPTEN__ - return std::make_shared(); -#else - auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), - config.user_agent_binding_info, config.user_agent_application_info); - return std::make_shared( - logger, std::move(user_agent), config.default_socket_provider_thread_observer); -#endif - }()) - , m_client([&] { - sync::Client::Config c; - c.logger = logger; - c.socket_provider = m_socket_provider; - c.reconnect_mode = config.reconnect_mode; - c.one_connection_per_session = !config.multiplex_sessions; - - // Only set the timeouts if they have sensible values - if (config.timeouts.connect_timeout >= 1000) - c.connect_timeout = config.timeouts.connect_timeout; - if (config.timeouts.connection_linger_time > 0) - c.connection_linger_time = config.timeouts.connection_linger_time; - if (config.timeouts.ping_keepalive_period > 5000) - c.ping_keepalive_period = config.timeouts.ping_keepalive_period; - if (config.timeouts.pong_keepalive_timeout > 5000) - c.pong_keepalive_timeout = config.timeouts.pong_keepalive_timeout; - if (config.timeouts.fast_reconnect_limit > 1000) - c.fast_reconnect_limit = config.timeouts.fast_reconnect_limit; - c.reconnect_backoff_info.resumption_delay_interval = - config.timeouts.reconnect_backoff_info.resumption_delay_interval; - c.reconnect_backoff_info.max_resumption_delay_interval = - config.timeouts.reconnect_backoff_info.max_resumption_delay_interval; - c.reconnect_backoff_info.resumption_delay_backoff_multiplier = - config.timeouts.reconnect_backoff_info.resumption_delay_backoff_multiplier; - if (c.reconnect_backoff_info.resumption_delay_interval.count() < 1000) - logger->warn("A resumption delay interval less than 1000 (1 second) is not recommended"); - if (c.reconnect_backoff_info.resumption_delay_backoff_multiplier < 1) - throw InvalidArgument("Delay backoff multiplier in reconnect backoff info cannot be less than 1"); - return c; - }()) - , m_logger_ptr(logger) - , m_logger(*m_logger_ptr) -#if NETWORK_REACHABILITY_AVAILABLE - , m_reachability_observer(none, [weak_sync_manager](const NetworkReachabilityStatus status) { - if (status != NotReachable) { - if (auto sync_manager = weak_sync_manager.lock()) { - sync_manager->reconnect(); - } - } - }) - { - if (!m_reachability_observer.start_observing()) - m_logger.error("Failed to set up network reachability observer"); - } -#else - { - static_cast(weak_sync_manager); - } -#endif - - void cancel_reconnect_delay() - { - m_client.cancel_reconnect_delay(); - } - - void stop() - { - m_client.shutdown(); - } - - void voluntary_disconnect_all_connections() - { - m_client.voluntary_disconnect_all_connections(); - } - - std::unique_ptr make_session(std::shared_ptr db, - std::shared_ptr flx_sub_store, - std::shared_ptr migration_store, - sync::Session::Config&& config) - { - return std::make_unique(m_client, std::move(db), std::move(flx_sub_store), - std::move(migration_store), std::move(config)); - } - - bool decompose_server_url(const std::string& url, sync::ProtocolEnvelope& protocol, std::string& address, - sync::Client::port_type& port, std::string& path) const - { - return m_client.decompose_server_url(url, protocol, address, port, path); - } - - void wait_for_session_terminations() - { - m_client.wait_for_session_terminations_or_client_stopped(); - } - - // Async version of wait_for_session_terminations(). - util::Future notify_session_terminated() - { - return m_client.notify_session_terminated(); - } - -private: - std::shared_ptr m_socket_provider; - sync::Client m_client; - std::shared_ptr m_logger_ptr; - util::Logger& m_logger; -#if NETWORK_REACHABILITY_AVAILABLE - NetworkReachabilityObserver m_reachability_observer; -#endif -}; - -} // namespace realm::_impl - -#endif // REALM_OS_SYNC_CLIENT_HPP diff --git a/src/realm/object-store/sync/impl/sync_file.cpp b/src/realm/object-store/sync/impl/sync_file.cpp deleted file mode 100644 index 88def0eb62c..00000000000 --- a/src/realm/object-store/sync/impl/sync_file.cpp +++ /dev/null @@ -1,559 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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 - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include - -inline static int mkstemp(char* _template) -{ - return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE); -} -#else -#include -#endif - - -using File = realm::util::File; - -namespace realm { - -namespace { - -uint8_t value_of_hex_digit(char hex_digit) -{ - if (hex_digit >= '0' && hex_digit <= '9') { - return hex_digit - '0'; - } - else if (hex_digit >= 'A' && hex_digit <= 'F') { - return 10 + hex_digit - 'A'; - } - else if (hex_digit >= 'a' && hex_digit <= 'f') { - return 10 + hex_digit - 'a'; - } - else { - throw LogicError(ErrorCodes::InvalidArgument, "Cannot get the value of a character that isn't a hex digit."); - } -} - -bool filename_is_reserved(const std::string& filename) -{ - return (filename == "." || filename == ".."); -} - -bool character_is_unreserved(char character) -{ - bool is_capital_letter = (character >= 'A' && character <= 'Z'); - bool is_lowercase_letter = (character >= 'a' && character <= 'z'); - bool is_number = (character >= '0' && character <= '9'); - bool is_allowed_symbol = (character == '-' || character == '_' || character == '.'); - return is_capital_letter || is_lowercase_letter || is_number || is_allowed_symbol; -} - -char decoded_char_for(const std::string& percent_encoding, size_t index) -{ - if (index + 2 >= percent_encoding.length()) { - throw LogicError(ErrorCodes::InvalidArgument, - "Malformed string: not enough characters after '%' before end of string."); - } - REALM_ASSERT(percent_encoding[index] == '%'); - return (16 * value_of_hex_digit(percent_encoding[index + 1])) + value_of_hex_digit(percent_encoding[index + 2]); -} - -} // namespace - -namespace util { - -std::string make_percent_encoded_string(const std::string& raw_string) -{ - std::string buffer; - buffer.reserve(raw_string.size()); - for (size_t i = 0; i < raw_string.size(); i++) { - unsigned char character = raw_string[i]; - if (character_is_unreserved(character)) { - buffer.push_back(character); - } - else { - buffer.resize(buffer.size() + 3); - // Format string must resolve to exactly 3 characters. - snprintf(&buffer.back() - 2, 4, "%%%2X", character); - } - } - return buffer; -} - -std::string make_raw_string(const std::string& percent_encoded_string) -{ - std::string buffer; - size_t input_len = percent_encoded_string.length(); - buffer.reserve(input_len); - size_t idx = 0; - while (idx < input_len) { - char current = percent_encoded_string[idx]; - if (current == '%') { - // Decode. +3. - buffer.push_back(decoded_char_for(percent_encoded_string, idx)); - idx += 3; - } - else { - // No need to decode. +1. - if (!character_is_unreserved(current)) { - throw LogicError(ErrorCodes::InvalidArgument, - "Input string is invalid: contains reserved characters."); - } - buffer.push_back(current); - idx++; - } - } - return buffer; -} - -std::string file_path_by_appending_component(const std::string& path, const std::string& component, - FilePathType path_type) -{ -#ifdef _WIN32 - const char separator = '\\'; -#else - const char separator = '/'; -#endif - std::string buffer; - buffer.reserve(2 + path.length() + component.length()); - buffer.append(path); - std::string terminal = ""; - if (path_type == FilePathType::Directory && component[component.length() - 1] != separator) { - terminal = separator; - } - char path_last = path[path.length() - 1]; - char component_first = component[0]; - if (path_last == separator && component_first == separator) { - buffer.append(component.substr(1)); - buffer.append(terminal); - } - else if (path_last == separator || component_first == separator) { - buffer.append(component); - buffer.append(terminal); - } - else { - buffer.append(std::string(1, separator)); - buffer.append(component); - buffer.append(terminal); - } - return buffer; -} - -std::string file_path_by_appending_extension(const std::string& path, const std::string& extension) -{ - std::string buffer; - buffer.reserve(1 + path.length() + extension.length()); - buffer.append(path); - char path_last = path[path.length() - 1]; - char extension_first = extension[0]; - if (path_last == '.' && extension_first == '.') { - buffer.append(extension.substr(1)); - } - else if (path_last == '.' || extension_first == '.') { - buffer.append(extension); - } - else { - buffer.append("."); - buffer.append(extension); - } - return buffer; -} - -std::string create_timestamped_template(const std::string& prefix, int wildcard_count) -{ - constexpr int WILDCARD_MAX = 20; - constexpr int WILDCARD_MIN = 6; - wildcard_count = std::min(WILDCARD_MAX, std::max(WILDCARD_MIN, wildcard_count)); - std::time_t time = std::time(nullptr); - std::stringstream stream; - stream << prefix << "-" << util::format_local_time(time, "%Y%m%d-%H%M%S") << "-" - << std::string(wildcard_count, 'X'); - return stream.str(); -} - -std::string reserve_unique_file_name(const std::string& path, const std::string& template_string) -{ - REALM_ASSERT_DEBUG(template_string.find("XXXXXX") != std::string::npos); - std::string path_buffer = file_path_by_appending_component(path, template_string, FilePathType::File); - int fd = mkstemp(&path_buffer[0]); - if (fd < 0) { - int err = errno; - throw RuntimeError(ErrorCodes::FileOperationFailed, - util::format("Failed to make temporary path: %1 (%2)", - std::system_error(err, std::system_category()).what(), err)); - } - // Remove the file so we can use the name for our own file. -#ifdef _WIN32 - _close(fd); - _unlink(path_buffer.c_str()); -#else - close(fd); - unlink(path_buffer.c_str()); -#endif - return path_buffer; -} - -static std::string validate_and_clean_path(const std::string& path) -{ - REALM_ASSERT(path.length() > 0); - std::string escaped_path = util::make_percent_encoded_string(path); - if (filename_is_reserved(escaped_path)) - throw LogicError( - ErrorCodes::InvalidArgument, - util::format("A path can't have an identifier reserved by the filesystem: '%1'", escaped_path)); - return escaped_path; -} - -} // namespace util - -SyncFileManager::SyncFileManager(const app::AppConfig& config) - : m_base_path(util::file_path_by_appending_component(config.base_file_path, c_sync_directory, - util::FilePathType::Directory)) - , m_app_path(util::file_path_by_appending_component(m_base_path, util::validate_and_clean_path(config.app_id), - util::FilePathType::Directory)) -{ - util::try_make_dir(m_base_path); - util::try_make_dir(m_app_path); -} - -std::string SyncFileManager::get_special_directory(std::string directory_name) const -{ - auto dir_path = file_path_by_appending_component(m_app_path, directory_name, util::FilePathType::Directory); - util::try_make_dir(dir_path); - return dir_path; -} - -std::string SyncFileManager::user_directory(const std::string& user_id) const -{ - std::string user_path = get_user_directory_path(user_id); - util::try_make_dir(user_path); - return user_path; -} - -void SyncFileManager::remove_user_realms(const std::string& user_id) const -{ - // The following is redundant except for apps built before file tracking. - std::string user_path = get_user_directory_path(user_id); - util::try_remove_dir_recursive(user_path); -} - -bool SyncFileManager::remove_realm(const std::string& absolute_path) const -{ - REALM_ASSERT(absolute_path.length() > 0); - bool success = true; - try { - constexpr bool delete_lockfile = true; - realm::DB::delete_files(absolute_path, &success, delete_lockfile); - } - catch (FileAccessError const&) { - success = false; - } - return success; -} - -bool SyncFileManager::copy_realm_file(const std::string& old_path, const std::string& new_path) const -{ - REALM_ASSERT(old_path.length() > 0); - try { - const bool overwrite_existing = false; - return File::copy(old_path, new_path, overwrite_existing); - } - catch (FileAccessError const&) { - return false; - } - return true; -} - -bool SyncFileManager::remove_realm(const std::string& user_id, const std::vector& legacy_user_identities, - const std::string& raw_realm_path, const std::string& partition) const -{ - auto existing = get_existing_realm_file_path(user_id, legacy_user_identities, raw_realm_path, partition); - if (existing) { - return remove_realm(*existing); - } - return false; // if there is nothing to remove this is considered to be not successful -} - -bool SyncFileManager::try_file_exists(const std::string& path) noexcept -{ - try { - // May throw; for example when the path is too long - return util::File::exists(path); - } - catch (const std::exception&) { - return false; - } -} - -static bool try_file_remove(const std::string& path) noexcept -{ - try { - return util::File::try_remove(path); - } - catch (const std::exception&) { - return false; - } -} - -util::Optional -SyncFileManager::get_existing_realm_file_path(const std::string& user_id, - const std::vector& legacy_user_identities, - const std::string& realm_file_name, const std::string& partition) const -{ - std::string preferred_name_without_suffix = preferred_realm_path_without_suffix(user_id, realm_file_name); - if (try_file_exists(preferred_name_without_suffix)) { - return preferred_name_without_suffix; - } - - std::string preferred_name_with_suffix = preferred_name_without_suffix + c_realm_file_suffix; - if (try_file_exists(preferred_name_with_suffix)) { - return preferred_name_with_suffix; - } - - // Shorten the Realm path to just `/.realm` - std::string hashed_name = fallback_hashed_realm_file_path(preferred_name_without_suffix); - std::string hashed_path = hashed_name + c_realm_file_suffix; - if (try_file_exists(hashed_path)) { - // detected that the hashed fallback has been used previously - // it was created for a reason so keep using it - return hashed_path; - } - - // The legacy fallback paths are not applicable to flexible sync - if (partition.empty()) { - return util::none; - } - - // We used to hash the string value of the partition. For compatibility, check that SHA256 - // hash file name exists, and if it does, continue to use it. - if (!partition.empty()) { - std::string hashed_partition_path = legacy_hashed_partition_path(user_id, partition); - if (try_file_exists(hashed_partition_path)) { - return hashed_partition_path; - } - } - - for (auto& legacy_identity : legacy_user_identities) { - // retain support for legacy paths - std::string old_path = legacy_realm_file_path(legacy_identity, realm_file_name); - if (try_file_exists(old_path)) { - return old_path; - } - // retain support for legacy local identity paths - std::string old_local_identity_path = legacy_local_identity_path(legacy_identity, partition); - if (try_file_exists(old_local_identity_path)) { - return old_local_identity_path; - } - } - - return util::none; -} - -std::string SyncFileManager::realm_file_path(const std::string& user_id, - const std::vector& legacy_user_identities, - const std::string& realm_file_name, const std::string& partition) const -{ - auto existing_path = get_existing_realm_file_path(user_id, legacy_user_identities, realm_file_name, partition); - if (existing_path) { - return *existing_path; - } - - // since this appears to be a new file, test the normal location - // we use a test file with the same name and a suffix of the - // same length, so we can catch "filename too long" errors on windows - std::string preferred_name_without_suffix = preferred_realm_path_without_suffix(user_id, realm_file_name); - std::string preferred_name_with_suffix = preferred_name_without_suffix + c_realm_file_suffix; - try { - std::string test_path = preferred_name_without_suffix + c_realm_file_test_suffix; - auto defer = util::make_scope_exit([test_path]() noexcept { - try_file_remove(test_path); - }); - util::File f(test_path, util::File::Mode::mode_Write); - // if the test file succeeds, delete it and return the preferred location - } - catch (const FileAccessError&) { - // the preferred test failed, test the hashed path - std::string hashed_name = fallback_hashed_realm_file_path(preferred_name_without_suffix); - std::string hashed_path = hashed_name + c_realm_file_suffix; - try { - std::string test_hashed_path = hashed_name + c_realm_file_test_suffix; - auto defer = util::make_scope_exit([test_hashed_path]() noexcept { - try_file_remove(test_hashed_path); - }); - util::File f(test_hashed_path, util::File::Mode::mode_Write); - // at this point the create succeeded, clean up the test file and return the hashed path - return hashed_path; - } - catch (const FileAccessError& e_hashed) { - // hashed test path also failed, give up and report error to user. - throw LogicError(ErrorCodes::InvalidArgument, - util::format("A valid realm path cannot be created for the " - "Realm identity '%1' at neither '%2' nor '%3'. %4", - realm_file_name, preferred_name_with_suffix, hashed_path, e_hashed.what())); - } - } - - return preferred_name_with_suffix; -} - -std::string SyncFileManager::metadata_path() const -{ - auto dir_path = file_path_by_appending_component(get_utility_directory(), c_metadata_directory, - util::FilePathType::Directory); - util::try_make_dir(dir_path); - return util::file_path_by_appending_component(dir_path, c_metadata_realm); -} - -bool SyncFileManager::remove_metadata_realm() const -{ - auto dir_path = file_path_by_appending_component(get_utility_directory(), c_metadata_directory, - util::FilePathType::Directory); - try { - util::try_remove_dir_recursive(dir_path); - return true; - } - catch (FileAccessError const&) { - return false; - } -} - -std::string SyncFileManager::preferred_realm_path_without_suffix(const std::string& user_id, - const std::string& realm_file_name) const -{ - auto escaped_file_name = util::validate_and_clean_path(realm_file_name); - std::string preferred_name = util::file_path_by_appending_component(user_directory(user_id), escaped_file_name); - if (StringData(preferred_name).ends_with(c_realm_file_suffix)) { - preferred_name = preferred_name.substr(0, preferred_name.size() - strlen(c_realm_file_suffix)); - } - return preferred_name; -} - -std::string SyncFileManager::fallback_hashed_realm_file_path(const std::string& preferred_path) const -{ - std::array hash; - util::sha256(preferred_path.data(), preferred_path.size(), hash.data()); - std::string hashed_name = - util::file_path_by_appending_component(m_app_path, util::hex_dump(hash.data(), hash.size(), "")); - return hashed_name; -} - -std::string SyncFileManager::legacy_hashed_partition_path(const std::string& user_id, - const std::string& partition) const -{ - std::array hash; - util::sha256(partition.data(), partition.size(), hash.data()); - std::string legacy_hashed_file_name = util::hex_dump(hash.data(), hash.size(), ""); - std::string legacy_partition_path = util::file_path_by_appending_component( - get_user_directory_path(user_id), legacy_hashed_file_name + c_realm_file_suffix); - return legacy_partition_path; -} - -std::string SyncFileManager::legacy_realm_file_path(const std::string& local_user_identity, - const std::string& realm_file_name) const -{ - auto path = - util::file_path_by_appending_component(m_app_path, c_legacy_sync_directory, util::FilePathType::Directory); - path = util::file_path_by_appending_component(path, util::validate_and_clean_path(local_user_identity), - util::FilePathType::Directory); - path = util::file_path_by_appending_component(path, util::validate_and_clean_path(realm_file_name)); - return path; -} - -std::string SyncFileManager::legacy_local_identity_path(const std::string& local_user_identity, - const std::string& realm_file_name) const -{ - auto escaped_file_name = util::validate_and_clean_path(realm_file_name); - std::string user_path = get_user_directory_path(local_user_identity); - std::string path_name = util::file_path_by_appending_component(user_path, escaped_file_name); - std::string path = path_name + c_realm_file_suffix; - - return path; -} - -std::string SyncFileManager::get_user_directory_path(const std::string& user_id) const -{ - return file_path_by_appending_component(m_app_path, util::validate_and_clean_path(user_id), - util::FilePathType::Directory); -} - -static std::string string_from_partition(std::string_view partition) -{ - bson::Bson partition_value = bson::parse(partition); - switch (partition_value.type()) { - case bson::Bson::Type::Int32: - return util::format("i_%1", static_cast(partition_value)); - case bson::Bson::Type::Int64: - return util::format("l_%1", static_cast(partition_value)); - case bson::Bson::Type::String: - return util::format("s_%1", static_cast(partition_value)); - case bson::Bson::Type::ObjectId: - return util::format("o_%1", static_cast(partition_value).to_string()); - case bson::Bson::Type::Uuid: - return util::format("u_%1", static_cast(partition_value).to_string()); - case bson::Bson::Type::Null: - return "null"; - default: - throw InvalidArgument(util::format("Unsupported partition key value: '%1'. Only int, string " - "UUID and ObjectId types are currently supported.", - partition_value.to_string())); - } -} - -std::string SyncFileManager::path_for_realm(const SyncConfig& config, - std::optional custom_file_name) const -{ - auto user = config.user; - REALM_ASSERT(user); - // Attempt to make a nicer filename which will ease debugging when - // locating files in the filesystem. - auto file_name = [&]() -> std::string { - if (custom_file_name) { - return *custom_file_name; - } - if (config.flx_sync_requested) { - REALM_ASSERT_DEBUG(config.partition_value.empty()); - return "flx_sync_default"; - } - return string_from_partition(config.partition_value); - }(); - auto path = realm_file_path(user->user_id(), user->legacy_identities(), file_name, config.partition_value); - user->track_realm(path); - return path; -} - -} // namespace realm diff --git a/src/realm/object-store/sync/impl/sync_file.hpp b/src/realm/object-store/sync/impl/sync_file.hpp deleted file mode 100644 index c5191149e2e..00000000000 --- a/src/realm/object-store/sync/impl/sync_file.hpp +++ /dev/null @@ -1,161 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_SYNC_FILE_HPP -#define REALM_OS_SYNC_FILE_HPP - -#include -#include -#include - -namespace realm { -struct SyncConfig; -class SyncUser; - -namespace app { -struct AppConfig; -} - -namespace util { - -enum class FilePathType { File, Directory }; - -// FIXME: Does it make sense to use realm::StringData arguments for these functions instead of std::string? - -/// Given a string, turn it into a percent-encoded string. -std::string make_percent_encoded_string(const std::string& raw_string); - -/// Given a percent-encoded string, turn it into the original (non-encoded) string. -std::string make_raw_string(const std::string& percent_encoded_string); - -/// Given a file path and a path component, return a new path created by appending the component to the path. -std::string file_path_by_appending_component(const std::string& path, const std::string& component, - FilePathType path_type = FilePathType::File); - -/// Given a file path and an extension, append the extension to the path. -std::string file_path_by_appending_extension(const std::string& path, const std::string& extension); - -/// Create a timestamped `mktemp`-compatible template string using the current local time. -std::string create_timestamped_template(const std::string& prefix, int wildcard_count = 8); - -/// Reserve a unique file name based on a base directory path and a `mktemp`-compatible template string. -/// Returns the path of the file. -std::string reserve_unique_file_name(const std::string& path, const std::string& template_string); - -} // namespace util - -// This class manages how Synced Realms are stored on the filesystem. -class SyncFileManager { -public: - SyncFileManager(const app::AppConfig&); - - /// Remove the Realms at the specified absolute paths along with any associated helper files. - void remove_user_realms(const std::string& user_id) const; // throws - - /// A non throw version of File::exists(), returning false if any exceptions are thrown when attempting to access - /// this file. - static bool try_file_exists(const std::string& path) noexcept; - - std::optional get_existing_realm_file_path(const std::string& user_id, - const std::vector& legacy_user_identities, - const std::string& realm_file_name, - const std::string& partition) const; - /// Return the path for a given Realm, creating the user directory if it does not already exist. - std::string realm_file_path(const std::string& user_id, const std::vector& legacy_user_identities, - const std::string& realm_file_name, const std::string& partition) const; - - // Get the default path for a Realm for the given configuration. - // The default value is `///.realm`. - // If the file cannot be created at this location, for example due to path length restrictions, - // this function may pass back `/.realm` - std::string path_for_realm(const SyncConfig& config, - std::optional custom_file_name = std::nullopt) const; - - /// Remove the Realm at a given path for a given user. Returns `true` if the remove operation fully succeeds. - bool remove_realm(const std::string& user_id, const std::vector& legacy_user_identities, - const std::string& realm_file_name, const std::string& partition) const; - - /// Remove the Realm whose primary Realm file is located at `absolute_path`. Returns `true` if the remove - /// operation fully succeeds. - bool remove_realm(const std::string& absolute_path) const; - - /// Copy the Realm file at the location `old_path` to the location of `new_path`. - bool copy_realm_file(const std::string& old_path, const std::string& new_path) const; - - /// Return the path for the metadata Realm files. - std::string metadata_path() const; - - /// Remove the metadata Realm. - bool remove_metadata_realm() const; - - const std::string& base_path() const - { - return m_base_path; - } - - const std::string& app_path() const - { - return m_app_path; - } - - std::string recovery_directory_path(std::optional const& directory = {}) const - { - return get_special_directory(directory.value_or(c_recovery_directory)); - } - -private: - // Denotes the base path for the mongodb-realm app associated with this sync manager. - // Expected to be `base_path` + "mongodb-realm/" + `app_id` + "/". - const std::string m_base_path; - // Denotes the root path for any mongodb-realm app for the passed in `base_path`. - // Expected to be `base_path` + "mongodb-realm/". - const std::string m_app_path; - - static constexpr const char c_sync_directory[] = "mongodb-realm"; - static constexpr const char c_utility_directory[] = "server-utility"; - static constexpr const char c_recovery_directory[] = "recovered-realms"; - static constexpr const char c_metadata_directory[] = "metadata"; - static constexpr const char c_metadata_realm[] = "sync_metadata.realm"; - static constexpr const char c_realm_file_suffix[] = ".realm"; - static constexpr const char c_realm_file_test_suffix[] = - ".rtest"; // Must have same length as c_realm_file_suffix. - static constexpr const char c_legacy_sync_directory[] = "realm-object-server"; - - std::string get_special_directory(std::string directory_name) const; - - std::string get_utility_directory() const - { - return get_special_directory(c_utility_directory); - } - /// Return the user directory for a given user, creating it if it does not already exist. - std::string user_directory(const std::string& user_id) const; - // Construct the absolute path to the users directory - std::string get_user_directory_path(const std::string& user_id) const; - std::string legacy_hashed_partition_path(const std::string& user_id, const std::string& partition) const; - std::string legacy_realm_file_path(const std::string& local_user_identity, - const std::string& realm_file_name) const; - std::string legacy_local_identity_path(const std::string& local_user_identity, - const std::string& realm_file_name) const; - std::string preferred_realm_path_without_suffix(const std::string& user_id, - const std::string& realm_file_name) const; - std::string fallback_hashed_realm_file_path(const std::string& preferred_path) const; -}; - -} // namespace realm - -#endif // REALM_OS_SYNC_FILE_HPP diff --git a/src/realm/object-store/sync/jwt.cpp b/src/realm/object-store/sync/jwt.cpp deleted file mode 100644 index 999fbd956e7..00000000000 --- a/src/realm/object-store/sync/jwt.cpp +++ /dev/null @@ -1,89 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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 - -#include -#include - -namespace realm { - -static std::string_view split_token(std::string_view jwt) noexcept -{ - constexpr static char delimiter = '.'; - - auto pos = jwt.find(delimiter); - if (pos == 0 || pos == jwt.npos) { - return {}; - } - jwt = jwt.substr(pos + 1); - - pos = jwt.find(delimiter); - if (pos == jwt.npos) { - return {}; - } - auto payload = jwt.substr(0, pos); - jwt = jwt.substr(pos + 1); - - // We don't use the signature, but verify one is present - if (jwt.size() == 0 || std::count(jwt.begin(), jwt.end(), '.') != 0) { - return {}; - } - - return payload; -} - -RealmJWT::RealmJWT(std::string_view token) -{ - auto payload = split_token(token); - if (!payload.data()) { - throw app::AppError(ErrorCodes::BadToken, "malformed JWT"); - } - - auto json_str = util::base64_decode_to_vector(payload); - if (!json_str) { - throw app::AppError(ErrorCodes::BadToken, "JWT payload could not be base64 decoded"); - } - - auto json = static_cast(bson::parse(*json_str)); - - this->token = token; - this->expires_at = static_cast(json["exp"]); - this->issued_at = static_cast(json["iat"]); - - if (auto user_data = json.find("user_data")) { - this->user_data = static_cast(*user_data); - } -} - -bool RealmJWT::validate(std::string_view token) -{ - auto payload = split_token(token); - if (!payload.data()) { - return false; - } - - auto json_str = util::base64_decode_to_vector(payload); - if (!json_str) { - return false; - } - - return bson::accept(*json_str); -} - -} // namespace realm diff --git a/src/realm/object-store/sync/jwt.hpp b/src/realm/object-store/sync/jwt.hpp deleted file mode 100644 index 7e7b2f7dc01..00000000000 --- a/src/realm/object-store/sync/jwt.hpp +++ /dev/null @@ -1,66 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_JWT_HPP -#define REALM_OS_JWT_HPP - -#include - -#include -#include - -namespace realm { -// A struct that decodes a given JWT. -struct RealmJWT { - // The raw encoded token - std::string token; - - // When the token expires. - int64_t expires_at = 0; - // When the token was issued. - int64_t issued_at = 0; - // Custom user data embedded in the encoded token. - std::optional user_data; - - RealmJWT() = default; - explicit RealmJWT(std::string_view token); - explicit RealmJWT(const std::string& token) - : RealmJWT(std::string_view(token)) - { - } - - static bool validate(std::string_view token); - - bool operator==(const RealmJWT& other) const noexcept - { - return token == other.token; - } - bool operator!=(const RealmJWT& other) const noexcept - { - return token != other.token; - } - - explicit operator bool() const noexcept - { - return !token.empty(); - } -}; - -} // namespace realm - -#endif // REALM_OS_JWT_HPP diff --git a/src/realm/object-store/sync/mongo_client.cpp b/src/realm/object-store/sync/mongo_client.cpp deleted file mode 100644 index 8179b43d1c4..00000000000 --- a/src/realm/object-store/sync/mongo_client.cpp +++ /dev/null @@ -1,38 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include - -namespace realm::app { - -MongoClient::~MongoClient() = default; - -MongoDatabase MongoClient::operator[](const std::string& name) -{ - return MongoDatabase(name, m_user, m_service, m_service_name); -} - -MongoDatabase MongoClient::db(const std::string& name) -{ - return MongoDatabase(name, m_user, m_service, m_service_name); -} - -} // namespace realm::app diff --git a/src/realm/object-store/sync/mongo_client.hpp b/src/realm/object-store/sync/mongo_client.hpp deleted file mode 100644 index 9cb419c59d5..00000000000 --- a/src/realm/object-store/sync/mongo_client.hpp +++ /dev/null @@ -1,63 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef MONGO_CLIENT_HPP -#define MONGO_CLIENT_HPP - -#include -#include - -namespace realm::app { -class AppServiceClient; -class MongoDatabase; -class User; - -/// A client responsible for communication with a remote MongoDB database. -class MongoClient { -public: - ~MongoClient(); - MongoClient(const MongoClient&) = default; - MongoClient(MongoClient&&) = default; - MongoClient& operator=(const MongoClient&) = default; - MongoClient& operator=(MongoClient&&) = default; - - /// Gets a `MongoDatabase` instance for the given database name. - /// @param name the name of the database to retrieve - MongoDatabase operator[](const std::string& name); - - /// Gets a `MongoDatabase` instance for the given database name. - /// @param name the name of the database to retrieve - MongoDatabase db(const std::string& name); - -private: - friend class User; - MongoClient(std::shared_ptr user, std::shared_ptr service, std::string service_name) - : m_user(std::move(user)) - , m_service(std::move(service)) - , m_service_name(std::move(service_name)) - { - } - - std::shared_ptr m_user; - std::shared_ptr m_service; - std::string m_service_name; -}; - -} // namespace realm::app - -#endif /* mongo_client_hpp */ diff --git a/src/realm/object-store/sync/mongo_collection.cpp b/src/realm/object-store/sync/mongo_collection.cpp deleted file mode 100644 index 7c3a1a11ce0..00000000000 --- a/src/realm/object-store/sync/mongo_collection.cpp +++ /dev/null @@ -1,591 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -#include -#include -#include - -namespace realm { -namespace app { - -using namespace bson; -template -using ResponseHandler = MongoCollection::ResponseHandler; - -namespace { - -template -util::Optional get(const BsonDocument& map, const char* key) -{ - if (auto val = map.find(key)) { - return static_cast((*val)); - } - return util::none; -} - -ResponseHandler> get_delete_count_handler(ResponseHandler&& completion) -{ - return [completion = std::move(completion)](util::Optional&& value, util::Optional&& error) { - if (value && !error) { - try { - auto& document = static_cast(*value); - return completion(get(document, "deletedCount").value_or(0), std::move(error)); - } - catch (const std::exception& e) { - return completion(0, AppError(ErrorCodes::BadBsonParse, e.what())); - } - } - - return completion(0, std::move(error)); - }; -} - -ResponseHandler> get_update_handler(ResponseHandler&& completion) -{ - return [completion = std::move(completion)](util::Optional&& value, util::Optional&& error) { - if (error) { - return completion({}, std::move(error)); - } - - try { - auto& document = static_cast(*value); - return completion(MongoCollection::UpdateResult{get(document, "matchedCount").value_or(0), - get(document, "modifiedCount").value_or(0), - get(document, "upsertedId")}, - std::move(error)); - } - catch (const std::exception& e) { - return completion({}, AppError(ErrorCodes::BadBsonParse, e.what())); - } - }; -} - -ResponseHandler> get_document_handler(ResponseHandler>&& completion) -{ - return [completion = std::move(completion)](util::Optional&& value, util::Optional&& error) { - if (error) { - return completion(util::none, std::move(error)); - } - - if (!value) { - // no docs were found - return completion(util::none, util::none); - } - - if (holds_alternative(*value)) { - // no docs were found - return completion(util::none, util::none); - } - - return completion(static_cast(*value), util::none); - }; -} - -} // anonymous namespace - -MongoCollection::MongoCollection(const std::string& name, const std::string& database_name, - const std::shared_ptr& user, const std::shared_ptr& service, - const std::string& service_name) - : m_name(name) - , m_database_name(database_name) - , m_base_operation_args({{"database", m_database_name}, {"collection", m_name}}) - , m_user(user) - , m_service(service) - , m_service_name(service_name) -{ -} - -void MongoCollection::find(const BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion) -{ - find_bson(filter_bson, options, - [completion = std::move(completion)](util::Optional&& value, util::Optional&& error) { - if (error) { - return completion(util::none, std::move(error)); - } - - return completion(static_cast(*value), util::none); - }); -} - -void MongoCollection::find(const BsonDocument& filter_bson, ResponseHandler>&& completion) -{ - find(filter_bson, {}, std::move(completion)); -} - -void MongoCollection::find_one(const BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion) -{ - find_one_bson(filter_bson, options, get_document_handler(std::move(completion))); -} - -void MongoCollection::find_one(const BsonDocument& filter_bson, - ResponseHandler>&& completion) -{ - find_one(filter_bson, {}, std::move(completion)); -} - -void MongoCollection::insert_one(const BsonDocument& value_bson, ResponseHandler>&& completion) -{ - insert_one_bson(value_bson, [completion = std::move(completion)](util::Optional&& value, - util::Optional&& error) { - if (error) { - return completion(util::none, std::move(error)); - } - - auto& document = static_cast(*value); - return completion(document.at("insertedId"), util::none); - }); -} - -void MongoCollection::aggregate(const BsonArray& pipeline, ResponseHandler>&& completion) -{ - aggregate_bson(pipeline, [completion = std::move(completion)](util::Optional&& value, - util::Optional&& error) { - if (error) { - return completion(util::none, std::move(error)); - } - - return completion(static_cast(*value), util::none); - }); -} - -void MongoCollection::count(const BsonDocument& filter_bson, int64_t limit, ResponseHandler&& completion) -{ - count_bson(filter_bson, limit, - [completion = std::move(completion)](util::Optional&& value, util::Optional&& error) { - if (error) { - return completion(0, std::move(error)); - } - - return completion(static_cast(*value), util::none); - }); -} - -void MongoCollection::count(const BsonDocument& filter_bson, ResponseHandler&& completion) -{ - count(filter_bson, 0, std::move(completion)); -} - -void MongoCollection::insert_many(const BsonArray& documents, ResponseHandler&& completion) -{ - insert_many_bson(documents, [completion = std::move(completion)](util::Optional&& value, - util::Optional&& error) { - if (error) { - return completion({}, std::move(error)); - } - - auto& bson = static_cast(*value); - return completion(get(bson, "insertedIds").value_or(BsonArray()), std::move(error)); - }); -} - -void MongoCollection::delete_one(const BsonDocument& filter_bson, ResponseHandler&& completion) -{ - delete_one_bson(filter_bson, get_delete_count_handler(std::move(completion))); -} - -void MongoCollection::delete_many(const BsonDocument& filter_bson, ResponseHandler&& completion) -{ - delete_many_bson(filter_bson, get_delete_count_handler(std::move(completion))); -} - -void MongoCollection::update_one(const BsonDocument& filter_bson, const BsonDocument& update_bson, bool upsert, - ResponseHandler&& completion) -{ - update_one_bson(filter_bson, update_bson, upsert, get_update_handler(std::move(completion))); -} - -void MongoCollection::update_one(const BsonDocument& filter_bson, const BsonDocument& update_bson, - ResponseHandler&& completion) -{ - update_one(filter_bson, update_bson, false, std::move(completion)); -} - -void MongoCollection::update_many(const BsonDocument& filter_bson, const BsonDocument& update_bson, bool upsert, - ResponseHandler&& completion) -{ - update_many_bson(filter_bson, update_bson, upsert, get_update_handler(std::move(completion))); -} - -void MongoCollection::update_many(const BsonDocument& filter_bson, const BsonDocument& update_bson, - ResponseHandler&& completion) -{ - update_many(filter_bson, update_bson, false, std::move(completion)); -} - -void MongoCollection::find_one_and_update(const BsonDocument& filter_bson, const BsonDocument& update_bson, - const MongoCollection::FindOneAndModifyOptions& options, - ResponseHandler>&& completion) -{ - find_one_and_update_bson(filter_bson, update_bson, options, get_document_handler(std::move(completion))); -} - -void MongoCollection::find_one_and_update(const BsonDocument& filter_bson, const BsonDocument& update_bson, - ResponseHandler>&& completion) -{ - find_one_and_update(filter_bson, update_bson, {}, std::move(completion)); -} - -void MongoCollection::find_one_and_replace(const BsonDocument& filter_bson, const BsonDocument& replacement_bson, - const MongoCollection::FindOneAndModifyOptions& options, - ResponseHandler>&& completion) -{ - find_one_and_replace_bson(filter_bson, replacement_bson, options, get_document_handler(std::move(completion))); -} - -void MongoCollection::find_one_and_replace(const BsonDocument& filter_bson, const BsonDocument& replacement_bson, - ResponseHandler>&& completion) -{ - find_one_and_replace(filter_bson, replacement_bson, {}, std::move(completion)); -} - -void MongoCollection::find_one_and_delete(const BsonDocument& filter_bson, - const MongoCollection::FindOneAndModifyOptions& options, - ResponseHandler>&& completion) -{ - find_one_and_delete_bson(filter_bson, options, get_document_handler(std::move(completion))); -} - -void MongoCollection::find_one_and_delete(const BsonDocument& filter_bson, - ResponseHandler>&& completion) -{ - find_one_and_delete(filter_bson, {}, std::move(completion)); -} - -void MongoCollection::call_function(const char* name, const bson::BsonDocument& arg, - ResponseHandler>&& completion) -{ - m_service->call_function(m_user, name, BsonArray({arg}), m_service_name, std::move(completion)); -} - -static void set_options(BsonDocument& base_args, const MongoCollection::FindOptions& options) -{ - if (options.limit) { - base_args["limit"] = *options.limit; - } - - if (options.projection_bson) { - base_args["project"] = *options.projection_bson; - } - - if (options.sort_bson) { - base_args["sort"] = *options.sort_bson; - } -} - -void MongoCollection::find_bson(const BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion) -try { - auto base_args = m_base_operation_args; - base_args["query"] = filter_bson; - set_options(base_args, options); - - call_function("find", base_args, std::move(completion)); -} -catch (const std::exception& e) { - return completion(util::none, AppError(ErrorCodes::MalformedJson, e.what())); -} - -void MongoCollection::find_one_bson(const BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion) -try { - auto base_args = m_base_operation_args; - base_args["query"] = filter_bson; - set_options(base_args, options); - call_function("findOne", base_args, std::move(completion)); -} -catch (const std::exception& e) { - return completion(util::none, AppError(ErrorCodes::MalformedJson, e.what())); -} - -void MongoCollection::insert_one_bson(const BsonDocument& value_bson, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["document"] = value_bson; - call_function("insertOne", base_args, std::move(completion)); -} - -void MongoCollection::aggregate_bson(const BsonArray& pipline, ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["pipeline"] = pipline; - call_function("aggregate", base_args, std::move(completion)); -} - -void MongoCollection::count_bson(const BsonDocument& filter_bson, int64_t limit, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["query"] = filter_bson; - if (limit != 0) { - base_args["limit"] = limit; - } - call_function("count", base_args, std::move(completion)); -} - -void MongoCollection::insert_many_bson(const BsonArray& documents, ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["documents"] = documents; - call_function("insertMany", base_args, std::move(completion)); -} - -void MongoCollection::delete_one_bson(const BsonDocument& filter_bson, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["query"] = filter_bson; - call_function("deleteOne", base_args, std::move(completion)); -} - -void MongoCollection::delete_many_bson(const BsonDocument& filter_bson, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["query"] = filter_bson; - call_function("deleteMany", base_args, std::move(completion)); -} - -void MongoCollection::update_one_bson(const BsonDocument& filter_bson, const BsonDocument& update_bson, bool upsert, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["query"] = filter_bson; - base_args["update"] = update_bson; - base_args["upsert"] = upsert; - call_function("updateOne", base_args, std::move(completion)); -} - -void MongoCollection::update_many_bson(const BsonDocument& filter_bson, const BsonDocument& update_bson, bool upsert, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["query"] = filter_bson; - base_args["update"] = update_bson; - base_args["upsert"] = upsert; - call_function("updateMany", base_args, std::move(completion)); -} - -void MongoCollection::find_one_and_update_bson(const BsonDocument& filter_bson, const BsonDocument& update_bson, - const MongoCollection::FindOneAndModifyOptions& options, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["filter"] = filter_bson; - base_args["update"] = update_bson; - options.set_bson(base_args); - call_function("findOneAndUpdate", base_args, std::move(completion)); -} - -void MongoCollection::find_one_and_replace_bson(const BsonDocument& filter_bson, const BsonDocument& replacement_bson, - const MongoCollection::FindOneAndModifyOptions& options, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["filter"] = filter_bson; - base_args["update"] = replacement_bson; - options.set_bson(base_args); - call_function("findOneAndReplace", base_args, std::move(completion)); -} - -void MongoCollection::find_one_and_delete_bson(const BsonDocument& filter_bson, - const MongoCollection::FindOneAndModifyOptions& options, - ResponseHandler>&& completion) -{ - auto base_args = m_base_operation_args; - base_args["filter"] = filter_bson; - options.set_bson(base_args); - call_function("findOneAndDelete", base_args, std::move(completion)); -} - -void WatchStream::feed_buffer(std::string_view input) -{ - REALM_ASSERT(m_state == NEED_DATA); - m_buffer += input; - advance_buffer_state(); -} - -void WatchStream::advance_buffer_state() -{ - REALM_ASSERT(m_state == NEED_DATA); - while (m_state == NEED_DATA) { - if (m_buffer_offset == m_buffer.size()) { - m_buffer.clear(); - m_buffer_offset = 0; - return; - } - - // NOTE not supporting CR-only newlines, just LF and CRLF. - auto next_newline = m_buffer.find('\n', m_buffer_offset); - if (next_newline == std::string::npos) { - // We have a partial line. - if (m_buffer_offset != 0) { - // Slide the partial line down to the front of the buffer. - m_buffer.assign(m_buffer.data() + m_buffer_offset, m_buffer.size() - m_buffer_offset); - m_buffer_offset = 0; - } - return; - } - - feed_line(std::string_view(m_buffer.data() + m_buffer_offset, next_newline - m_buffer_offset)); - m_buffer_offset = next_newline + 1; // Advance past this line, including its newline. - } -} - -void WatchStream::feed_line(std::string_view line) -{ - REALM_ASSERT(m_state == NEED_DATA); - // This is an implementation of the algorithm described at - // https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation. - // Currently the server does not use id or retry lines, so that processing isn't implemented. - - // ignore trailing LF if not removed by SDK. - if (!line.empty() && line.back() == '\n') - line = line.substr(0, line.size() - 1); - - // ignore trailing CR from CRLF - if (!line.empty() && line.back() == '\r') - line = line.substr(0, line.size() - 1); - - if (line.empty()) { - // This is the "dispatch the event" portion of the algorithm. - if (m_data_buffer.empty()) { - m_event_type.clear(); - return; - } - - if (m_data_buffer.back() == '\n') - m_data_buffer.pop_back(); - - feed_sse({m_data_buffer, m_event_type}); - m_data_buffer.clear(); - m_event_type.clear(); - return; - } - - if (line[0] == ':') - return; - - const auto colon = line.find(':'); - const auto field = line.substr(0, colon); - auto value = colon == std::string::npos ? std::string_view() : line.substr(colon + 1); - if (!value.empty() && value[0] == ' ') - value = value.substr(1); - - if (field == "event") { - m_event_type = value; - } - else if (field == "data") { - m_data_buffer += value; - m_data_buffer += '\n'; - } - else { - // line is ignored (even if field is id or retry). - } -} - -void WatchStream::feed_sse(ServerSentEvent sse) -{ - REALM_ASSERT(m_state == NEED_DATA); - std::string buffer; // must outlast if-block since we bind sse.data to it. - size_t first_percent = sse.data.find('%'); - if (first_percent != std::string::npos) { - // For some reason, the stich server decided to add percent-encoding for '%', '\n', and '\r' to its - // event-stream replies. But it isn't real urlencoding, since most characters pass through, so we can't use - // uri_percent_decode() here. - buffer.reserve(sse.data.size()); - size_t start = 0; - while (true) { - auto percent = start == 0 ? first_percent : sse.data.find('%', start); - if (percent == std::string::npos) { - buffer += sse.data.substr(start); - break; - } - - buffer += sse.data.substr(start, percent - start); - - auto encoded = sse.data.substr(percent, 3); // may be smaller than 3 if string ends with % - if (encoded == "%25") { - buffer += '%'; - } - else if (encoded == "%0A") { - buffer += '\x0A'; // '\n' - } - else if (encoded == "%0D") { - buffer += '\x0D'; // '\r' - } - else { - buffer += encoded; // propagate as-is - } - start = percent + encoded.size(); - } - - sse.data = buffer; - } - - if (sse.eventType.empty() || sse.eventType == "message") { - try { - auto parsed = parse(sse.data); - if (parsed.type() == Bson::Type::Document) { - m_next_event = parsed.operator const BsonDocument&(); - m_state = HAVE_EVENT; - return; - } - } - catch (...) { - // fallthrough to same handling as for non-document value. - } - m_state = HAVE_ERROR; - m_error = std::make_unique(ErrorCodes::BadBsonParse, - "server returned malformed event: " + std::string(sse.data)); - } - else if (sse.eventType == "error") { - m_state = HAVE_ERROR; - - // default error message if we have issues parsing the reply. - m_error = std::make_unique(ErrorCodes::AppUnknownError, std::string(sse.data)); - try { - auto parsed = parse(sse.data); - if (parsed.type() != Bson::Type::Document) - return; - auto& obj = static_cast(parsed); - auto& code = obj.at("error_code"); - auto& msg = obj.at("error"); - if (code.type() != Bson::Type::String) - return; - if (msg.type() != Bson::Type::String) - return; - auto error_code = ErrorCodes::from_string(static_cast(code)); - if (error_code == ErrorCodes::UnknownError) - error_code = ErrorCodes::AppUnknownError; - m_error = std::make_unique(error_code, std::move(static_cast(msg))); - } - catch (...) { - return; // Use the default state. - } - } - else { - // Ignore other event types - } -} -} // namespace app -} // namespace realm diff --git a/src/realm/object-store/sync/mongo_collection.hpp b/src/realm/object-store/sync/mongo_collection.hpp deleted file mode 100644 index 0884459abc6..00000000000 --- a/src/realm/object-store/sync/mongo_collection.hpp +++ /dev/null @@ -1,469 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef MONGO_COLLECTION_HPP -#define MONGO_COLLECTION_HPP - -#include -#include -#include - -#include -#include - -namespace realm { - -namespace app { -class AppServiceClient; -class User; -struct AppError; - -class MongoCollection { -public: - struct UpdateResult { - /// The number of documents that matched the filter. - int32_t matched_count; - /// The number of documents modified. - int32_t modified_count; - /// The identifier of the inserted document if an upsert took place. - util::Optional upserted_id; - }; - - /// Options to use when executing a `find` command on a `MongoCollection`. - struct FindOptions { - /// The maximum number of documents to return. - util::Optional limit; - - /// Limits the fields to return for all matching documents. - util::Optional projection_bson; - - /// The order in which to return matching documents. - util::Optional sort_bson; - }; - - /// Options to use when executing a `find_one_and_update`, `find_one_and_replace`, - /// or `find_one_and_delete` command on a `mongo_collection`. - struct FindOneAndModifyOptions { - /// Limits the fields to return for all matching documents. - util::Optional projection_bson; - /// The order in which to return matching documents. - util::Optional sort_bson; - /// Whether or not to perform an upsert, default is false - /// (only available for find_one_and_replace and find_one_and_update) - bool upsert = false; - /// If this is true then the new document is returned, - /// Otherwise the old document is returned (default) - /// (only available for find_one_and_replace and find_one_and_update) - bool return_new_document = false; - - void set_bson(bson::BsonDocument& bson) const - { - if (upsert) { - bson["upsert"] = true; - } - - if (return_new_document) { - bson["returnNewDocument"] = true; - } - - if (projection_bson) { - bson["projection"] = *projection_bson; - } - - if (sort_bson) { - bson["sort"] = *sort_bson; - } - } - }; - - ~MongoCollection() = default; - MongoCollection(MongoCollection&&) = default; - MongoCollection(const MongoCollection&) = default; - MongoCollection& operator=(const MongoCollection& v) = default; - MongoCollection& operator=(MongoCollection&&) = default; - - const std::string& name() const - { - return m_name; - } - - const std::string& database_name() const - { - return m_database_name; - } - - template - using ResponseHandler = util::UniqueFunction)>; - - /// Finds the documents in this collection which match the provided filter. - /// @param filter_bson A `Document` as bson that should match the query. - /// @param options `FindOptions` to use when executing the command. - /// @param completion The resulting bson array of documents or error if one occurs - void find(const bson::BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion); - - /// Finds the documents in this collection which match the provided filter. - /// @param filter_bson A `Document` as bson that should match the query. - /// @param completion The resulting bson array as a string or error if one occurs - void find(const bson::BsonDocument& filter_bson, ResponseHandler>&& completion); - - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// @param filter_bson A `Document` as bson that should match the query. - /// @param options `FindOptions` to use when executing the command. - /// @param completion The resulting bson or error if one occurs - void find_one(const bson::BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion); - - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// @param filter_bson A `Document` as bson that should match the query. - /// @param completion The resulting bson or error if one occurs - void find_one(const bson::BsonDocument& filter_bson, - ResponseHandler>&& completion); - - /// Runs an aggregation framework pipeline against this collection. - /// @param pipeline A bson array made up of `Documents` containing the pipeline of aggregation operations to - /// perform. - /// @param completion The resulting bson array of documents or error if one occurs - void aggregate(const bson::BsonArray& pipeline, ResponseHandler>&& completion); - - /// Counts the number of documents in this collection matching the provided filter. - /// @param filter_bson A `Document` as bson that should match the query. - /// @param limit The max amount of documents to count - /// @param completion Returns the count of the documents that matched the filter. - void count(const bson::BsonDocument& filter_bson, int64_t limit, ResponseHandler&& completion); - - /// Counts the number of documents in this collection matching the provided filter. - /// @param filter_bson A `Document` as bson that should match the query. - /// @param completion Returns the count of the documents that matched the filter. - void count(const bson::BsonDocument& filter_bson, ResponseHandler&& completion); - - /// Encodes the provided value to BSON and inserts it. If the value is missing an identifier, one will be - /// generated for it. - /// @param value_bson A `Document` value to insert. - /// @param completion The result of attempting to perform the insert. An Id will be returned for the - /// inserted object on sucess - void insert_one(const bson::BsonDocument& value_bson, ResponseHandler>&& completion); - - /// Encodes the provided values to BSON and inserts them. If any values are missing identifiers, - /// they will be generated. - /// @param documents The `Document` values in a bson array to insert. - /// @param completion The result of the insert, returns an array inserted document ids in order - void insert_many(const bson::BsonArray& documents, ResponseHandler&& completion); - - /// Deletes a single matching document from the collection. - /// @param filter_bson A `Document` as bson that should match the query. - /// @param completion The result of performing the deletion. Returns the count of deleted objects - void delete_one(const bson::BsonDocument& filter_bson, ResponseHandler&& completion); - - /// Deletes multiple documents - /// @param filter_bson Document representing the match criteria - /// @param completion The result of performing the deletion. Returns the count of the deletion - void delete_many(const bson::BsonDocument& filter_bson, ResponseHandler&& completion); - - /// Updates a single document matching the provided filter in this collection. - /// @param filter_bson A bson `Document` representing the match criteria. - /// @param update_bson A bson `Document` representing the update to be applied to a matching document. - /// @param upsert When true, creates a new document if no document matches the query. - /// @param completion The result of the attempt to update a document. - void update_one(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, bool upsert, - ResponseHandler&& completion); - - /// Updates a single document matching the provided filter in this collection. - /// @param filter_bson A bson `Document` representing the match criteria. - /// @param update_bson A bson `Document` representing the update to be applied to a matching document. - /// @param completion The result of the attempt to update a document. - void update_one(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, - ResponseHandler&& completion); - - /// Updates multiple documents matching the provided filter in this collection. - /// @param filter_bson A bson `Document` representing the match criteria. - /// @param update_bson A bson `Document` representing the update to be applied to a matching document. - /// @param upsert When true, creates a new document if no document matches the query. - /// @param completion The result of the attempt to update a document. - void update_many(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, bool upsert, - ResponseHandler&& completion); - - /// Updates multiple documents matching the provided filter in this collection. - /// @param filter_bson A bson `Document` representing the match criteria. - /// @param update_bson A bson `Document` representing the update to be applied to a matching document. - /// @param completion The result of the attempt to update a document. - void update_many(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, - ResponseHandler&& completion); - - /// Updates a single document in a collection based on a query filter and - /// returns the document in either its pre-update or post-update form. Unlike - /// `update_one`, this action allows you to atomically find, update, and - /// return a document with the same command. This avoids the risk of other - /// update operations changing the document between separate find and update - /// operations. - /// @param filter_bson A bson `Document` representing the match criteria. - /// @param update_bson A bson `Document` representing the update to be applied to a matching document. - /// @param options Optional `FindOneAndModifyOptions` to use when executing the command. - /// @param completion The result of the attempt to update a document. - void find_one_and_update(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, - const FindOneAndModifyOptions& options, - ResponseHandler>&& completion); - - /// Updates a single document in a collection based on a query filter and - /// returns the document in either its pre-update or post-update form. Unlike - /// `update_one`, this action allows you to atomically find, update, and - /// return a document with the same command. This avoids the risk of other - /// update operations changing the document between separate find and update - /// operations. - /// @param filter_bson A bson `Document` representing the match criteria. - /// @param update_bson A bson `Document` representing the update to be applied to a matching document. - /// @param completion The result of the attempt to update a document. - void find_one_and_update(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, - ResponseHandler>&& completion); - - /// Overwrites a single document in a collection based on a query filter and - /// returns the document in either its pre-replacement or post-replacement - /// form. Unlike `update_one`, this action allows you to atomically find, - /// replace, and return a document with the same command. This avoids the - /// risk of other update operations changing the document between separate - /// find and update operations. - /// @param filter_bson A `Document` that should match the query. - /// @param replacement_bson A `Document` describing the update. - /// @param options Optional `FindOneAndModifyOptions` to use when executing the command. - /// @param completion The result of the attempt to replace a document. - void find_one_and_replace(const bson::BsonDocument& filter_bson, const bson::BsonDocument& replacement_bson, - const FindOneAndModifyOptions& options, - ResponseHandler>&& completion); - - /// Overwrites a single document in a collection based on a query filter and - /// returns the document in either its pre-replacement or post-replacement - /// form. Unlike `update_one`, this action allows you to atomically find, - /// replace, and return a document with the same command. This avoids the - /// risk of other update operations changing the document between separate - /// find and update operations. - /// @param filter_bson A `Document` that should match the query. - /// @param replacement_bson A `Document` describing the update. - /// @param completion The result of the attempt to replace a document. - void find_one_and_replace(const bson::BsonDocument& filter_bson, const bson::BsonDocument& replacement_bson, - ResponseHandler>&& completion); - - /// Removes a single document from a collection based on a query filter and - /// returns a document with the same form as the document immediately before - /// it was deleted. Unlike `delete_one`, this action allows you to atomically - /// find and delete a document with the same command. This avoids the risk of - /// other update operations changing the document between separate find and - /// delete operations. - /// @param filter_bson A `Document` that should match the query. - /// @param options Optional `FindOneAndModifyOptions` to use when executing the command. - /// @param completion The result of the attempt to delete a document. - void find_one_and_delete(const bson::BsonDocument& filter_bson, const FindOneAndModifyOptions& options, - ResponseHandler>&& completion); - - /// Removes a single document from a collection based on a query filter and - /// returns a document with the same form as the document immediately before - /// it was deleted. Unlike `delete_one`, this action allows you to atomically - /// find and delete a document with the same command. This avoids the risk of - /// other update operations changing the document between separate find and - /// delete operations. - /// @param filter_bson A `Document` that should match the query. - /// @param completion The result of the attempt to delete a document. - void find_one_and_delete(const bson::BsonDocument& filter_bson, - ResponseHandler>&& completion); - - // The following methods are equivalent to the ones without _bson suffix with the exception - // that they return the raw bson response from the function instead of attempting to parse it. - - void find_bson(const bson::BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion); - - void find_one_bson(const bson::BsonDocument& filter_bson, const FindOptions& options, - ResponseHandler>&& completion); - - void aggregate_bson(const bson::BsonArray& pipeline, ResponseHandler>&& completion); - - void count_bson(const bson::BsonDocument& filter_bson, int64_t limit, - ResponseHandler>&& completion); - - void insert_one_bson(const bson::BsonDocument& value_bson, - ResponseHandler>&& completion); - - void insert_many_bson(const bson::BsonArray& documents, ResponseHandler>&& completion); - - void delete_one_bson(const bson::BsonDocument& filter_bson, - ResponseHandler>&& completion); - - void delete_many_bson(const bson::BsonDocument& filter_bson, - ResponseHandler>&& completion); - - void update_one_bson(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, bool upsert, - ResponseHandler>&& completion); - - void update_many_bson(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, bool upsert, - ResponseHandler>&& completion); - - void find_one_and_update_bson(const bson::BsonDocument& filter_bson, const bson::BsonDocument& update_bson, - const FindOneAndModifyOptions& options, - ResponseHandler>&& completion); - - void find_one_and_replace_bson(const bson::BsonDocument& filter_bson, const bson::BsonDocument& replacement_bson, - const FindOneAndModifyOptions& options, - ResponseHandler>&& completion); - - void find_one_and_delete_bson(const bson::BsonDocument& filter_bson, const FindOneAndModifyOptions& options, - ResponseHandler>&& completion); - - /* - * SDKs should also support a watch method with the following 3 overloads: - * watch() - * watch(ids: List) - * watch(filter: BsonDocument) - * - * In all cases, an asynchronous stream should be returned or a multi-shot - * callback should be accepted depending on the idioms in your language. - * The argument to send the server are a single BsonDocument, either empty - * or with a single kv-pair for the argument name and value of the selected - * overload. - * - * See the WatchStream class below for how to implement this stream. - */ - -private: - friend class MongoDatabase; - - MongoCollection(const std::string& name, const std::string& database_name, const std::shared_ptr& user, - const std::shared_ptr& service, const std::string& service_name); - - void call_function(const char* name, const bson::BsonDocument& arg, - ResponseHandler>&& completion); - - /// The name of this collection. - std::string m_name; - - /// The name of the database containing this collection. - std::string m_database_name; - - /// Returns a document of database name and collection name - bson::BsonDocument m_base_operation_args; - - std::shared_ptr m_user; - - std::shared_ptr m_service; - - std::string m_service_name; -}; - -/** - * Simplifies the handling the stream for collection.watch() API. - * - * General pattern for languages with pull-based async generators (preferred): - * auto request = app.make_streaming_request("watch", ...); - * auto reply = await doHttpRequestUsingNativeLibs(request); - * if (reply.error) - * throw reply.error; - * auto ws = WatchStream(); - * for await (chunk : reply.body) { - * ws.feedBuffer(chunk); - * while (ws.state == WatchStream::HAVE_EVENT) { - * yield ws.nextEvent(); - * } - * if (ws.state == WatchStream::HAVE_ERROR) - * throw ws.error; - * } - * - * General pattern for languages with only push-based streams: - * auto request = app.make_streaming_request("watch", ...); - * doHttpRequestUsingNativeLibs(request, { - * .onError = [downstream](error) { downstream.onError(error); }, - * .onHeadersDone = [downstream](reply) { - * if (reply.error) - * downstream.onError(error); - * }, - * .onBodyChunk = [downstream, ws = WatchStream()](chunk) { - * ws.feedBuffer(chunk); - * while (ws.state == WatchStream::HAVE_EVENT) { - * downstream.nextEvent(ws.nextEvent()); - * } - * if (ws.state == WatchStream::HAVE_ERROR) - * downstream.onError(ws.error); - * } - * }); - */ -struct WatchStream { - // NOTE: this is a fully processed event, not a single "data: foo" line! - struct ServerSentEvent { - std::string_view data; - std::string_view eventType = "message"; - }; - - // Call these when you have data, in whatever shape is easiest for your SDK to get. - // Pick one, mixing and matching on a single instance isn't supported. - // These can only be called in NEED_DATA state, which is the initial state. - void feed_buffer(std::string_view); // May have multiple and/or partial lines. - void feed_line(std::string_view); // May include terminating CR and/or LF (not required). - void feed_sse(ServerSentEvent); // Only interested in "message" and "error" events. Others are ignored. - - // Call state() to see what to do next. - enum State { - NEED_DATA, // Need to call one of the feed functions. - HAVE_EVENT, // Call next_event() to consume an event. - HAVE_ERROR, // Call error(). - }; - State state() const - { - return m_state; - } - - // Consumes the returned event. If you used feed_buffer(), there may be another event or error after this one, - // so you need to call state() again to see what to do next. - bson::BsonDocument next_event() - { - REALM_ASSERT(m_state == HAVE_EVENT); - auto out = std::move(m_next_event); - m_state = NEED_DATA; - advance_buffer_state(); - return out; - } - - // Once this enters the error state, it stays that way. You should not feed any more data. - const app::AppError& error() const - { - REALM_ASSERT(m_state == HAVE_ERROR); - return *m_error; - } - -private: - void advance_buffer_state(); - - State m_state = NEED_DATA; - std::unique_ptr m_error; - bson::BsonDocument m_next_event; - - // Used by feed_buffer to construct lines - std::string m_buffer; - size_t m_buffer_offset = 0; - - // Used by feed_line for building the next SSE - std::string m_event_type; - std::string m_data_buffer; -}; - -} // namespace app -} // namespace realm - -#endif /* mongo_collection_h */ diff --git a/src/realm/object-store/sync/mongo_database.cpp b/src/realm/object-store/sync/mongo_database.cpp deleted file mode 100644 index 18e9cfb287d..00000000000 --- a/src/realm/object-store/sync/mongo_database.cpp +++ /dev/null @@ -1,34 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include - -namespace realm::app { - -MongoCollection MongoDatabase::collection(const std::string& collection_name) -{ - return MongoCollection(collection_name, m_name, m_user, m_service, m_service_name); -} - -MongoCollection MongoDatabase::operator[](const std::string& collection_name) -{ - return MongoCollection(collection_name, m_name, m_user, m_service, m_service_name); -} - -} // namespace realm::app diff --git a/src/realm/object-store/sync/mongo_database.hpp b/src/realm/object-store/sync/mongo_database.hpp deleted file mode 100644 index 255aacacfa4..00000000000 --- a/src/realm/object-store/sync/mongo_database.hpp +++ /dev/null @@ -1,74 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_MONGO_DATABASE_HPP -#define REALM_OS_MONGO_DATABASE_HPP - -#include -#include - -namespace realm::app { -class AppServiceClient; -class MongoCollection; -class User; - -class MongoDatabase { -public: - ~MongoDatabase() = default; - MongoDatabase(const MongoDatabase&) = default; - MongoDatabase(MongoDatabase&&) = default; - MongoDatabase& operator=(const MongoDatabase&) = default; - MongoDatabase& operator=(MongoDatabase&&) = default; - - /// The name of this database - const std::string& name() const - { - return m_name; - } - - /// Gets a collection. - /// @param collection_name The name of the collection to return - /// @returns The collection as json - MongoCollection collection(const std::string& collection_name); - - /// Gets a collection. - /// @param collection_name The name of the collection to return - /// @returns The collection as json - MongoCollection operator[](const std::string& collection_name); - -private: - MongoDatabase(std::string name, std::shared_ptr user, std::shared_ptr service, - std::string service_name) - : m_name(std::move(name)) - , m_user(std::move(user)) - , m_service(std::move(service)) - , m_service_name(std::move(service_name)) - { - } - - friend class MongoClient; - - std::string m_name; - std::shared_ptr m_user; - std::shared_ptr m_service; - std::string m_service_name; -}; - -} // namespace realm::app - -#endif /* REALM_OS_MONGO_DATABASE_HPP */ diff --git a/src/realm/object-store/sync/push_client.cpp b/src/realm/object-store/sync/push_client.cpp deleted file mode 100644 index 3e2e52fd26b..00000000000 --- a/src/realm/object-store/sync/push_client.cpp +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include - -#include -#include -#include -#include - -namespace realm::app { - -PushClient::~PushClient() = default; - -void PushClient::request(const std::shared_ptr& user, HttpMethod method, std::string&& body, - util::UniqueFunction)>&& completion) -{ - auto push_route = util::format("/app/%1/push/providers/%2/registration", m_app_id, m_service_name); - std::string route = m_auth_request_client->url_for_path(push_route); - m_auth_request_client->do_authenticated_request(method, std::move(route), std::move(body), user, - RequestTokenType::AccessToken, - [completion = std::move(completion)](const Response& response) { - completion(AppUtils::check_for_errors(response)); - }); -} - -void PushClient::register_device(const std::string& registration_token, const std::shared_ptr& user, - util::UniqueFunction)>&& completion) -{ - bson::BsonDocument args{{"registrationToken", registration_token}}; - request(user, HttpMethod::put, bson::Bson(args).to_string(), std::move(completion)); -} - -void PushClient::deregister_device(const std::shared_ptr& user, - util::UniqueFunction)>&& completion) -{ - request(user, HttpMethod::del, "", std::move(completion)); -} - -} // namespace realm::app diff --git a/src/realm/object-store/sync/push_client.hpp b/src/realm/object-store/sync/push_client.hpp deleted file mode 100644 index 346ffd717bb..00000000000 --- a/src/realm/object-store/sync/push_client.hpp +++ /dev/null @@ -1,73 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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 utilied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_PUSH_CLIENT_HPP -#define REALM_OS_PUSH_CLIENT_HPP - -#include -#include - -namespace realm::app { -class AuthRequestClient; -struct AppError; -class User; - -class PushClient { -public: - PushClient(const std::string& service_name, const std::string& app_id, - std::shared_ptr&& auth_request_client) - : m_service_name(service_name) - , m_app_id(app_id) - , m_auth_request_client(std::move(auth_request_client)) - { - } - - ~PushClient(); - PushClient(const PushClient&) = default; - PushClient(PushClient&&) = default; - PushClient& operator=(const PushClient&) = default; - PushClient& operator=(PushClient&&) = default; - - - /// Register a device for push notifications. - /// @param registration_token GCM registration token for the device. - /// @param user The sync user requesting push registration. - /// @param completion An error will be returned should something go wrong. - void register_device(const std::string& registration_token, const std::shared_ptr& user, - util::UniqueFunction)>&& completion); - - - /// Deregister a device for push notificatons, no token or device id needs to be passed - /// as it is linked to the user in MongoDB Realm Cloud. - /// @param user The sync user requesting push degistration. - /// @param completion An error will be returned should something go wrong. - void deregister_device(const std::shared_ptr& user, - util::UniqueFunction)>&& completion); - -private: - std::string m_service_name; - std::string m_app_id; - std::shared_ptr m_auth_request_client; - - void request(const std::shared_ptr& user, HttpMethod method, std::string&& body, - util::UniqueFunction)>&& completion); -}; - -} // namespace realm::app - -#endif /* REALM_OS_PUSH_CLIENT_HPP */ diff --git a/src/realm/object-store/sync/subscribable.hpp b/src/realm/object-store/sync/subscribable.hpp deleted file mode 100644 index 84b1e5c58d8..00000000000 --- a/src/realm/object-store/sync/subscribable.hpp +++ /dev/null @@ -1,161 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_SUBSCRIBABLE_HPP -#define REALM_SUBSCRIBABLE_HPP - -#include -#include -#include -#include -#include - -namespace realm { - -/// Generic subscribable that allows for coarse, manual notifications -/// from class type T. -template -struct Subscribable { - /// Token that identifies an observer. - /// Unsubscribes when deconstructed to - /// avoid dangling observers. - struct Token { - Token(Subscribable* subscribable, uint64_t token) - : m_subscribable(subscribable) - , m_token(token) - { - } - Token(Token&& other) noexcept - : m_subscribable(std::move(other.m_subscribable)) - , m_token(std::move(other.m_token)) - { - other.m_subscribable = nullptr; - } - Token& operator=(Token&& other) noexcept - { - m_subscribable = std::move(other.m_subscribable); - m_token = std::move(other.m_token); - other.m_subscribable = nullptr; - return *this; - } - Token(const Token&) = delete; - Token& operator=(const Token&) = delete; - - ~Token() - { - if (m_subscribable) { - m_subscribable->unsubscribe(*this); - } - } - - uint64_t value() const - { - return m_token; - } - - private: - Subscribable* m_subscribable; - uint64_t m_token; - - template - friend struct Subscribable; - }; - - using Observer = std::function; - - Subscribable() = default; - Subscribable(const Subscribable& other) - { - std::scoped_lock lock(m_mutex, other.m_mutex); - m_subscribers = other.m_subscribers; - } - Subscribable(Subscribable&& other) noexcept - { - std::scoped_lock lock(m_mutex, other.m_mutex); - m_subscribers = std::move(other.m_subscribers); - } - Subscribable& operator=(const Subscribable& other) - { - if (&other == this) { - return *this; - } - std::scoped_lock lock(m_mutex, other.m_mutex); - m_subscribers = other.m_subscribers; - return *this; - } - Subscribable& operator=(Subscribable&& other) noexcept - { - if (&other == this) { - return *this; - } - std::scoped_lock lock(m_mutex, other.m_mutex); - m_subscribers = std::move(other.m_subscribers); - return *this; - } - /// Subscribe to notifications for class type T. Any mutation to the T class - /// will trigger the observer. Notifying subscribers must be done manually - /// by the Subscribable. - /// @param observer callback to be called on mutation - /// @returns a token identifying the observer - [[nodiscard]] Token subscribe(Observer&& observer) - { - std::lock_guard lock(m_mutex); - static std::atomic s_token = 0; - m_subscribers.insert({s_token, std::move(observer)}); - return Token{this, s_token++}; - } - - /// Unsubscribe to notifications for this Subscribable using the - /// token returned when calling `subscribe`. - /// @param token the token identifying the observer. - void unsubscribe(const Token& token) - { - std::lock_guard lock(m_mutex); - m_subscribers.erase(token.m_token); - } - - /// A count of subscribers subscribed to class T. - /// @return the amount of subscribers subscribed to class T. - size_t subscribers_count() const - { - std::lock_guard lock(m_mutex); - return m_subscribers.size(); - } - -protected: - /// Emit a change event to all subscribers. - void emit_change_to_subscribers(const T& subject) const - { - std::unordered_map subscribers; - { - std::lock_guard lock(m_mutex); - subscribers = m_subscribers; - } - for (const auto& [_, subscriber] : subscribers) { - subscriber(subject); - } - } - -private: - mutable std::mutex m_mutex; - std::unordered_map m_subscribers; -}; - -} // namespace realm - -#endif /* REALM_SUBSCRIBABLE_HPP */ diff --git a/src/realm/object-store/sync/sync_manager.cpp b/src/realm/object-store/sync/sync_manager.cpp deleted file mode 100644 index d63de0e5986..00000000000 --- a/src/realm/object-store/sync/sync_manager.cpp +++ /dev/null @@ -1,422 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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 - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -using namespace realm; -using namespace realm::_impl; - -SyncClientTimeouts::SyncClientTimeouts() - : connect_timeout(sync::default_connect_timeout) - , connection_linger_time(sync::default_connection_linger_time) - , ping_keepalive_period(sync::default_ping_keepalive_period) - , pong_keepalive_timeout(sync::default_pong_keepalive_timeout) - , fast_reconnect_limit(sync::default_fast_reconnect_limit) -{ -} - -std::shared_ptr SyncManager::create(const SyncClientConfig& config) -{ - return std::make_shared(Private(), config); -} - -SyncManager::SyncManager(Private, const SyncClientConfig& config) - : m_config(config) -{ - // create the initial logger - if the logger_factory is updated later, a new - // logger will be created at that time. - do_make_logger(); -} - -void SyncManager::tear_down_for_testing() -{ - close_all_sessions(); - - { - util::CheckedLockGuard lock(m_mutex); - // Stop the client. This will abort any uploads that inactive sessions are waiting for. - if (m_sync_client) - m_sync_client->stop(); - } - - { - util::CheckedUniqueLock lock(m_session_mutex); - - bool no_sessions = !do_has_existing_sessions(); - // There's a race between this function and sessions tearing themselves down waiting for m_session_mutex. - // So we give up to a 5 second grace period for any sessions being torn down to unregister themselves. - auto since_poll_start = [start = std::chrono::steady_clock::now()] { - return std::chrono::steady_clock::now() - start; - }; - for (; !no_sessions && since_poll_start() < std::chrono::seconds(5); - no_sessions = !do_has_existing_sessions()) { - lock.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - lock.lock(); - } - // Callers of `SyncManager::tear_down_for_testing` should ensure there are no existing sessions - // prior to calling `tear_down_for_testing`. - if (!no_sessions) { - util::CheckedLockGuard lock(m_mutex); - for (auto session : m_sessions) { - m_logger_ptr->error("open session at path '%1'", session.first); - } - } - REALM_ASSERT_RELEASE(no_sessions); - - // Destroy any inactive sessions. - // FIXME: We shouldn't have any inactive sessions at this point! Sessions are expected to - // remain inactive until their final upload completes, at which point they are unregistered - // and destroyed. Our call to `sync::Client::stop` above aborts all uploads, so all sessions - // should have already been destroyed. - m_sessions.clear(); - } - - { - util::CheckedLockGuard lock(m_mutex); - // Destroy the client now that we have no remaining sessions. - m_sync_client.reset(); - m_logger_ptr.reset(); - } -} - -void SyncManager::set_log_level(util::Logger::Level level) noexcept -{ - util::CheckedLockGuard lock(m_mutex); - m_config.log_level = level; - // Update the level threshold in the already created logger - if (m_logger_ptr) { - m_logger_ptr->set_level_threshold(level); - } -} - -void SyncManager::set_logger_factory(SyncClientConfig::LoggerFactory factory) -{ - util::CheckedLockGuard lock(m_mutex); - m_config.logger_factory = std::move(factory); - - if (m_sync_client) - throw LogicError(ErrorCodes::IllegalOperation, - "Cannot set the logger factory after creating the sync client"); - - // Create a new logger using the new factory - do_make_logger(); -} - -void SyncManager::do_make_logger() -{ - if (m_config.logger_factory) { - m_logger_ptr = m_config.logger_factory(m_config.log_level); - } - else { - m_logger_ptr = util::Logger::get_default_logger(); - } - REALM_ASSERT(m_logger_ptr); -} - -std::shared_ptr SyncManager::get_logger() const -{ - util::CheckedLockGuard lock(m_mutex); - return m_logger_ptr; -} - -void SyncManager::set_user_agent(std::string user_agent) -{ - util::CheckedLockGuard lock(m_mutex); - m_config.user_agent_application_info = std::move(user_agent); -} - -void SyncManager::set_timeouts(SyncClientTimeouts timeouts) -{ - util::CheckedLockGuard lock(m_mutex); - m_config.timeouts = timeouts; -} - -void SyncManager::reconnect() const -{ - util::CheckedLockGuard lock(m_session_mutex); - for (auto& it : m_sessions) { - it.second->handle_reconnect(); - } -} - -util::Logger::Level SyncManager::log_level() const noexcept -{ - util::CheckedLockGuard lock(m_mutex); - return m_config.log_level; -} - -SyncManager::~SyncManager() NO_THREAD_SAFETY_ANALYSIS -{ - // Grab the current sessions under a lock so we can shut them down. We have to - // release the lock before calling them as shutdown_and_wait() will call - // back into us. - decltype(m_sessions) current_sessions; - { - util::CheckedLockGuard lk(m_session_mutex); - m_sessions.swap(current_sessions); - } - - for (auto& [_, session] : current_sessions) { - session->detach_from_sync_manager(); - } - - { - util::CheckedLockGuard lk(m_mutex); - // Stop the client. This will abort any uploads that inactive sessions are waiting for. - if (m_sync_client) - m_sync_client->stop(); - } -} - -std::vector> SyncManager::get_all_sessions() const -{ - util::CheckedLockGuard lock(m_session_mutex); - std::vector> sessions; - for (auto& [_, session] : m_sessions) { - if (auto external_reference = session->existing_external_reference()) - sessions.push_back(std::move(external_reference)); - } - return sessions; -} - -std::vector> SyncManager::get_all_sessions_for(const SyncUser& user) const -{ - util::CheckedLockGuard lock(m_session_mutex); - std::vector> sessions; - for (auto& [_, session] : m_sessions) { - if (session->user().get() == &user) { - if (auto external_reference = session->existing_external_reference()) - sessions.push_back(std::move(external_reference)); - } - } - return sessions; -} - -std::shared_ptr SyncManager::get_existing_active_session(const std::string& path) const -{ - util::CheckedLockGuard lock(m_session_mutex); - if (auto session = get_existing_session_locked(path)) { - if (auto external_reference = session->existing_external_reference()) - return external_reference; - } - return nullptr; -} - -std::shared_ptr SyncManager::get_existing_session_locked(const std::string& path) const -{ - auto it = m_sessions.find(path); - return it == m_sessions.end() ? nullptr : it->second; -} - -std::shared_ptr SyncManager::get_existing_session(const std::string& path) const -{ - util::CheckedLockGuard lock(m_session_mutex); - if (auto session = get_existing_session_locked(path)) - return session->external_reference(); - - return nullptr; -} - -std::shared_ptr SyncManager::get_session(std::shared_ptr db, const RealmConfig& config) -{ - auto& client = get_sync_client(); // Throws -#ifndef __EMSCRIPTEN__ - auto path = db->get_path(); - REALM_ASSERT_EX(path == config.path, path, config.path); -#else - auto path = config.path; -#endif - REALM_ASSERT(config.sync_config); - - util::CheckedUniqueLock lock(m_session_mutex); - if (auto session = get_existing_session_locked(path)) { - return session->external_reference(); - } - - auto shared_session = SyncSession::create(client, std::move(db), config, this); - m_sessions[path] = shared_session; - - // Create the external reference immediately to ensure that the session will become - // inactive if an exception is thrown in the following code. - return shared_session->external_reference(); -} - -bool SyncManager::has_existing_sessions() -{ - util::CheckedLockGuard lock(m_session_mutex); - return do_has_existing_sessions(); -} - -bool SyncManager::do_has_existing_sessions() -{ - return std::any_of(m_sessions.begin(), m_sessions.end(), [](auto& element) { - return element.second->existing_external_reference(); - }); -} - -void SyncManager::wait_for_sessions_to_terminate() -{ - auto& client = get_sync_client(); // Throws - client.wait_for_session_terminations(); -} - -void SyncManager::unregister_session(const std::string& path) -{ - util::CheckedUniqueLock lock(m_session_mutex); - auto it = m_sessions.find(path); - if (it == m_sessions.end()) { - // The session may already be unregistered. This always happens in the - // SyncManager destructor, and can also happen due to multiple threads - // tearing things down at once. - return; - } - - // Sync session teardown calls this function, so we need to be careful with - // locking here. We need to unlock `m_session_mutex` before we do anything - // which could result in a re-entrant call or we'll deadlock, which in this - // function means unlocking before we destroy a `shared_ptr` - // (either the external reference or internal reference versions). - // The external reference version will only be the final reference if - // another thread drops a reference while we're in this function. - // Dropping the final internal reference does not appear to ever actually - // result in a recursive call to this function at the time this comment was - // written, but releasing the lock in that case as well is still safer. - - if (auto existing_session = it->second->existing_external_reference()) { - // We got here because the session entered the inactive state, but - // there's still someone referencing it so we should leave it be. This - // can happen if the user was logged out, or if all Realms using the - // session were destroyed but the SDK user is holding onto the session. - - // Explicit unlock so that `existing_session`'s destructor runs after - // the unlock for the reasons noted above - lock.unlock(); - return; - } - - // Remove the session from the map while holding the lock, but then defer - // destroying it until after we unlock the mutex for the reasons noted above. - auto session = m_sessions.extract(it); - lock.unlock(); -} - -void SyncManager::update_sessions_for(SyncUser& user, SyncUser::State old_state, SyncUser::State new_state, - std::string_view new_access_token) -{ - bool should_revive = old_state != SyncUser::State::LoggedIn && new_state == SyncUser::State::LoggedIn; - bool should_stop = old_state == SyncUser::State::LoggedIn && new_state != SyncUser::State::LoggedIn; - - auto sessions = get_all_sessions_for(user); - if (new_access_token.size()) { - for (auto& session : sessions) { - session->update_access_token(new_access_token); - } - } - else if (should_revive) { - for (auto& session : sessions) { - session->revive_if_needed(); - } - } - else if (should_stop) { - for (auto& session : sessions) { - session->force_close(); - } - } -} - -void SyncManager::set_session_multiplexing(bool allowed) -{ - util::CheckedLockGuard lock(m_mutex); - if (m_config.multiplex_sessions == allowed) - return; // Already enabled, we can ignore - - if (m_sync_client) - throw LogicError(ErrorCodes::IllegalOperation, - "Cannot enable session multiplexing after creating the sync client"); - - m_config.multiplex_sessions = allowed; -} - -SyncClient& SyncManager::get_sync_client() const -{ - util::CheckedLockGuard lock(m_mutex); - if (!m_sync_client) - m_sync_client = create_sync_client(); // Throws - return *m_sync_client; -} - -std::unique_ptr SyncManager::create_sync_client() const -{ - REALM_ASSERT(m_logger_ptr); - return std::make_unique(m_logger_ptr, m_config, weak_from_this()); -} - -void SyncManager::close_all_sessions() -{ - // force_close() will call unregister_session(), which requires m_session_mutex, - // so we need to iterate over them without holding the lock. - decltype(m_sessions) sessions; - { - util::CheckedLockGuard lk(m_session_mutex); - m_sessions.swap(sessions); - } - - for (auto& [_, session] : sessions) { - session->force_close(); - } - - get_sync_client().wait_for_session_terminations(); -} - -void SyncManager::set_sync_route(std::string sync_route, bool verified) -{ - REALM_ASSERT(!sync_route.empty()); // Cannot be set to empty string - { - util::CheckedLockGuard lock(m_mutex); - m_sync_route = sync_route; - m_sync_route_verified = verified; - } -} - -void SyncManager::restart_all_sessions() -{ - // Restart the sessions that are currently active - auto sessions = get_all_sessions(); - for (auto& session : sessions) { - session->restart_session(); - } -} - -void SyncManager::OnlyForTesting::voluntary_disconnect_all_connections(SyncManager& mgr) -{ - mgr.get_sync_client().voluntary_disconnect_all_connections(); -} diff --git a/src/realm/object-store/sync/sync_manager.hpp b/src/realm/object-store/sync/sync_manager.hpp deleted file mode 100644 index 18654368c65..00000000000 --- a/src/realm/object-store/sync/sync_manager.hpp +++ /dev/null @@ -1,196 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_SYNC_MANAGER_HPP -#define REALM_OS_SYNC_MANAGER_HPP - -#include -#include -#include - -#include - -class TestAppSession; -class TestSyncManager; - -namespace realm { - -class DB; -struct SyncConfig; -struct RealmConfig; -class SyncSession; - -namespace _impl { -struct SyncClient; -} - -class SyncManager : public std::enable_shared_from_this { - struct Private {}; - -public: - // Enables/disables using a single connection for all sync sessions for each host/port/user rather - // than one per session. - // This must be called before any sync sessions are created and cannot be - // disabled afterwards. - void set_session_multiplexing(bool allowed) REQUIRES(!m_mutex); - - // Destroys the sync manager, terminates all sessions created by it, and stops its SyncClient. - ~SyncManager(); - - // Sets the log level for the Sync Client. - // The log level can only be set up until the point the Sync Client is - // created (when the first Session is created) or an App operation is - // performed (e.g. log in). - void set_log_level(util::Logger::Level) noexcept REQUIRES(!m_mutex); - void set_logger_factory(SyncClientConfig::LoggerFactory) REQUIRES(!m_mutex); - - // Sets the application level user agent string. - // This should have the format specified here: - // https://github.com/realm/realm-sync/blob/develop/src/realm/sync/client.hpp#L126 The user agent can only be set - // up until the point the Sync Client is created. This happens when the first Session is created. - void set_user_agent(std::string user_agent) REQUIRES(!m_mutex); - - // Sets client timeout settings. - // The timeout settings can only be set up until the point the Sync Client is created. - // This happens when the first Session is created. - void set_timeouts(SyncClientTimeouts timeouts) REQUIRES(!m_mutex); - - /// Ask all valid sync sessions to perform whatever tasks might be necessary to - /// re-establish connectivity with the Realm Object Server. It is presumed that - /// the caller knows that network connectivity has been restored. - /// - /// Refer to `SyncSession::handle_reconnect()` to see what sort of work is done - /// on a per-session basis. - void reconnect() const REQUIRES(!m_session_mutex); - - util::Logger::Level log_level() const noexcept REQUIRES(!m_mutex); - - std::vector> get_all_sessions() const REQUIRES(!m_session_mutex); - std::vector> get_all_sessions_for(const SyncUser& user) const - REQUIRES(!m_session_mutex); - std::shared_ptr get_session(std::shared_ptr db, const RealmConfig& config) - REQUIRES(!m_mutex, !m_session_mutex); - std::shared_ptr get_existing_session(const std::string& path) const REQUIRES(!m_session_mutex); - std::shared_ptr get_existing_active_session(const std::string& path) const - REQUIRES(!m_session_mutex); - - // Returns `true` if the SyncManager still contains any existing sessions not yet fully cleaned up. - // This will return true as long as there is an external reference to a session object, no matter - // the state of that session. - bool has_existing_sessions() REQUIRES(!m_session_mutex); - - // Blocking call that only return once all sessions have been terminated. - // Due to the async nature of the SyncClient, even with `SyncSessionStopPolicy::Immediate`, a - // session is not guaranteed to stop immediately when a Realm is closed. Using this method - // makes it possible to guarantee that all sessions have, in fact, been closed. - void wait_for_sessions_to_terminate() REQUIRES(!m_mutex); - - // DO NOT CALL OUTSIDE OF TESTING CODE. - // Forcibly close all remaining sync sessions, stop the sync client, and - // discard all state. The SyncManager must never be used again after this - // function has been called (note: not after it has returned). - void tear_down_for_testing() REQUIRES(!m_mutex, !m_session_mutex); - - // Immediately closes any open sync sessions for this sync manager - void close_all_sessions() REQUIRES(!m_mutex, !m_session_mutex); - - // Force all the active sessions to restart - void restart_all_sessions() REQUIRES(!m_mutex, !m_session_mutex); - - // Update all sessions for a given user following a state change for that - // user (and optionally a new access token) - void update_sessions_for(SyncUser& user, SyncUser::State old_state, SyncUser::State new_state, - std::string_view new_access_token) REQUIRES(!m_mutex, !m_session_mutex); - - - // Used by App to update the sync route any time the location info has been refreshed. - // m_sync_route starts out as a generated value based on the configured base_url when - // the SyncManager is created by App. If this is incorrect, the websocket connection - // will fail, resulting in an update to the access token (and the location, if it hasn't - // been updated yet). - void set_sync_route(std::string sync_route, bool verified) REQUIRES(!m_mutex); - - std::pair sync_route() REQUIRES(!m_mutex) - { - util::CheckedLockGuard lock(m_mutex); - return {m_sync_route, m_sync_route_verified}; - } - - SyncClientConfig config() const REQUIRES(!m_mutex) - { - util::CheckedLockGuard lock(m_mutex); - return m_config; - } - - // Return the cached logger - std::shared_ptr get_logger() const REQUIRES(!m_mutex); - - struct OnlyForTesting { - friend class TestHelper; - - static void voluntary_disconnect_all_connections(SyncManager&); - }; - - static std::shared_ptr create(const SyncClientConfig& config); - SyncManager(Private, const SyncClientConfig& config); - -private: - friend class SyncSession; - friend class SyncUser; - friend class ::TestSyncManager; - friend class ::TestAppSession; - - util::CheckedMutex m_mutex; - mutable std::unique_ptr<_impl::SyncClient> m_sync_client GUARDED_BY(m_mutex); - SyncClientConfig m_config GUARDED_BY(m_mutex); - std::shared_ptr m_logger_ptr GUARDED_BY(m_mutex); - // The sync route URL for the sync connection to the server. - std::string m_sync_route GUARDED_BY(m_mutex); - // If true, then the sync route has been verified by querying the location info or successfully - // connecting to the server. - bool m_sync_route_verified GUARDED_BY(m_mutex) = false; - - // Map of sessions by path name. - // Sessions remove themselves from this map by calling `unregister_session` once they're - // inactive and have performed any necessary cleanup work. - util::CheckedMutex m_session_mutex; - std::unordered_map> m_sessions GUARDED_BY(m_session_mutex); - - // Stop tracking the session for the given path if it is inactive. - // No-op if the session is either still active or in the active sessions list - // due to someone holding a strong reference to it. - void unregister_session(const std::string& path) REQUIRES(!m_session_mutex); - - _impl::SyncClient& get_sync_client() const REQUIRES(!m_mutex); - std::unique_ptr<_impl::SyncClient> create_sync_client() const REQUIRES(m_mutex); - - std::shared_ptr get_existing_session_locked(const std::string& path) const REQUIRES(m_session_mutex); - - void init_metadata(SyncClientConfig config, const std::string& app_id); - - // internally create a new logger - used by configure() and set_logger_factory() - void do_make_logger() REQUIRES(m_mutex); - - // Internal method returning `true` if the SyncManager still contains sessions not yet fully closed. - // Callers of this method should hold the `m_session_mutex` themselves. - bool do_has_existing_sessions() REQUIRES(m_session_mutex); -}; - -} // namespace realm - -#endif // REALM_OS_SYNC_MANAGER_HPP diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp deleted file mode 100644 index 2cf2489e55a..00000000000 --- a/src/realm/object-store/sync/sync_session.cpp +++ /dev/null @@ -1,1744 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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 - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace realm; -using namespace realm::_impl; - -using SessionWaiterPointer = void (sync::Session::*)(util::UniqueFunction); - -constexpr const char SyncError::c_original_file_path_key[]; -constexpr const char SyncError::c_recovery_file_path_key[]; - -/// STATES: -/// -/// WAITING_FOR_ACCESS_TOKEN: a request has been initiated to ask -/// for an updated access token and the session is waiting for a response. -/// From: INACTIVE, DYING -/// To: -/// * ACTIVE: when the SDK successfully refreshes the token -/// * INACTIVE: if asked to log out, or if asked to close -/// -/// ACTIVE: the session is connected to the Sync Server and is actively -/// transferring data. -/// From: INACTIVE, DYING, WAITING_FOR_ACCESS_TOKEN -/// To: -/// * INACTIVE: if asked to log out, or if asked to close and the stop policy -/// is Immediate. -/// * DYING: if asked to close and the stop policy is AfterChangesUploaded -/// -/// DYING: the session is performing clean-up work in preparation to be destroyed. -/// From: ACTIVE -/// To: -/// * INACTIVE: when the clean-up work completes, if the session wasn't -/// revived, or if explicitly asked to log out before the -/// clean-up work begins -/// * ACTIVE: if the session is revived -/// * WAITING_FOR_ACCESS_TOKEN: if the session tried to enter ACTIVE, -/// but the token is invalid or expired. -/// -/// INACTIVE: the user owning this session has logged out, the `sync::Session` -/// owned by this session is destroyed, and the session is quiescent. -/// Note that a session briefly enters this state before being destroyed, but -/// it can also enter this state and stay there if the user has been logged out. -/// From: initial, ACTIVE, DYING, WAITING_FOR_ACCESS_TOKEN -/// To: -/// * ACTIVE: if the session is revived -/// * WAITING_FOR_ACCESS_TOKEN: if the session tried to enter ACTIVE, -/// but the token is invalid or expired. - -void SyncSession::become_active() -{ - REALM_ASSERT(m_state != State::Active); - m_state = State::Active; - - // First time the session becomes active, register a notification on the sentinel subscription set to restart the - // session and update to native FLX. - if (m_migration_sentinel_query_version) { - m_flx_subscription_store->get_by_version(*m_migration_sentinel_query_version) - .get_state_change_notification(sync::SubscriptionSet::State::Complete) - .get_async([=, weak_self = weak_from_this()](StatusWith s) { - if (!s.is_ok()) { - return; - } - REALM_ASSERT(s.get_value() == sync::SubscriptionSet::State::Complete); - if (auto strong_self = weak_self.lock()) { - strong_self->m_migration_store->cancel_migration(); - strong_self->restart_session(); - } - }); - m_migration_sentinel_query_version.reset(); - } - - // when entering from the Dying state the session will still be bound - create_sync_session(); - - // Register all the pending wait-for-completion blocks. This can - // potentially add a redundant callback if we're coming from the Dying - // state, but that's okay (we won't call the user callbacks twice). - SyncSession::CompletionCallbacks callbacks_to_register; - std::swap(m_completion_callbacks, callbacks_to_register); - - for (auto& [id, callback_tuple] : callbacks_to_register) { - add_completion_callback(std::move(callback_tuple.second), callback_tuple.first); - } -} - -void SyncSession::become_dying(util::CheckedUniqueLock lock) -{ - REALM_ASSERT(m_state != State::Dying); - m_state = State::Dying; - - // If we have no session, we cannot possibly upload anything. - if (!m_session) { - become_inactive(std::move(lock)); - return; - } - - size_t current_death_count = ++m_death_count; - m_session->async_wait_for_upload_completion([weak_session = weak_from_this(), current_death_count](Status) { - if (auto session = weak_session.lock()) { - util::CheckedUniqueLock lock(session->m_state_mutex); - if (session->m_state == State::Dying && session->m_death_count == current_death_count) { - session->become_inactive(std::move(lock)); - } - } - }); - m_state_mutex.unlock(lock); -} - -void SyncSession::become_inactive(util::CheckedUniqueLock lock, Status status, bool cancel_subscription_notifications) -{ - REALM_ASSERT(m_state != State::Inactive); - m_state = State::Inactive; - - do_become_inactive(std::move(lock), status, cancel_subscription_notifications); -} - -void SyncSession::become_paused(util::CheckedUniqueLock lock) -{ - REALM_ASSERT(m_state != State::Paused); - auto old_state = m_state; - m_state = State::Paused; - - // Nothing to do if we're already inactive besides update the state. - if (old_state == State::Inactive) { - m_state_mutex.unlock(lock); - return; - } - - do_become_inactive(std::move(lock), Status::OK(), true); -} - -void SyncSession::restart_session() -{ - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - do_restart_session(std::move(lock)); - break; - case State::WaitingForAccessToken: - case State::Paused: - case State::Dying: - case State::Inactive: - return; - } -} - -void SyncSession::do_restart_session(util::CheckedUniqueLock) -{ - // Go straight to inactive so the progress completion waiters will - // continue to wait until the session restarts and completes the - // upload/download sync - m_state = State::Inactive; - - if (m_session) { - m_session.reset(); - } - - // Create a new session and re-register the completion callbacks - // The latest server path will be retrieved from sync_manager when - // the new session is created by create_sync_session() in become - // active. - become_active(); -} - -void SyncSession::do_become_inactive(util::CheckedUniqueLock lock, Status status, - bool cancel_subscription_notifications) -{ - // Manually set the disconnected state. Sync would also do this, but - // since the underlying SyncSession object already have been destroyed, - // we are not able to get the callback. - util::CheckedUniqueLock connection_state_lock(m_connection_state_mutex); - auto old_state = m_connection_state; - auto new_state = m_connection_state = SyncSession::ConnectionState::Disconnected; - connection_state_lock.unlock(); - - SyncSession::CompletionCallbacks waits; - std::swap(waits, m_completion_callbacks); - - m_session = nullptr; - if (m_sync_manager) { - m_sync_manager->unregister_session(m_db->get_path()); - } - - auto subscription_store = m_flx_subscription_store; - m_state_mutex.unlock(lock); - - // Send notifications after releasing the lock to prevent deadlocks in the callback. - if (old_state != new_state) { - m_connection_change_notifier.invoke_callbacks(old_state, connection_state()); - } - - if (status.is_ok()) - status = Status(ErrorCodes::OperationAborted, "Sync session became inactive"); - - if (subscription_store && cancel_subscription_notifications) { - subscription_store->notify_all_state_change_notifications(status); - } - - // Inform any queued-up completion handlers that they were cancelled. - for (auto& [id, callback] : waits) - callback.second(status); -} - -void SyncSession::become_waiting_for_access_token() -{ - REALM_ASSERT(m_state != State::WaitingForAccessToken); - m_state = State::WaitingForAccessToken; -} - -void SyncSession::handle_bad_auth(const std::shared_ptr& user, Status status) -{ - // TODO: ideally this would write to the logs as well in case users didn't set up their error handler. - { - util::CheckedUniqueLock lock(m_state_mutex); - cancel_pending_waits(std::move(lock), status); - } - if (user) { - user->request_log_out(); - } - - if (auto error_handler = config(&SyncConfig::error_handler)) { - auto user_facing_error = SyncError({ErrorCodes::AuthError, status.reason()}, true); - error_handler(shared_from_this(), std::move(user_facing_error)); - } -} - -static bool check_for_auth_failure(const app::AppError& error) -{ - using namespace realm::sync; - // Auth failure is returned as a 401 (unauthorized) or 403 (forbidden) response - if (error.additional_status_code) { - auto status_code = HTTPStatus(*error.additional_status_code); - if (status_code == HTTPStatus::Unauthorized || status_code == HTTPStatus::Forbidden) - return true; - } - - return false; -} - -static bool check_for_redirect_response(const app::AppError& error) -{ - using namespace realm::sync; - // Check for unhandled 301/308 permanent redirect response - if (error.additional_status_code) { - auto status_code = HTTPStatus(*error.additional_status_code); - if (status_code == HTTPStatus::MovedPermanently || status_code == HTTPStatus::PermanentRedirect) - return true; - } - - return false; -} - -util::UniqueFunction)> -SyncSession::handle_refresh(const std::shared_ptr& session, bool restart_session) -{ - return [session, restart_session](std::optional error) { - auto session_user = session->user(); - if (!session_user) { - util::CheckedUniqueLock lock(session->m_state_mutex); - auto refresh_error = error ? error->to_status() : Status::OK(); - session->cancel_pending_waits(std::move(lock), refresh_error); - } - else if (error) { - if (ErrorCodes::error_categories(error->code()).test(ErrorCategory::client_error)) { - // any other client errors other than app_deallocated are considered fatal because - // there was a problem locally before even sending the request to the server - // eg. ClientErrorCode::user_not_found, ClientErrorCode::user_not_logged_in, - // ClientErrorCode::too_many_redirects - session->handle_bad_auth(session_user, error->to_status()); - } - else if (check_for_auth_failure(*error)) { - // A 401 response on a refresh request means that the token cannot be refreshed and we should not - // retry. This can be because an admin has revoked this user's sessions, the user has been disabled, - // or the refresh token has expired according to the server's clock. - session->handle_bad_auth( - session_user, - {error->code(), util::format("Unable to refresh the user access token: %1", error->reason())}); - } - else if (check_for_redirect_response(*error)) { - // A 301 or 308 response is an unhandled permanent redirect response (which should not happen) - if - // this is received, fail the request with an appropriate error message. - // Temporary redirect responses (302, 307) are not supported - session->handle_bad_auth( - session_user, - {error->code(), util::format("Unhandled redirect response when trying to reach the server: %1", - error->reason())}); - } - else { - // A refresh request has failed. This is an unexpected non-fatal error and we would - // like to retry but we shouldn't do this immediately in order to not swamp the - // server with requests. Consider two scenarios: - // 1) If this request was spawned from the proactive token check, or a user - // initiated request, the token may actually be valid. Just advance to Active - // from WaitingForAccessToken if needed and let the sync server tell us if the - // token is valid or not. If this also fails we will end up in case 2 below. - // 2) If the sync connection initiated the request because the server is - // unavailable or the connection otherwise encounters an unexpected error, we want - // to let the sync client attempt to reinitialize the connection using its own - // internal backoff timer which will happen automatically so nothing needs to - // happen here. - util::CheckedUniqueLock lock(session->m_state_mutex); - // If updating access token while opening realm, just become active at this point - // and try to use the current access token. - if (session->m_state == State::WaitingForAccessToken) { - session->become_active(); - } - // If `cancel_waits_on_nonfatal_error` is true, then cancel the waiters and pass along the error - else if (session->config(&SyncConfig::cancel_waits_on_nonfatal_error)) { - session->cancel_pending_waits(std::move(lock), error->to_status()); // unlocks the mutex - } - } - } - else { - // If the session needs to be restarted, then restart the session now - // The latest access token and server url will be pulled from the sync - // manager when the new session is started. - if (restart_session) { - session->restart_session(); - } - // Otherwise, update the access token and reconnect - else { - session->update_access_token(session_user->access_token()); - } - } - }; -} - -SyncSession::SyncSession(Private, SyncClient& client, std::shared_ptr db, const RealmConfig& config, - SyncManager* sync_manager) - : m_config{config} - , m_db{std::move(db)} - , m_original_sync_config{m_config.sync_config} - , m_migration_store{sync::MigrationStore::create(m_db)} - , m_client(client) - , m_sync_manager(sync_manager) -{ - REALM_ASSERT(m_config.sync_config); - // we don't want the following configs enabled during a client reset - m_config.scheduler = nullptr; - m_config.audit_config = nullptr; - - // Adjust the sync_config if using PBS sync and already in the migrated or rollback state - if (m_migration_store->is_migrated() || m_migration_store->is_rollback_in_progress()) { - m_config.sync_config = sync::MigrationStore::convert_sync_config_to_flx(m_original_sync_config); - } - - // If using FLX, set up m_flx_subscription_store and the history_write_validator - if (m_config.sync_config->flx_sync_requested) { - create_subscription_store(); - std::weak_ptr weak_sub_mgr(m_flx_subscription_store); - set_write_validator_factory(weak_sub_mgr); - } - - // After a migration to FLX, if the user opens the realm with a flexible sync configuration, we need to first - // upload any unsynced changes before updating to native FLX. - // A subscription set is used as sentinel so we know when to stop uploading. - // Note: Currently, a sentinel subscription set is always created even if there is nothing to upload. - if (m_migration_store->is_migrated() && m_original_sync_config->flx_sync_requested) { - m_migration_store->create_sentinel_subscription_set(*m_flx_subscription_store); - m_migration_sentinel_query_version = m_migration_store->get_sentinel_subscription_set_version(); - REALM_ASSERT(m_migration_sentinel_query_version); - } -} - -void SyncSession::detach_from_sync_manager() -{ - shutdown_and_wait(); - util::CheckedLockGuard lk(m_state_mutex); - m_sync_manager = nullptr; -} - -void SyncSession::update_error_and_mark_file_for_deletion(SyncError& error, ShouldBackup should_backup) -{ - util::CheckedLockGuard config_lock(m_config_mutex); - // Add a SyncFileActionMetadata marking the Realm as needing to be deleted. - auto original_path = path(); - error.user_info[SyncError::c_original_file_path_key] = original_path; - using Action = SyncFileAction; - auto action = should_backup == ShouldBackup::yes ? Action::BackUpThenDeleteRealm : Action::DeleteRealm; - std::string recovery_path = m_config.sync_config->user->create_file_action( - action, original_path, m_config.sync_config->recovery_directory); - if (should_backup == ShouldBackup::yes) { - error.user_info[SyncError::c_recovery_file_path_key] = recovery_path; - } -} - -void SyncSession::download_fresh_realm(const sync::SessionErrorInfo& error_info) -{ - // first check that recovery will not be prevented - if (error_info.server_requests_action == sync::ProtocolErrorInfo::Action::ClientResetNoRecovery) { - auto mode = config(&SyncConfig::client_resync_mode); - if (mode == ClientResyncMode::Recover) { - handle_fresh_realm_downloaded( - nullptr, - {ErrorCodes::RuntimeError, - "A client reset is required but the server does not permit recovery for this client"}, - error_info); - return; - } - } - - std::vector encryption_key; - { - util::CheckedLockGuard lock(m_config_mutex); - encryption_key = m_config.encryption_key; - } - - DBOptions options; - options.allow_file_format_upgrade = false; - options.enable_async_writes = false; - if (!encryption_key.empty()) - options.encryption_key = encryption_key.data(); - - DBRef db; - auto fresh_path = client_reset::get_fresh_path_for(m_db->get_path()); - try { - // We want to attempt to use a pre-existing file to reduce the chance of - // downloading the first part of the file only to then delete it over - // and over, but if we fail to open it then we should just start over. - try { - db = DB::create(sync::make_client_replication(), fresh_path, options); - } - catch (...) { - util::File::try_remove(fresh_path); - } - - if (!db) { - db = DB::create(sync::make_client_replication(), fresh_path, options); - } - } - catch (...) { - // Failed to open the fresh path after attempting to delete it, so we - // just can't do automatic recovery. - handle_fresh_realm_downloaded(nullptr, exception_to_status(), error_info); - return; - } - - util::CheckedLockGuard state_lock(m_state_mutex); - if (m_state != State::Active) { - return; - } - - RealmConfig fresh_config; - { - util::CheckedLockGuard config_lock(m_config_mutex); - fresh_config = m_config; - fresh_config.path = fresh_path; - // in case of migrations use the migrated config - auto fresh_sync_config = m_migrated_sync_config ? *m_migrated_sync_config : *m_config.sync_config; - // deep copy the sync config so we don't modify the live session's config - fresh_config.sync_config = std::make_shared(fresh_sync_config); - fresh_config.sync_config->client_resync_mode = ClientResyncMode::Manual; - fresh_config.schema_version = m_previous_schema_version.value_or(m_config.schema_version); - } - - auto fresh_sync_session = m_sync_manager->get_session(db, fresh_config); - auto& history = static_cast(*db->get_replication()); - // the fresh Realm may apply writes to this db after it has outlived its sync session - // the writes are used to generate a changeset for recovery, but are never committed - history.set_write_validator_factory({}); - - fresh_sync_session->assert_mutex_unlocked(); - // The fresh realm uses flexible sync. - if (auto fresh_sub_store = fresh_sync_session->get_flx_subscription_store()) { - auto fresh_sub = fresh_sub_store->get_latest(); - // The local realm uses flexible sync as well so copy the active subscription set to the fresh realm. - if (auto local_subs_store = m_flx_subscription_store) { - auto fresh_mut_sub = fresh_sub.make_mutable_copy(); - fresh_mut_sub.import(local_subs_store->get_active()); - fresh_sub = fresh_mut_sub.commit(); - } - - auto self = shared_from_this(); - using SubscriptionState = sync::SubscriptionSet::State; - fresh_sub.get_state_change_notification(SubscriptionState::Complete) - .then([=](SubscriptionState) -> util::Future { - if (error_info.server_requests_action != sync::ProtocolErrorInfo::Action::MigrateToFLX) { - return fresh_sub; - } - if (!self->m_migration_store->is_migration_in_progress()) { - return fresh_sub; - } - - // fresh_sync_session is using a new realm file that doesn't have the migration_store info - // so the query string from the local migration store will need to be provided - auto query_string = self->m_migration_store->get_query_string(); - REALM_ASSERT(query_string); - // Create subscriptions in the fresh realm based on the schema instructions received in the bootstrap - // message. - fresh_sync_session->m_migration_store->create_subscriptions(*fresh_sub_store, *query_string); - return fresh_sub_store->get_latest() - .get_state_change_notification(SubscriptionState::Complete) - .then([fresh_sub_store](SubscriptionState) { - return fresh_sub_store->get_latest(); - }); - }) - .get_async([=](StatusWith&& subs) { - // Keep the sync session alive while it's downloading, but then close - // it immediately - fresh_sync_session->force_close(); - if (subs.is_ok()) { - self->handle_fresh_realm_downloaded(db, Status::OK(), error_info, std::move(subs.get_value())); - } - else { - self->handle_fresh_realm_downloaded(nullptr, std::move(subs.get_status()), error_info); - } - }); - } - else { // pbs - fresh_sync_session->wait_for_download_completion([=, weak_self = weak_from_this()](Status status) { - // Keep the sync session alive while it's downloading, but then close - // it immediately - fresh_sync_session->force_close(); - if (auto strong_self = weak_self.lock()) { - if (status.is_ok()) { - strong_self->handle_fresh_realm_downloaded(db, Status::OK(), error_info); - } - else { - strong_self->handle_fresh_realm_downloaded(nullptr, std::move(status), error_info); - } - } - }); - } - fresh_sync_session->revive_if_needed(); -} - -void SyncSession::handle_fresh_realm_downloaded(DBRef db, Status result, const sync::SessionErrorInfo& cr_error_info, - std::optional new_subs) -{ - util::CheckedUniqueLock lock(m_state_mutex); - if (m_state != State::Active) { - return; - } - // The download can fail for many reasons. For example: - // - unable to write the fresh copy to the file system - // - during download of the fresh copy, the fresh copy itself is reset - // - in FLX mode there was a problem fulfilling the previously active subscription - if (!result.is_ok()) { - if (result == ErrorCodes::OperationAborted) { - return; - } - lock.unlock(); - - sync::SessionErrorInfo synthetic( - Status{ErrorCodes::AutoClientResetFailed, - util::format("A fatal error occurred during '%1' client reset for %2: '%3'", - cr_error_info.server_requests_action, cr_error_info.status, result)}, - sync::IsFatal{true}); - handle_error(synthetic); - return; - } - - // Performing a client reset requires tearing down our current - // sync session and creating a new one with the relevant client reset config. This - // will result in session completion handlers firing - // when the old session is torn down, which we don't want as this - // is supposed to be transparent to the user. - // - // To avoid this, we need to move the completion handlers aside temporarily so - // that moving to the inactive state doesn't clear them - they will be - // re-registered when the session becomes active again. - { - m_client_reset_fresh_copy = db; - CompletionCallbacks callbacks; - // Save the client reset error for when the original sync session is revived - m_client_reset_error = cr_error_info; - - std::swap(m_completion_callbacks, callbacks); - // always swap back, even if advance_state throws - auto guard = util::make_scope_exit([&]() noexcept { - util::CheckedUniqueLock lock(m_state_mutex); - if (m_completion_callbacks.empty()) - std::swap(callbacks, m_completion_callbacks); - else - m_completion_callbacks.merge(std::move(callbacks)); - }); - // Do not cancel the notifications on subscriptions. - bool cancel_subscription_notifications = false; - bool is_migration = - m_client_reset_error->server_requests_action == sync::ProtocolErrorInfo::Action::MigrateToFLX || - m_client_reset_error->server_requests_action == sync::ProtocolErrorInfo::Action::RevertToPBS; - become_inactive(std::move(lock), Status::OK(), cancel_subscription_notifications); // unlocks the lock - - // Once the session is inactive, update sync config and subscription store after migration. - if (is_migration) { - apply_sync_config_after_migration_or_rollback(); - auto flx_sync_requested = config(&SyncConfig::flx_sync_requested); - update_subscription_store(flx_sync_requested, std::move(new_subs)); - } - } - revive_if_needed(); -} - -util::Future SyncSession::pause_async() -{ - { - util::CheckedUniqueLock lock(m_state_mutex); - // Nothing to wait for if the session is already paused or inactive. - if (m_state == SyncSession::State::Paused || m_state == SyncSession::State::Inactive) { - return util::Future::make_ready(); - } - } - // Transition immediately to `paused` state. Calling this function must guarantee that any - // sync::Session object in SyncSession::m_session that existed prior to the time of invocation - // must have been destroyed upon return. This allows the caller to follow up with a call to - // sync::Client::notify_session_terminated() in order to be notified when the Realm file is closed. This works - // so long as this SyncSession object remains in the `paused` state after the invocation of shutdown(). - pause(); - return m_client.notify_session_terminated(); -} - -void SyncSession::OnlyForTesting::handle_error(SyncSession& session, sync::SessionErrorInfo&& error) -{ - session.handle_error(std::move(error)); -} - -util::Future SyncSession::OnlyForTesting::pause_async(SyncSession& session) -{ - return session.pause_async(); -} - -// This method should only be called from within the error handler callback registered upon the underlying -// `m_session`. -void SyncSession::handle_error(sync::SessionErrorInfo error) -{ - enum class NextStateAfterError { none, inactive, error }; - auto next_state = error.is_fatal ? NextStateAfterError::error : NextStateAfterError::none; - std::optional delete_file; - bool log_out_user = false; - bool unrecognized_by_client = false; - - if (error.status == ErrorCodes::AutoClientResetFailed) { - // At this point, automatic recovery has been attempted but it failed. - // Fallback to a manual reset and let the user try to handle it. - next_state = NextStateAfterError::inactive; - delete_file = ShouldBackup::yes; - } - else if (error.server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) { - switch (error.server_requests_action) { - case sync::ProtocolErrorInfo::Action::NoAction: - REALM_UNREACHABLE(); // This is not sent by the MongoDB server - case sync::ProtocolErrorInfo::Action::ApplicationBug: - [[fallthrough]]; - case sync::ProtocolErrorInfo::Action::ProtocolViolation: - next_state = NextStateAfterError::inactive; - break; - case sync::ProtocolErrorInfo::Action::Warning: - break; // not fatal, but should be bubbled up to the user below. - case sync::ProtocolErrorInfo::Action::Transient: - // Not real errors, don't need to be reported to the binding. - return; - case sync::ProtocolErrorInfo::Action::DeleteRealm: - next_state = NextStateAfterError::inactive; - delete_file = ShouldBackup::no; - break; - case sync::ProtocolErrorInfo::Action::ClientReset: - [[fallthrough]]; - case sync::ProtocolErrorInfo::Action::ClientResetNoRecovery: - switch (config(&SyncConfig::client_resync_mode)) { - case ClientResyncMode::Manual: - next_state = NextStateAfterError::inactive; - delete_file = ShouldBackup::yes; - break; - case ClientResyncMode::DiscardLocal: - [[fallthrough]]; - case ClientResyncMode::RecoverOrDiscard: - [[fallthrough]]; - case ClientResyncMode::Recover: - download_fresh_realm(error); - return; // do not propagate the error to the user at this point - } - break; - case sync::ProtocolErrorInfo::Action::MigrateToFLX: - // Should not receive this error if original sync config is FLX - REALM_ASSERT(!m_original_sync_config->flx_sync_requested); - REALM_ASSERT(error.migration_query_string && !error.migration_query_string->empty()); - // Original config was PBS, migrating to FLX - m_migration_store->migrate_to_flx(*error.migration_query_string, - m_original_sync_config->partition_value); - save_sync_config_after_migration_or_rollback(); - download_fresh_realm(error); - return; - case sync::ProtocolErrorInfo::Action::RevertToPBS: - // If the client was updated to use FLX natively, but the server was rolled back to PBS, - // the server should be sending switch_to_flx_sync; throw exception if this error is not - // received. - if (m_original_sync_config->flx_sync_requested) { - throw LogicError(ErrorCodes::InvalidServerResponse, - "Received 'RevertToPBS' from server after rollback while client is natively " - "using FLX - expected 'SwitchToPBS'"); - } - // Original config was PBS, rollback the migration - m_migration_store->rollback_to_pbs(); - save_sync_config_after_migration_or_rollback(); - download_fresh_realm(error); - return; - case sync::ProtocolErrorInfo::Action::RefreshUser: - if (auto u = user()) { - u->request_access_token(handle_refresh(shared_from_this(), false)); - } - return; - case sync::ProtocolErrorInfo::Action::RefreshLocation: - if (auto u = user()) { - u->request_refresh_location(handle_refresh(shared_from_this(), true)); - } - return; - case sync::ProtocolErrorInfo::Action::LogOutUser: - next_state = NextStateAfterError::inactive; - log_out_user = true; - break; - case sync::ProtocolErrorInfo::Action::MigrateSchema: - util::CheckedUniqueLock lock(m_state_mutex); - // Should only be received for FLX sync. - REALM_ASSERT(m_original_sync_config->flx_sync_requested); - m_previous_schema_version = error.previous_schema_version; - return; // do not propagate the error to the user at this point - } - } - else { - // Unrecognized error code. - unrecognized_by_client = true; - } - - util::CheckedUniqueLock lock(m_state_mutex); - SyncError sync_error{error.status, error.is_fatal, error.log_url, std::move(error.compensating_writes)}; - // `action` is used over `shouldClientReset` and `isRecoveryModeDisabled`. - sync_error.server_requests_action = error.server_requests_action; - sync_error.is_unrecognized_by_client = unrecognized_by_client; - - if (delete_file) - update_error_and_mark_file_for_deletion(sync_error, *delete_file); - - if (m_state == State::Dying && error.is_fatal) { - become_inactive(std::move(lock), error.status); - return; - } - - // Don't bother invoking m_config.error_handler if the sync is inactive. - // It does not make sense to call the handler when the session is closed. - if (m_state == State::Inactive || m_state == State::Paused) { - return; - } - - switch (next_state) { - case NextStateAfterError::none: - if (config(&SyncConfig::cancel_waits_on_nonfatal_error)) { - cancel_pending_waits(std::move(lock), sync_error.status); // unlocks the mutex - } - break; - case NextStateAfterError::inactive: { - become_inactive(std::move(lock), sync_error.status); - break; - } - case NextStateAfterError::error: { - cancel_pending_waits(std::move(lock), sync_error.status); - break; - } - } - - if (log_out_user) { - if (auto u = user()) - u->request_log_out(); - } - - if (auto error_handler = config(&SyncConfig::error_handler)) { - error_handler(shared_from_this(), std::move(sync_error)); - } -} - -void SyncSession::cancel_pending_waits(util::CheckedUniqueLock lock, Status error) -{ - CompletionCallbacks callbacks; - std::swap(callbacks, m_completion_callbacks); - - // Inform any waiters on pending subscription states that they were cancelled - if (m_flx_subscription_store) { - auto subscription_store = m_flx_subscription_store; - m_state_mutex.unlock(lock); - subscription_store->notify_all_state_change_notifications(error); - } - else { - m_state_mutex.unlock(lock); - } - - // Inform any queued-up completion handlers that they were cancelled. - for (auto& [id, callback] : callbacks) - callback.second(error); -} - -void SyncSession::handle_progress_update(uint64_t downloaded, uint64_t downloadable, uint64_t uploaded, - uint64_t uploadable, uint64_t snapshot_version, double download_estimate, - double upload_estimate, int64_t query_version) -{ - m_progress_notifier.update(downloaded, downloadable, uploaded, uploadable, snapshot_version, download_estimate, - upload_estimate, query_version); -} - - -static sync::Session::Config::ClientReset -make_client_reset_config(const RealmConfig& base_config, const std::shared_ptr& sync_config, - DBRef&& fresh_copy, sync::SessionErrorInfo&& error_info, bool schema_migration_detected) -{ - REALM_ASSERT(sync_config->client_resync_mode != ClientResyncMode::Manual); - - sync::Session::Config::ClientReset config{sync_config->client_resync_mode, std::move(fresh_copy), - std::move(error_info.status), error_info.server_requests_action}; - - // The conditions here are asymmetric because if we have *either* a before - // or after callback we need to make sure to initialize the local schema - // before the client reset happens. - if (!sync_config->notify_before_client_reset && !sync_config->notify_after_client_reset) - return config; - - // We cannot initialize the local schema in case of a sync schema migration. - // Currently, a schema migration involves breaking changes so opening the realm - // with the new schema results in a crash. - if (schema_migration_detected) - return config; - - RealmConfig realm_config = base_config; - realm_config.sync_config = std::make_shared(*sync_config); // deep copy - realm_config.scheduler = util::Scheduler::make_dummy(); - - if (sync_config->notify_after_client_reset) { - config.notify_after_client_reset = [realm_config](VersionID previous_version, bool did_recover) { - auto coordinator = _impl::RealmCoordinator::get_coordinator(realm_config); - ThreadSafeReference active_after = coordinator->get_unbound_realm(); - SharedRealm frozen_before = coordinator->get_realm(realm_config, previous_version); - REALM_ASSERT(frozen_before); - REALM_ASSERT(frozen_before->is_frozen()); - realm_config.sync_config->notify_after_client_reset(std::move(frozen_before), std::move(active_after), - did_recover); - }; - } - config.notify_before_client_reset = [config = std::move(realm_config)]() -> VersionID { - // Opening the Realm live here may make a write if the schema is different - // than what exists on disk. It is necessary to pass a fully usable Realm - // to the user here. Note that the schema changes made here will be considered - // an "offline write" to be recovered if this is recovery mode. - auto before = Realm::get_shared_realm(config); - if (auto& notify_before = config.sync_config->notify_before_client_reset) { - notify_before(config.sync_config->freeze_before_reset_realm ? before->freeze() : before); - } - // Note that if the SDK wrote to the Realm (hopefully by requesting a - // live instance and not opening a secondary one), this may be a - // different version than what we had before calling the callback. - before->refresh(); - return before->read_transaction_version(); - }; - - return config; -} - -void SyncSession::create_sync_session() -{ - if (m_session) - return; - - util::CheckedLockGuard config_lock(m_config_mutex); - - REALM_ASSERT(m_config.sync_config); - SyncConfig& sync_config = *m_config.sync_config; - REALM_ASSERT(sync_config.user); - - std::weak_ptr weak_self = weak_from_this(); - - sync::Session::Config session_config; - session_config.signed_user_token = sync_config.user->access_token(); - session_config.user_id = sync_config.user->user_id(); - session_config.realm_identifier = sync_config.partition_value; - session_config.verify_servers_ssl_certificate = sync_config.client_validate_ssl; - session_config.ssl_trust_certificate_path = sync_config.ssl_trust_certificate_path; - session_config.ssl_verify_callback = sync_config.ssl_verify_callback; - session_config.proxy_config = sync_config.proxy_config; - session_config.simulate_integration_error = sync_config.simulate_integration_error; - session_config.flx_bootstrap_batch_size_bytes = sync_config.flx_bootstrap_batch_size_bytes; - session_config.fresh_realm_download = client_reset::is_fresh_path(m_config.path); - session_config.schema_version = m_config.schema_version; - - if (sync_config.on_sync_client_event_hook) { - session_config.on_sync_client_event_hook = [hook = sync_config.on_sync_client_event_hook, - weak_self](const SyncClientHookData& data) { - return hook(weak_self, data); - }; - } - - { - // At this point the sync route was either updated when the first App request was performed, or - // was populated by a generated value that will be used for first contact. If the generated sync - // route is not correct, either a redirection will be received or the connection will fail, - // resulting in an update to both the access token and the location. - auto [sync_route, verified] = m_sync_manager->sync_route(); - REALM_ASSERT_EX(!sync_route.empty(), "Server URL cannot be empty"); - - if (!m_client.decompose_server_url(sync_route, session_config.protocol_envelope, - session_config.server_address, session_config.server_port, - session_config.service_identifier)) { - throw sync::BadServerUrl(sync_route); - } - session_config.server_verified = verified; - - m_server_url = sync_route; - m_server_url_verified = verified; - } - - if (sync_config.authorization_header_name) { - session_config.authorization_header_name = *sync_config.authorization_header_name; - } - session_config.custom_http_headers = sync_config.custom_http_headers; - - if (m_client_reset_error) { - auto client_reset_error = std::exchange(m_client_reset_error, std::nullopt); - if (client_reset_error->server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) { - // Use the original sync config, not the updated one from the migration store - session_config.client_reset_config = - make_client_reset_config(m_config, m_original_sync_config, std::move(m_client_reset_fresh_copy), - std::move(*client_reset_error), m_previous_schema_version.has_value()); - session_config.schema_version = m_previous_schema_version.value_or(m_config.schema_version); - } - } - - session_config.progress_handler = [weak_self](uint_fast64_t downloaded, uint_fast64_t downloadable, - uint_fast64_t uploaded, uint_fast64_t uploadable, - uint_fast64_t snapshot_version, double download_estimate, - double upload_estimate, int64_t query_version) { - if (auto self = weak_self.lock()) { - self->handle_progress_update(downloaded, downloadable, uploaded, uploadable, snapshot_version, - download_estimate, upload_estimate, query_version); - } - }; - - session_config.connection_state_change_listener = [weak_self](sync::ConnectionState state, - std::optional error) { - using cs = sync::ConnectionState; - ConnectionState new_state = [&] { - switch (state) { - case cs::disconnected: - return ConnectionState::Disconnected; - case cs::connecting: - return ConnectionState::Connecting; - case cs::connected: - return ConnectionState::Connected; - } - REALM_UNREACHABLE(); - }(); - // If the OS SyncSession object is destroyed, we ignore any events from the underlying Session as there is - // nothing useful we can do with them. - if (auto self = weak_self.lock()) { - self->update_connection_state(new_state); - if (error) { - self->handle_error(std::move(*error)); - } - } - }; - - m_session = m_client.make_session(m_db, m_flx_subscription_store, m_migration_store, std::move(session_config)); -} - -void SyncSession::update_connection_state(ConnectionState new_state) -{ - if (new_state == ConnectionState::Connected) { - util::CheckedLockGuard lock(m_config_mutex); - m_server_url_verified = true; - } - - ConnectionState old_state; - { - util::CheckedLockGuard lock(m_connection_state_mutex); - old_state = m_connection_state; - m_connection_state = new_state; - } - - // Notify any registered connection callbacks of the state transition - if (old_state != new_state) { - m_connection_change_notifier.invoke_callbacks(old_state, new_state); - } -} - -void SyncSession::nonsync_transact_notify(sync::version_type version) -{ - m_progress_notifier.set_local_version(version); - - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - case State::WaitingForAccessToken: - if (m_session) { - m_session->nonsync_transact_notify(version); - } - break; - case State::Dying: - case State::Inactive: - case State::Paused: - break; - } -} - -void SyncSession::revive_if_needed() -{ - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - case State::WaitingForAccessToken: - case State::Paused: - return; - case State::Dying: - case State::Inactive: - do_revive(std::move(lock)); - break; - } -} - -void SyncSession::handle_reconnect() -{ - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - m_session->cancel_reconnect_delay(); - break; - case State::Dying: - case State::Inactive: - case State::WaitingForAccessToken: - case State::Paused: - break; - } -} - -void SyncSession::force_close() -{ - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - case State::Dying: - case State::WaitingForAccessToken: - become_inactive(std::move(lock)); - break; - case State::Inactive: - case State::Paused: - break; - } -} - -void SyncSession::pause() -{ - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - case State::Dying: - case State::WaitingForAccessToken: - case State::Inactive: - become_paused(std::move(lock)); - break; - case State::Paused: - break; - } -} - -void SyncSession::resume() -{ - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - case State::WaitingForAccessToken: - return; - case State::Paused: - case State::Dying: - case State::Inactive: - do_revive(std::move(lock)); - break; - } -} - -void SyncSession::do_revive(util::CheckedUniqueLock&& lock) -{ - auto u = user(); - // If the sync manager has a valid route and the user and it's access token - // are valid, then revive the session. - if (!u || !u->access_token_refresh_required()) { - become_active(); - m_state_mutex.unlock(lock); - return; - } - - // Otherwise, either the access token has expired or the location info hasn't - // been requested since the app was started - request a new access token to - // refresh both. - become_waiting_for_access_token(); - // Release the lock for SDKs with a single threaded - // networking implementation such as our test suite - // so that the update can trigger a state change from - // the completion handler. - m_state_mutex.unlock(lock); - initiate_access_token_refresh(); -} - -void SyncSession::close() -{ - util::CheckedUniqueLock lock(m_state_mutex); - close(std::move(lock)); -} - -void SyncSession::close(util::CheckedUniqueLock lock) -{ - switch (m_state) { - case State::Active: { - switch (config(&SyncConfig::stop_policy)) { - case SyncSessionStopPolicy::Immediately: - become_inactive(std::move(lock)); - break; - case SyncSessionStopPolicy::LiveIndefinitely: - // Don't do anything; session lives forever. - m_state_mutex.unlock(lock); - break; - case SyncSessionStopPolicy::AfterChangesUploaded: - // Wait for all pending changes to upload. - become_dying(std::move(lock)); - break; - } - break; - } - case State::Dying: - m_state_mutex.unlock(lock); - break; - case State::Paused: - case State::Inactive: { - // We need to unregister from the sync manager if it still exists so that we don't end up - // holding the DBRef open after the session is closed. Otherwise we can end up preventing - // the user from deleting the realm when it's in the paused/inactive state. - if (m_sync_manager) { - m_sync_manager->unregister_session(m_db->get_path()); - } - m_state_mutex.unlock(lock); - break; - } - case State::WaitingForAccessToken: - // Immediately kill the session. - become_inactive(std::move(lock)); - break; - } -} - -void SyncSession::shutdown_and_wait() -{ - { - // Transition immediately to `inactive` state. Calling this function must guarantee that any - // sync::Session object in SyncSession::m_session that existed prior to the time of invocation - // must have been destroyed upon return. This allows the caller to follow up with a call to - // sync::Client::wait_for_session_terminations_or_client_stopped() in order to wait for the - // Realm file to be closed. This works so long as this SyncSession object remains in the - // `inactive` state after the invocation of shutdown_and_wait(). - util::CheckedUniqueLock lock(m_state_mutex); - if (m_state != State::Inactive && m_state != State::Paused) { - become_inactive(std::move(lock)); - } - } - m_client.wait_for_session_terminations(); -} - -void SyncSession::update_access_token(std::string_view signed_token) -{ - util::CheckedUniqueLock lock(m_state_mutex); - switch (m_state) { - case State::Active: - m_session->refresh(signed_token); - break; - case State::WaitingForAccessToken: - become_active(); - break; - case State::Paused: - // token will be pulled from user when the session is unpaused - return; - case State::Dying: - case State::Inactive: - do_revive(std::move(lock)); - break; - } -} - -void SyncSession::initiate_access_token_refresh() -{ - if (auto session_user = user()) { - session_user->request_access_token(handle_refresh(shared_from_this(), false)); - } -} - -void SyncSession::add_completion_callback(util::UniqueFunction callback, ProgressDirection direction) -{ - bool is_download = (direction == ProgressDirection::download); - - m_completion_request_counter++; - m_completion_callbacks.emplace_hint(m_completion_callbacks.end(), m_completion_request_counter, - std::make_pair(direction, std::move(callback))); - // If the state is inactive then just store the callback and return. The callback will get - // re-registered with the underlying session if/when the session ever becomes active again. - if (!m_session) { - return; - } - - auto waiter = is_download ? &sync::Session::async_wait_for_download_completion - : &sync::Session::async_wait_for_upload_completion; - - (m_session.get()->*waiter)([weak_self = weak_from_this(), id = m_completion_request_counter](Status status) { - auto self = weak_self.lock(); - if (!self) - return; - util::CheckedUniqueLock lock(self->m_state_mutex); - auto callback_node = self->m_completion_callbacks.extract(id); - lock.unlock(); - if (callback_node) { - callback_node.mapped().second(std::move(status)); - } - }); -} - -void SyncSession::wait_for_upload_completion(util::UniqueFunction&& callback) -{ - util::CheckedUniqueLock lock(m_state_mutex); - add_completion_callback(std::move(callback), ProgressDirection::upload); -} - -void SyncSession::wait_for_download_completion(util::UniqueFunction&& callback) -{ - util::CheckedUniqueLock lock(m_state_mutex); - add_completion_callback(std::move(callback), ProgressDirection::download); -} - -uint64_t SyncSession::register_progress_notifier(std::function&& notifier, - ProgressDirection direction, bool is_streaming) -{ - int64_t pending_query_version = 0; - if (auto sub_store = get_flx_subscription_store()) { - pending_query_version = sub_store->get_version_info().latest; - } - return m_progress_notifier.register_callback(std::move(notifier), direction, is_streaming, pending_query_version); -} - -void SyncSession::unregister_progress_notifier(uint64_t token) -{ - m_progress_notifier.unregister_callback(token); -} - -uint64_t SyncSession::register_connection_change_callback(std::function&& callback) -{ - return m_connection_change_notifier.add_callback(std::move(callback)); -} - -void SyncSession::unregister_connection_change_callback(uint64_t token) -{ - m_connection_change_notifier.remove_callback(token); -} - -SyncSession::~SyncSession() {} - -SyncSession::State SyncSession::state() const -{ - util::CheckedUniqueLock lock(m_state_mutex); - return m_state; -} - -SyncSession::ConnectionState SyncSession::connection_state() const -{ - util::CheckedUniqueLock lock(m_connection_state_mutex); - return m_connection_state; -} - -std::string const& SyncSession::path() const -{ - return m_db->get_path(); -} - -std::shared_ptr SyncSession::get_flx_subscription_store() -{ - util::CheckedLockGuard lock(m_state_mutex); - return m_flx_subscription_store; -} - -std::shared_ptr SyncSession::get_subscription_store_base() -{ - util::CheckedLockGuard lock(m_state_mutex); - return m_subscription_store_base; -} - -sync::SaltedFileIdent SyncSession::get_file_ident() const -{ - auto repl = m_db->get_replication(); - REALM_ASSERT(repl); - REALM_ASSERT(dynamic_cast(repl)); - - sync::SaltedFileIdent ret; - sync::version_type unused_version; - sync::SyncProgress unused_progress; - static_cast(repl)->get_history().get_status(unused_version, ret, unused_progress); - return ret; -} - -std::string SyncSession::get_appservices_connection_id() const -{ - util::CheckedLockGuard lk(m_state_mutex); - if (!m_session) { - return {}; - } - return m_session->get_appservices_connection_id(); -} - -void SyncSession::update_configuration(SyncConfig new_config) -{ - while (true) { - util::CheckedUniqueLock state_lock(m_state_mutex); - if (m_state != State::Inactive && m_state != State::Paused) { - // Changing the state releases the lock, which means that by the - // time we reacquire the lock the state may have changed again - // (either due to one of the callbacks being invoked or another - // thread coincidentally doing something). We just attempt to keep - // switching it to inactive until it stays there. - become_inactive(std::move(state_lock)); - continue; - } - - util::CheckedUniqueLock config_lock(m_config_mutex); - REALM_ASSERT(m_state == State::Inactive || m_state == State::Paused); - REALM_ASSERT(!m_session); - REALM_ASSERT(m_config.sync_config->user == new_config.user); - // Since this is used for testing purposes only, just update the current sync_config - m_config.sync_config = std::make_shared(std::move(new_config)); - break; - } - revive_if_needed(); -} - -void SyncSession::apply_sync_config_after_migration_or_rollback() -{ - // Migration state changed - Update the configuration to - // match the new sync mode. - util::CheckedLockGuard cfg_lock(m_config_mutex); - if (!m_migrated_sync_config) - return; - - m_config.sync_config = m_migrated_sync_config; - m_migrated_sync_config.reset(); -} - -void SyncSession::save_sync_config_after_migration_or_rollback() -{ - util::CheckedLockGuard cfg_lock(m_config_mutex); - m_migrated_sync_config = m_migration_store->convert_sync_config(m_original_sync_config); -} - -void SyncSession::update_subscription_store(bool flx_sync_requested, std::optional new_subs) -{ - util::CheckedUniqueLock lock(m_state_mutex); - - // The session should be closed before updating the FLX subscription store - REALM_ASSERT(!m_session); - - // If the subscription store exists and switching to PBS, then clear the store - auto& history = static_cast(*m_db->get_replication()); - if (!flx_sync_requested) { - if (m_flx_subscription_store) { - // Empty the subscription store and cancel any pending subscription notification - // waiters - auto subscription_store = std::move(m_flx_subscription_store); - lock.unlock(); - auto tr = m_db->start_write(); - subscription_store->reset(*tr); - history.set_write_validator_factory(nullptr); - tr->commit(); - } - return; - } - - if (m_flx_subscription_store) - return; // Using FLX and subscription store already exists - - // Going from PBS -> FLX (or one doesn't exist yet), create a new subscription store - create_subscription_store(); - - std::weak_ptr weak_sub_mgr(m_flx_subscription_store); - - // If migrated to FLX, create subscriptions in the local realm to cover the existing data. - // This needs to be done before setting the write validator to avoid NoSubscriptionForWrite errors. - if (new_subs) { - auto active_mut_sub = m_flx_subscription_store->get_active().make_mutable_copy(); - active_mut_sub.import(std::move(*new_subs)); - active_mut_sub.set_state(sync::SubscriptionSet::State::Complete); - active_mut_sub.commit(); - } - - auto tr = m_db->start_write(); - set_write_validator_factory(weak_sub_mgr); - tr->rollback(); -} - -void SyncSession::create_subscription_store() -{ - REALM_ASSERT(!m_flx_subscription_store); - - // Create the main subscription store instance when this is first called - this will - // remain valid afterwards for the life of the SyncSession, but m_flx_subscription_store - // will be reset when rolling back to PBS after a client FLX migration - if (!m_subscription_store_base) { - m_subscription_store_base = sync::SubscriptionStore::create(m_db); - } - - // m_subscription_store_base is always around for the life of SyncSession, but the - // m_flx_subscription_store is set when using FLX. - m_flx_subscription_store = m_subscription_store_base; -} - -void SyncSession::set_write_validator_factory(std::weak_ptr weak_sub_mgr) -{ - auto& history = static_cast(*m_db->get_replication()); - history.set_write_validator_factory( - [weak_sub_mgr](Transaction& tr) -> util::UniqueFunction { - auto sub_mgr = weak_sub_mgr.lock(); - REALM_ASSERT_RELEASE(sub_mgr); - auto latest_sub_tables = sub_mgr->get_tables_for_latest(tr); - return [tables = std::move(latest_sub_tables)](const Table& table) { - if (table.get_table_type() != Table::Type::TopLevel) { - return; - } - auto object_class_name = Group::table_name_to_class_name(table.get_name()); - if (tables.find(object_class_name) == tables.end()) { - throw NoSubscriptionForWrite( - util::format("Cannot write to class %1 when no flexible sync subscription has been created.", - object_class_name)); - } - }; - }); -} - -// Represents a reference to the SyncSession from outside of the sync subsystem. -// We attempt to keep the SyncSession in an active state as long as it has an external reference. -class SyncSession::ExternalReference { -public: - ExternalReference(std::shared_ptr session) - : m_session(std::move(session)) - { - } - - ~ExternalReference() - { - m_session->did_drop_external_reference(); - } - -private: - std::shared_ptr m_session; -}; - -std::shared_ptr SyncSession::external_reference() -{ - util::CheckedLockGuard lock(m_external_reference_mutex); - - if (auto external_reference = m_external_reference.lock()) - return std::shared_ptr(external_reference, this); - - auto external_reference = std::make_shared(shared_from_this()); - m_external_reference = external_reference; - return std::shared_ptr(external_reference, this); -} - -std::shared_ptr SyncSession::existing_external_reference() -{ - util::CheckedLockGuard lock(m_external_reference_mutex); - - if (auto external_reference = m_external_reference.lock()) - return std::shared_ptr(external_reference, this); - - return nullptr; -} - -void SyncSession::did_drop_external_reference() -{ - util::CheckedUniqueLock lock1(m_state_mutex); - { - util::CheckedLockGuard lock2(m_external_reference_mutex); - - // If the session is being resurrected we should not close the session. - if (!m_external_reference.expired()) - return; - } - - close(std::move(lock1)); -} - -uint64_t SyncProgressNotifier::register_callback(std::function notifier, - NotifierType direction, bool is_streaming, - int64_t pending_query_version) -{ - util::UniqueFunction invocation; - uint64_t token_value = 0; - { - std::lock_guard lock(m_mutex); - token_value = m_progress_notifier_token++; - NotifierPackage package{std::move(notifier), m_local_transaction_version, is_streaming, - direction == NotifierType::download, pending_query_version}; - if (!m_current_progress) { - // Simply register the package, since we have no data yet. - m_packages.emplace(token_value, std::move(package)); - return token_value; - } - bool skip_registration = false; - invocation = package.create_invocation(*m_current_progress, skip_registration); - if (skip_registration) { - token_value = 0; - } - else { - m_packages.emplace(token_value, std::move(package)); - } - } - invocation(); - return token_value; -} - -void SyncProgressNotifier::unregister_callback(uint64_t token) -{ - std::lock_guard lock(m_mutex); - m_packages.erase(token); -} - -void SyncProgressNotifier::update(uint64_t downloaded, uint64_t downloadable, uint64_t uploaded, uint64_t uploadable, - uint64_t snapshot_version, double download_estimate, double upload_estimate, - int64_t query_version) -{ - std::vector> invocations; - { - std::lock_guard lock(m_mutex); - m_current_progress = Progress{uploadable, downloadable, uploaded, downloaded, - upload_estimate, download_estimate, snapshot_version, query_version}; - - for (auto it = m_packages.begin(); it != m_packages.end();) { - bool should_delete = false; - invocations.emplace_back(it->second.create_invocation(*m_current_progress, should_delete)); - it = should_delete ? m_packages.erase(it) : std::next(it); - } - } - // Run the notifiers only after we've released the lock. - for (auto& invocation : invocations) - invocation(); -} - -void SyncProgressNotifier::set_local_version(uint64_t snapshot_version) -{ - std::lock_guard lock(m_mutex); - m_local_transaction_version = snapshot_version; -} - -util::UniqueFunction -SyncProgressNotifier::NotifierPackage::create_invocation(Progress const& current_progress, bool& is_expired) -{ - uint64_t transfered = is_download ? current_progress.downloaded : current_progress.uploaded; - uint64_t transferable = is_download ? current_progress.downloadable : current_progress.uploadable; - double estimate = is_download ? current_progress.download_estimate : current_progress.upload_estimate; - - if (!is_streaming) { - // If the sync client has not yet processed all of the local - // transactions then the uploadable data is incorrect and we should - // not invoke the callback - if (!is_download && snapshot_version > current_progress.snapshot_version) - return [] {}; - - // If this is a non-streaming download progress update and this notifier was - // created for a later query version (e.g. we're currently downloading - // subscription set version zero, but subscription set version 1 existed - // when the notifier was registered), then we want to skip this callback. - if (is_download && current_progress.query_version < pending_query_version) { - return [] {}; - } - - // The initial download size we get from the server is the uncompacted - // size, and so the download may complete before we actually receive - // that much data. When that happens, transferrable will drop and we - // need to use the new value instead of the captured one. - if (!captured_transferable || *captured_transferable > transferable) - captured_transferable = transferable; - transferable = *captured_transferable; - - // Since we can adjust the transferrable downwards the estimate for uploads - // won't be correct since the sync client's view of the estimate is based on - // the total number of uploadable bytes available rather than the number of - // bytes this NotifierPackage was waiting to upload. - if (!is_download) { - estimate = transferable > 0 ? std::min(transfered / double(transferable), 1.0) : 0.0; - } - } - - // A notifier is expired if at least as many bytes have been transferred - // as were originally considered transferrable. - is_expired = - !is_streaming && (transfered >= transferable && (!is_download || !pending_query_version || estimate >= 1.0)); - return [=, notifier = notifier] { - notifier(transfered, transferable, estimate); - }; -} - -uint64_t SyncSession::ConnectionChangeNotifier::add_callback(std::function callback) -{ - std::lock_guard lock(m_callback_mutex); - auto token = m_next_token++; - m_callbacks.push_back({std::move(callback), token}); - return token; -} - -void SyncSession::ConnectionChangeNotifier::remove_callback(uint64_t token) -{ - Callback old; - { - std::lock_guard lock(m_callback_mutex); - auto it = std::find_if(begin(m_callbacks), end(m_callbacks), [=](const auto& c) { - return c.token == token; - }); - if (it == end(m_callbacks)) { - return; - } - - size_t idx = distance(begin(m_callbacks), it); - if (m_callback_index != npos) { - if (m_callback_index >= idx) - --m_callback_index; - } - --m_callback_count; - - old = std::move(*it); - m_callbacks.erase(it); - } -} - -void SyncSession::ConnectionChangeNotifier::invoke_callbacks(ConnectionState old_state, ConnectionState new_state) -{ - std::unique_lock lock(m_callback_mutex); - m_callback_count = m_callbacks.size(); - for (++m_callback_index; m_callback_index < m_callback_count; ++m_callback_index) { - // acquire a local reference to the callback so that removing the - // callback from within it can't result in a dangling pointer - auto cb = m_callbacks[m_callback_index].fn; - lock.unlock(); - cb(old_state, new_state); - lock.lock(); - } - m_callback_index = npos; -} - -util::Future SyncSession::send_test_command(std::string body) -{ - util::CheckedLockGuard lk(m_state_mutex); - if (!m_session) { - return Status{ErrorCodes::RuntimeError, "Session doesn't exist to send test command on"}; - } - - return m_session->send_test_command(std::move(body)); -} - -void SyncSession::migrate_schema(util::UniqueFunction&& callback) -{ - util::CheckedUniqueLock lock(m_state_mutex); - // If the schema migration is already in progress, just wait to complete. - if (m_schema_migration_in_progress) { - add_completion_callback(std::move(callback), ProgressDirection::download); - return; - } - m_schema_migration_in_progress = true; - - // Perform the migration: - // 1. Pause the sync session - // 2. Once the sync client releases the realm file: - // a. Delete all tables (private and public) - // b. Reset the subscription store - // d. Empty the sync history and adjust cursors - // e. Reset file ident (the server flags the old ident as in the case of a client reset) - // 3. Resume the session (the client asks for a new file ident) - // See `sync_schema_migration::perform_schema_migration` for more details. - - CompletionCallbacks callbacks; - std::swap(m_completion_callbacks, callbacks); - auto guard = util::make_scope_exit([&]() noexcept { - util::CheckedUniqueLock lock(m_state_mutex); - if (m_completion_callbacks.empty()) - std::swap(callbacks, m_completion_callbacks); - else - m_completion_callbacks.merge(std::move(callbacks)); - }); - m_state_mutex.unlock(lock); - - auto future = pause_async(); - std::move(future).get_async( - [callback = std::move(callback), weak_session = weak_from_this()](Status status) mutable { - if (!status.is_ok()) - return callback(status); - - auto session = weak_session.lock(); - if (!session) { - status = Status(ErrorCodes::InvalidSession, "Sync session was destroyed during schema migration"); - return callback(status); - } - sync_schema_migration::perform_schema_migration(*session->m_db); - { - util::CheckedUniqueLock lock(session->m_state_mutex); - session->m_previous_schema_version.reset(); - session->m_schema_migration_in_progress = false; - session->m_subscription_store_base.reset(); - session->m_flx_subscription_store.reset(); - } - session->update_subscription_store(true, {}); - session->wait_for_download_completion(std::move(callback)); - session->resume(); - }); -} diff --git a/src/realm/object-store/sync/sync_session.hpp b/src/realm/object-store/sync/sync_session.hpp deleted file mode 100644 index d6f3ac31018..00000000000 --- a/src/realm/object-store/sync/sync_session.hpp +++ /dev/null @@ -1,541 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_SYNC_SESSION_HPP -#define REALM_OS_SYNC_SESSION_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -namespace realm { -class DB; -class SyncManager; -class SyncUser; - -namespace sync { -class Session; -class MigrationStore; -} // namespace sync - -namespace _impl { -class RealmCoordinator; -struct SyncClient; - -class SyncProgressNotifier { -public: - enum class NotifierType { upload, download }; - using ProgressNotifierCallback = void(uint64_t transferred_bytes, uint64_t transferrable_bytes, - double progress_estimate); - - uint64_t register_callback(std::function, NotifierType direction, bool is_streaming, - int64_t pending_query_version); - void unregister_callback(uint64_t); - - void set_local_version(uint64_t); - void update(uint64_t downloaded, uint64_t downloadable, uint64_t uploaded, uint64_t uploadable, - uint64_t snapshot_version, double download_estimate, double upload_estimate, int64_t query_version); - -private: - mutable std::mutex m_mutex; - - // How many bytes are uploadable or downloadable. - struct Progress { - uint64_t uploadable; - uint64_t downloadable; - uint64_t uploaded; - uint64_t downloaded; - double upload_estimate; - double download_estimate; - uint64_t snapshot_version; - int64_t query_version; - }; - - // A PODS encapsulating some information for progress notifier callbacks a binding - // can register upon this session. - struct NotifierPackage { - std::function notifier; - uint64_t snapshot_version; - bool is_streaming; - bool is_download; - int64_t pending_query_version = 0; - std::optional captured_transferable; - util::UniqueFunction create_invocation(const Progress&, bool& is_expired); - }; - - // A counter used as a token to identify progress notifier callbacks registered on this session. - uint64_t m_progress_notifier_token = 1; - // Version of the last locally-created transaction that we're expecting to be uploaded. - uint64_t m_local_transaction_version = 0; - - // Will be `none` until we've received the initial notification from sync. Note that this - // happens only once ever during the lifetime of a given `SyncSession`, since these values are - // expected to semi-monotonically increase, and a lower-bounds estimate is still useful in the - // event more up-to-date information isn't yet available. FIXME: If we support transparent - // client reset in the future, we might need to reset the progress state variables if the Realm - // is rolled back. - std::optional m_current_progress; - - std::unordered_map m_packages; -}; - -} // namespace _impl - -class SyncSession : public std::enable_shared_from_this { - struct Private {}; - -public: - enum class State { - Active, - Dying, - Inactive, - WaitingForAccessToken, - Paused, - }; - - enum class ConnectionState { - Disconnected, - Connecting, - Connected, - }; - - using ConnectionStateChangeCallback = void(ConnectionState old_state, ConnectionState new_state); - using TransactionCallback = void(VersionID old_version, VersionID new_version); - using ProgressNotifierCallback = _impl::SyncProgressNotifier::ProgressNotifierCallback; - using ProgressDirection = _impl::SyncProgressNotifier::NotifierType; - - explicit SyncSession(Private, _impl::SyncClient&, std::shared_ptr, const RealmConfig&, - SyncManager* sync_manager); - SyncSession(const SyncSession&) = delete; - SyncSession& operator=(const SyncSession&) = delete; - ~SyncSession(); - State state() const REQUIRES(!m_state_mutex); - ConnectionState connection_state() const REQUIRES(!m_connection_state_mutex); - - // The on-disk path of the Realm file backing the Realm this `SyncSession` represents. - std::string const& path() const; - - // Returns the salted file ident of the Realm file if one has been assigned, or returns - // zero for the file ident/salt if no file ident has been assigned yet. This value can - // change after a client reset or during the initial download of the realm. - sync::SaltedFileIdent get_file_ident() const; - - // Register a callback that will be called when all pending uploads have completed. - // The callback is run asynchronously, and upon whatever thread the underlying sync client - // chooses to run it on. - void wait_for_upload_completion(util::UniqueFunction&& callback) REQUIRES(!m_state_mutex); - - // Register a callback that will be called when all pending downloads have been completed. - // Works the same way as `wait_for_upload_completion()`. - void wait_for_download_completion(util::UniqueFunction&& callback) REQUIRES(!m_state_mutex); - - // Register a notifier that updates the app regarding progress. - // - // If `m_current_progress` is populated when this method is called, the notifier - // will be called synchronously, to provide the caller with an initial assessment - // of the state of synchronization. Otherwise, the progress notifier will be - // registered, and only called once sync has begun providing progress data. - // - // If `is_streaming` is true, then the notifier will be called forever, and will - // always contain the most up-to-date number of downloadable or uploadable bytes. - // Otherwise, the number of downloaded or uploaded bytes will always be reported - // relative to the number of downloadable or uploadable bytes at the point in time - // when the notifier was registered. - // - // An integer representing a token is returned. This token can be used to manually - // unregister the notifier. If the integer is 0, the notifier was not registered. - // - // Note that bindings should dispatch the callback onto a separate thread or queue - // in order to avoid blocking the sync client. - uint64_t register_progress_notifier(std::function&&, ProgressDirection, - bool is_streaming) REQUIRES(!m_state_mutex); - - // Unregister a previously registered notifier. If the token is invalid, - // this method does nothing. - void unregister_progress_notifier(uint64_t); - - // Registers a callback that is invoked when the the underlying sync session changes - // its connection state - uint64_t register_connection_change_callback(std::function&&); - - // Unregisters a previously registered callback. If the token is invalid, - // this method does nothing - void unregister_connection_change_callback(uint64_t); - - // If possible, take the session and do anything necessary to make it `Active`. - // Specifically: - // If the sync session is currently `Dying`, ask it to stay alive instead. - // If the sync session is currently `Inactive`, recreate it. - // If the sync session is currently `Paused`, do nothing - call resume() instead. - // Otherwise, a no-op. - void revive_if_needed() REQUIRES(!m_state_mutex, !m_config_mutex); - - // Perform any actions needed in response to regaining network connectivity. - void handle_reconnect() REQUIRES(!m_state_mutex); - - // Inform the sync session that it should close. This will respect the stop policy specified in - // the SyncConfig, so its possible the session will remain open either until all pending local - // changes are uploaded or possibly forever. - void close() REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - - // Inform the sync session that it should close immediately, regardless of the stop policy. - // The session may resume after calling this if a new Realm is opened for the underlying DB - // of the SyncSession. Use pause() to close the sync session until you want to explicitly - // resume it. - void force_close() REQUIRES(!m_state_mutex, !m_connection_state_mutex); - - // Closes the sync session so that it will not resume until resume() is called. - void pause() REQUIRES(!m_state_mutex, !m_connection_state_mutex); - - // Resumes the sync session after it was paused by calling pause(). If the sync session is inactive - // for any other reason this will also resume it. - void resume() REQUIRES(!m_state_mutex, !m_config_mutex); - - // Drop the current session and restart a new one from scratch using the latest configuration in - // the sync manager. Used to respond to redirect responses from the server when the deployment - // model has changed while the user is logged in and a session is active. - // If this sync session is currently paused, a new session will not be started until resume() is - // called. - // NOTE: This method ignores the current stop policy and closes the current session immediately, - // since a new session will be created as part of this call. The new session will adhere to - // the stop policy if it is manually closed. - void restart_session() REQUIRES(!m_state_mutex, !m_connection_state_mutex, !m_config_mutex); - - // Shut down the synchronization session (sync::Session) and wait for the Realm file to no - // longer be open on behalf of it. - void shutdown_and_wait() REQUIRES(!m_state_mutex, !m_connection_state_mutex); - - // DO NOT CALL OUTSIDE OF TESTING CODE. - void detach_from_sync_manager() REQUIRES(!m_state_mutex, !m_connection_state_mutex); - - // The access token needs to periodically be refreshed and this is how to - // let the sync session know to update it's internal copy. - void update_access_token(std::string_view signed_token) REQUIRES(!m_state_mutex, !m_config_mutex); - - // Request an updated access token from this session's sync user. - void initiate_access_token_refresh() REQUIRES(!m_config_mutex); - - // Update the sync configuration used for this session. The new configuration must have the - // same user and reference realm url as the old configuration. The session will immediately - // disconnect (if it was active), and then attempt to connect using the new configuration. - // This is primarily intended to be used for TESTING only, even though it is used by the - // Swift SDK in `setCustomRequestHeaders` and is defined in the realm-js bindgen definitions. - void update_configuration(SyncConfig new_config) - REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - - // An object representing the user who owns the Realm this `SyncSession` represents. - std::shared_ptr user() const REQUIRES(!m_config_mutex) - { - util::CheckedLockGuard lock(m_config_mutex); - REALM_ASSERT(m_config.sync_config); - return m_config.sync_config->user; - } - - // A copy of the configuration object describing the Realm this `SyncSession` represents. - SyncConfig config() const REQUIRES(!m_config_mutex) - { - util::CheckedLockGuard lock(m_config_mutex); - REALM_ASSERT(m_config.sync_config); - return *m_config.sync_config; - } - - // When App is created, it will provide a generated websocket sync route when the SyncManager is - // created. When the next AppServices HTTP request is made via the App object, the location info - // will be requested and a verified websocket sync route will be provided to the SyncManager. If - // the SyncSession is started with a cached user, the websocket connection will be initially - // established using the generated sync route. If that connection is successful, then the sync - // route will be marked verified and used from that point on. If that connection fails, the - // SyncSession will update the location via an access token update to retrieve the verified - // sync route from the server's location info. The SyncSessio will then be restarted so it - // uses the updated sync route value. - - // If the SyncSession is active, this function returns the sync route that is being used - // by the current underlying session to connect to the server. - std::string full_realm_url() const REQUIRES(!m_config_mutex) - { - util::CheckedLockGuard lock(m_config_mutex); - return m_server_url; - } - - // If the sync route value was returned by querying the location information, or the - // SyncSession has successfully connected to the server using the configured sync route, - // then the sync route will be marked "verified". Otherwise, the location information will - // be requested from the server if the connection fails when trying to open a websocket - // to the server. - bool realm_url_verified() const REQUIRES(!m_config_mutex) - { - util::CheckedLockGuard lock(m_config_mutex); - return m_server_url_verified; - } - - std::shared_ptr get_flx_subscription_store() REQUIRES(!m_state_mutex); - - // Create an external reference to this session. The sync session attempts to remain active - // as long as an external reference to the session exists. - std::shared_ptr external_reference() REQUIRES(!m_external_reference_mutex); - - // Return an existing external reference to this session, if one exists. Otherwise, returns `nullptr`. - std::shared_ptr existing_external_reference() REQUIRES(!m_external_reference_mutex); - - struct OnlyForTesting; - - // Expose some internal functionality to other parts of the ObjectStore - // without making it public to everyone - class Internal { - friend class _impl::RealmCoordinator; - friend struct OnlyForTesting; - friend class AsyncOpenTask; - - static void nonsync_transact_notify(SyncSession& session, VersionID::version_type version) - { - session.nonsync_transact_notify(version); - } - - static std::shared_ptr get_db(SyncSession& session) - { - return session.m_db; - } - - static void migrate_schema(SyncSession& session, util::UniqueFunction&& callback) - { - session.migrate_schema(std::move(callback)); - } - }; - - // Expose some internal functionality to testing code. - struct OnlyForTesting { - static void handle_error(SyncSession& session, sync::SessionErrorInfo&& error); - static void nonsync_transact_notify(SyncSession& session, VersionID::version_type version) - { - session.nonsync_transact_notify(version); - } - static std::shared_ptr get_db(SyncSession& session) - { - return session.m_db; - } - - static std::string get_appservices_connection_id(SyncSession& session) - { - return session.get_appservices_connection_id(); - } - - // Supported commands can be found in `handleTestCommandMessage()` - // in baas/devicesync/server/qbs_client_handler_functions.go - static util::Future send_test_command(SyncSession& session, std::string request) - { - return session.send_test_command(std::move(request)); - } - - static std::shared_ptr get_subscription_store_base(SyncSession& session) - { - return session.get_subscription_store_base(); - } - - static util::Future pause_async(SyncSession& session); - }; - -private: - using std::enable_shared_from_this::shared_from_this; - using CompletionCallbacks = std::map>>; - - class ConnectionChangeNotifier { - public: - uint64_t add_callback(std::function callback); - void remove_callback(uint64_t token); - void invoke_callbacks(ConnectionState old_state, ConnectionState new_state); - - private: - struct Callback { - std::function fn; - uint64_t token; - }; - - std::mutex m_callback_mutex; - std::vector m_callbacks; - - size_t m_callback_index = -1; - size_t m_callback_count = -1; - uint64_t m_next_token = 0; - }; - - friend class realm::SyncManager; - // Called by SyncManager { - static std::shared_ptr create(_impl::SyncClient& client, std::shared_ptr db, - const RealmConfig& config, SyncManager* sync_manager) - { - REALM_ASSERT(config.sync_config); - return std::make_shared(Private(), client, std::move(db), config, sync_manager); - } - // } - - std::shared_ptr sync_manager() const REQUIRES(!m_state_mutex); - - static util::UniqueFunction)> - handle_refresh(const std::shared_ptr&, bool); - - // Initialize or tear down the subscription store based on whether or not flx_sync_requested is true - void update_subscription_store(bool flx_sync_requested, std::optional new_subs) - REQUIRES(!m_state_mutex); - void create_subscription_store() REQUIRES(m_state_mutex); - void set_write_validator_factory(std::weak_ptr weak_sub_mgr); - // Update the sync config after a PBS->FLX migration or FLX->PBS rollback occurs - void apply_sync_config_after_migration_or_rollback() REQUIRES(!m_config_mutex, !m_state_mutex); - void save_sync_config_after_migration_or_rollback() REQUIRES(!m_config_mutex); - - void download_fresh_realm(const sync::SessionErrorInfo& error_info) - REQUIRES(!m_config_mutex, !m_state_mutex, !m_connection_state_mutex); - void handle_fresh_realm_downloaded(DBRef db, Status result, const sync::SessionErrorInfo& cr_error_info, - std::optional new_subs = std::nullopt) - REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - void handle_error(sync::SessionErrorInfo) REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - void handle_bad_auth(const std::shared_ptr& user, Status status) - REQUIRES(!m_state_mutex, !m_config_mutex); - void handle_location_update_failed(Status status) - REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - // If sub_notify_error is set (including Status::OK()), then the pending subscription waiters will - // also be called with the sub_notify_error status value. - void cancel_pending_waits(util::CheckedUniqueLock, Status) RELEASE(m_state_mutex); - enum class ShouldBackup { yes, no }; - void update_error_and_mark_file_for_deletion(SyncError&, ShouldBackup) REQUIRES(m_state_mutex, !m_config_mutex); - void handle_progress_update(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, double, double, int64_t); - void handle_new_flx_sync_query(int64_t version); - - void nonsync_transact_notify(VersionID::version_type) REQUIRES(!m_state_mutex); - - void create_sync_session() REQUIRES(m_state_mutex, !m_config_mutex); - void did_drop_external_reference() - REQUIRES(!m_state_mutex, !m_config_mutex, !m_external_reference_mutex, !m_connection_state_mutex); - void close(util::CheckedUniqueLock) RELEASE(m_state_mutex) REQUIRES(!m_config_mutex, !m_connection_state_mutex); - - void become_active() REQUIRES(m_state_mutex, !m_config_mutex); - void become_dying(util::CheckedUniqueLock) RELEASE(m_state_mutex) REQUIRES(!m_connection_state_mutex); - void become_inactive(util::CheckedUniqueLock, Status = Status::OK(), bool = true) RELEASE(m_state_mutex) - REQUIRES(!m_connection_state_mutex); - void become_paused(util::CheckedUniqueLock) RELEASE(m_state_mutex) REQUIRES(!m_connection_state_mutex); - void become_waiting_for_access_token() REQUIRES(m_state_mutex); - util::Future pause_async() REQUIRES(!m_state_mutex, !m_connection_state_mutex); - - // do restart session restarts the session without freeing any of the waiters - void do_restart_session(util::CheckedUniqueLock) - REQUIRES(m_state_mutex, !m_connection_state_mutex, !m_config_mutex); - - // do_become_inactive is called from both become_paused()/become_inactive() and does all the steps to - // shutdown and cleanup the sync session besides setting m_state. - void do_become_inactive(util::CheckedUniqueLock, Status, bool) RELEASE(m_state_mutex) - REQUIRES(!m_connection_state_mutex); - // do_revive is called from both revive_if_needed() and resume(). It does all the steps to transition - // from a state that is not Active to Active. - void do_revive(util::CheckedUniqueLock&& lock) RELEASE(m_state_mutex) REQUIRES(!m_config_mutex); - - void add_completion_callback(util::UniqueFunction callback, ProgressDirection direction) - REQUIRES(m_state_mutex); - - std::string get_appservices_connection_id() const REQUIRES(!m_state_mutex); - - util::Future send_test_command(std::string body) REQUIRES(!m_state_mutex); - - void migrate_schema(util::UniqueFunction&& callback) - REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - - std::function m_sync_transact_callback GUARDED_BY(m_state_mutex); - - template - auto config(Field f) REQUIRES(!m_config_mutex) - { - util::CheckedLockGuard lock(m_config_mutex); - return m_config.sync_config.get()->*f; - } - - void assert_mutex_unlocked() ASSERT_CAPABILITY(!m_state_mutex) ASSERT_CAPABILITY(!m_config_mutex) {} - - // Return the subscription_store_base - to be used only for testing - std::shared_ptr get_subscription_store_base() REQUIRES(!m_state_mutex); - - // Updates the connection state for this SyncSession and notify any registered callbacks if changed. - // Also ensures server_url_verified is set if the connection state is updated to connected. - void update_connection_state(ConnectionState new_state) REQUIRES(!m_config_mutex, !m_connection_state_mutex); - - util::CheckedMutex m_state_mutex; - util::CheckedMutex m_connection_state_mutex; - - State m_state GUARDED_BY(m_state_mutex) = State::Inactive; - - // The underlying state of the connection. Even when sharing connections, the underlying session - // will always start out as disconnected and then immediately transition to the correct state when calling - // bind(). - ConnectionState m_connection_state GUARDED_BY(m_connection_state_mutex) = ConnectionState::Disconnected; - size_t m_death_count GUARDED_BY(m_state_mutex) = 0; - - util::CheckedMutex m_config_mutex; - RealmConfig m_config GUARDED_BY(m_config_mutex); - const std::shared_ptr m_db; - // The subscription store base is lazily created when needed, but never destroyed - std::shared_ptr m_subscription_store_base GUARDED_BY(m_state_mutex); - // m_flx_subscription_store will either point to m_subscription_store_base if currently using FLX - // or set to nullptr if currently using PBS (mutable for client PBS->FLX migration) - std::shared_ptr m_flx_subscription_store GUARDED_BY(m_state_mutex); - // Original sync config for reverting back to PBS if FLX migration is rolled back - const std::shared_ptr m_original_sync_config; // does not change after construction - std::shared_ptr m_migrated_sync_config GUARDED_BY(m_config_mutex); - const std::shared_ptr m_migration_store; - std::optional m_migration_sentinel_query_version GUARDED_BY(m_state_mutex); - std::optional m_client_reset_error GUARDED_BY(m_state_mutex); - DBRef m_client_reset_fresh_copy GUARDED_BY(m_state_mutex); - _impl::SyncClient& m_client; - SyncManager* m_sync_manager GUARDED_BY(m_state_mutex) = nullptr; - - int64_t m_completion_request_counter GUARDED_BY(m_state_mutex) = 0; - CompletionCallbacks m_completion_callbacks GUARDED_BY(m_state_mutex); - - // The underlying `Session` object that is owned and managed by this `SyncSession`. - // The session is first created when the `SyncSession` is moved out of its initial `inactive` state. - // The session might be destroyed if the `SyncSession` becomes inactive again (for example, if the - // user owning the session logs out). It might be created anew if the session is revived (if a - // logged-out user logs back in, the object store sync code will revive their sessions). - std::unique_ptr m_session GUARDED_BY(m_state_mutex); - - // The fully-resolved URL of this Realm, including the server and the path. - std::string m_server_url GUARDED_BY(m_config_mutex); - bool m_server_url_verified GUARDED_BY(m_config_mutex) = false; - - _impl::SyncProgressNotifier m_progress_notifier; - ConnectionChangeNotifier m_connection_change_notifier; - - util::CheckedMutex m_external_reference_mutex; - class ExternalReference; - std::weak_ptr m_external_reference GUARDED_BY(m_external_reference_mutex); - - // Set if ProtocolError::schema_version_changed error is received from the server. - std::optional m_previous_schema_version GUARDED_BY(m_state_mutex); - bool m_schema_migration_in_progress GUARDED_BY(m_state_mutex) = false; -}; - -} // namespace realm - -#endif // REALM_OS_SYNC_SESSION_HPP diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp deleted file mode 100644 index 45c6e3f0450..00000000000 --- a/src/realm/object-store/sync/sync_user.hpp +++ /dev/null @@ -1,98 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_SYNC_USER_HPP -#define REALM_OS_SYNC_USER_HPP - -#include -#include -#include - -#include -#include -#include - -namespace realm { -namespace app { -struct AppError; -} // namespace app -class SyncManager; -class SyncSession; - -enum class SyncFileAction { - // The Realm files at the given directory will be deleted. - DeleteRealm, - // The Realm file will be copied to a 'recovery' directory, and the original Realm files will be deleted. - BackUpThenDeleteRealm -}; - -class SyncUser { -public: - virtual ~SyncUser() = default; - bool is_logged_in() const - { - return state() == State::LoggedIn; - } - - enum class State { - // changing these is a file-format breaking change - LoggedOut = 0, - LoggedIn = 1, - Removed = 2, - }; - - /// Server-supplied unique id for this user. - virtual std::string user_id() const noexcept = 0; - /// App id which this user is associated with - virtual std::string app_id() const noexcept = 0; - /// Legacy uuids attached to this user. Only applicable to app::User. - virtual std::vector legacy_identities() const - { - return {}; - } - - virtual std::string access_token() const = 0; - virtual std::string refresh_token() const = 0; - virtual State state() const = 0; - - /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns - /// true. - virtual bool access_token_refresh_required() const = 0; - - virtual SyncManager* sync_manager() = 0; - - using CompletionHandler = util::UniqueFunction)>; - // The sync server has told the client to log out the user - // No completion handler as the user is already logged out server-side - virtual void request_log_out() = 0; - // The sync server has told the client to refresh the user's location - virtual void request_refresh_location(CompletionHandler&&) = 0; - // The sync server has told the client to refresh the user's access token - virtual void request_access_token(CompletionHandler&&) = 0; - - // Called whenever a Realm is opened with this user to enable deleting them - // when the user is removed - virtual void track_realm(std::string_view path) = 0; - // if the action is BackUpThenDeleteRealm, the path where it was backed up is returned - virtual std::string create_file_action(SyncFileAction action, std::string_view original_path, - std::optional requested_recovery_dir) = 0; -}; - -} // namespace realm - -#endif // REALM_OS_SYNC_USER_HPP diff --git a/src/realm/sync/CMakeLists.txt b/src/realm/sync/CMakeLists.txt deleted file mode 100644 index 8896f15b50f..00000000000 --- a/src/realm/sync/CMakeLists.txt +++ /dev/null @@ -1,154 +0,0 @@ -set(SYNC_SOURCES - config.cpp - noinst/changeset_index.cpp - noinst/client_history_impl.cpp - noinst/client_impl_base.cpp - noinst/client_reset.cpp - noinst/client_reset_operation.cpp - noinst/client_reset_recovery.cpp - noinst/migration_store.cpp - noinst/pending_bootstrap_store.cpp - noinst/pending_reset_store.cpp - noinst/protocol_codec.cpp - noinst/sync_metadata_schema.cpp - noinst/sync_schema_migration.cpp - changeset_encoder.cpp - changeset_parser.cpp - changeset.cpp - client.cpp - history.cpp - instruction_applier.cpp - instruction_replication.cpp - instructions.cpp - object_id.cpp - protocol.cpp - subscriptions.cpp - transform.cpp - network/default_socket.cpp - network/http.cpp - network/network.cpp - network/network_ssl.cpp - network/websocket.cpp -) - -set(IMPL_INSTALL_HEADERS - impl/clock.hpp - impl/clamped_hex_dump.hpp -) - -set(SYNC_INSTALL_HEADERS - binding_callback_thread_observer.hpp - config.hpp - changeset_encoder.hpp - changeset_parser.hpp - changeset.hpp - client.hpp - client_base.hpp - socket_provider.hpp - history.hpp - instruction_applier.hpp - instruction_replication.hpp - instructions.hpp - object_id.hpp - protocol.hpp - subscriptions.hpp - transform.hpp -) - -set(SYNC_NETWORK_INSTALL_HEADERS - network/default_socket.hpp - network/http.hpp - network/network.hpp - network/network_ssl.hpp - network/websocket.hpp - network/websocket_error.hpp -) - -set(NOINST_HEADERS - trigger.hpp - - noinst/changeset_index.hpp - noinst/client_history_impl.hpp - noinst/client_impl_base.hpp - noinst/client_reset.hpp - noinst/client_reset_operation.hpp - noinst/client_reset_recovery.hpp - noinst/integer_codec.hpp - noinst/migration_store.hpp - noinst/pending_bootstrap_store.hpp - noinst/pending_reset_store.hpp - noinst/protocol_codec.hpp - noinst/root_certs.hpp - noinst/sync_metadata_schema.hpp - noinst/sync_schema_migration.hpp -) - -set(SYNC_HEADERS ${IMPL_INSTALL_HEADESR} - ${SYNC_INSTALL_HEADERS} - ${SYNC_NETWORK_INSTALL_HEADERS} - ${NOINST_HEADERS}) - -add_library(Sync STATIC ${SYNC_SOURCES} ${SYNC_HEADERS}) -add_library(Realm::Sync ALIAS Sync) - -set_target_properties(Sync PROPERTIES - OUTPUT_NAME "realm-sync" -) - -if(NOT REALM_SYNC_MULTIPLEXING) - target_compile_definitions(Sync PUBLIC REALM_DISABLE_SYNC_MULTIPLEXING=1) -endif() - -target_link_libraries(Sync PUBLIC Storage) - -if(APPLE AND NOT REALM_FORCE_OPENSSL) - target_link_options(Sync INTERFACE "SHELL:-framework Security") -elseif(REALM_HAVE_OPENSSL) - target_link_libraries(Sync PUBLIC OpenSSL::SSL) -endif() - -if(WIN32) - if(NOT WINDOWS_STORE) - target_link_libraries(Sync INTERFACE Version.lib) - endif() - target_link_libraries(Sync INTERFACE Crypt32.lib) -endif() - -install(TARGETS Sync EXPORT realm - ARCHIVE COMPONENT devel - PUBLIC_HEADER COMPONENT devel) - -install(FILES ${SYNC_INSTALL_HEADERS} - DESTINATION include/realm/sync - COMPONENT devel) - -install(FILES ${IMPL_INSTALL_HEADERS} - DESTINATION include/realm/impl - COMPONENT devel) - -install(FILES ${SYNC_INSTALL_HEADERS} - DESTINATION include/realm/sync - COMPONENT devel) - -install(FILES ${SYNC_NETWORK_INSTALL_HEADERS} - DESTINATION include/realm/sync/network - COMPONENT devel) - -install(FILES ${UTIL_INSTALL_HEADERS} - DESTINATION include/realm/util - COMPONENT devel) - -install(FILES ../../external/mpark/variant.hpp - DESTINATION include/external/mpark - COMPONENT devel) -install(FILES ../../external/json/json.hpp - DESTINATION include/external/json - COMPONENT devel) - -add_subdirectory(noinst/server) - -if(NOT REALM_BUILD_LIB_ONLY AND NOT CMAKE_SYSTEM_NAME MATCHES "^Windows") - add_subdirectory(tools) - set_macos_only(tools) -endif() - diff --git a/src/realm/sync/binding_callback_thread_observer.hpp b/src/realm/sync/binding_callback_thread_observer.hpp deleted file mode 100644 index 0bb9f5753f2..00000000000 --- a/src/realm/sync/binding_callback_thread_observer.hpp +++ /dev/null @@ -1,52 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2017 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP -#define REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP - -#include - -namespace realm { -// Interface for observing the lifecycle of the worker thread used by -// DefaultSocketProvider. This is required to be able to attach/detach the thread -// to the JVM to be able to perform JNI calls. -struct BindingCallbackThreadObserver { - virtual ~BindingCallbackThreadObserver() = default; - - // Called on the thread shortly after it is created. This is guaranteed to - // be called before any other callbacks to the SDK are made. - virtual void did_create_thread() {} - // Called on the thread shortly before it is destroyed. No further callbacks - // to the SDK on the thread will be made after this is called. - virtual void will_destroy_thread() {} - // If has_handle_error() returns true, any uncaught exceptions from the - // event loop are passed to this. If this returns true, the thread exits - // cleanly, while if it returns false the exception is rethrown. - virtual bool handle_error(const std::exception&) - { - return false; - } - virtual bool has_handle_error() - { - return false; - } -}; - -} // namespace realm - -#endif // REALM_OS_BINDING_CALLBACK_THREAD_OBSERVER_HPP diff --git a/src/realm/sync/changeset.cpp b/src/realm/sync/changeset.cpp deleted file mode 100644 index 0e57ce506b5..00000000000 --- a/src/realm/sync/changeset.cpp +++ /dev/null @@ -1,596 +0,0 @@ -#include - -#if REALM_DEBUG -#include -#include -#include -#endif // REALM_DEBUG - -using namespace realm; -using namespace realm::sync; -using namespace realm::util; - -InternString Changeset::intern_string(StringData str) -{ - if (InternString interned = find_string(str)) - return interned; - - REALM_ASSERT(m_string_buffer.size() < std::numeric_limits::max()); - REALM_ASSERT(m_strings.size() < std::numeric_limits::max()); - REALM_ASSERT(str.size() < std::numeric_limits::max()); - - // FIXME: Very slow. - uint32_t size = uint32_t(str.size()); - uint32_t offset = uint32_t(m_string_buffer.size()); - m_string_buffer.append(str.data(), size); - uint32_t index = uint32_t(m_strings.size()); - m_strings.push_back(StringBufferRange{offset, size}); - return InternString{index}; -} - - -InternString Changeset::find_string(StringData string) const noexcept -{ - // FIXME: Linear search can be very expensive as changesets can be very big - std::size_t n = m_strings.size(); - for (std::size_t i = 0; i < n; ++i) { - const auto& range = m_strings[i]; - StringData string_2{m_string_buffer.data() + range.offset, range.size}; - if (string_2 == string) - return InternString{std::uint_least32_t(i)}; - } - return InternString{}; -} - -PrimaryKey Changeset::get_key(const Instruction::PrimaryKey& key) const noexcept -{ - return mpark::visit(overload{ - [this](InternString str) -> PrimaryKey { - return get_string(str); - }, - [](auto otherwise) -> PrimaryKey { - return otherwise; - }, - }, - key); -} - -bool Changeset::operator==(const Changeset& that) const noexcept -{ - if (m_instructions == that.m_instructions) { - return m_strings == that.m_strings; - } - return false; -} - -std::ostream& Changeset::print_value(std::ostream& os, const Instruction::Payload& value) const noexcept -{ - using Type = Instruction::Payload::Type; - - os << get_type_name(value.type) << "("; - auto& data = value.data; - switch (value.type) { - case Type::ObjectValue: - break; - case Type::GlobalKey: - os << data.key; - break; - case Type::Erased: - break; - case Type::Set: - break; - case Type::List: - break; - case Type::Dictionary: - break; - case Type::Null: - break; - case Type::Int: - os << data.integer; - break; - case Type::Bool: - os << data.boolean; - break; - case Type::String: - os << "\"" << get_string(data.str) << "\""; - break; - case Type::Binary: - os << "..."; - break; - case Type::Timestamp: - os << data.timestamp; - break; - case Type::Float: - os << data.fnum; - break; - case Type::Double: - os << data.dnum; - break; - case Type::Decimal: - os << data.decimal; - break; - case Type::UUID: - os << data.uuid; - break; - case Type::Link: { - os << "target_table = " << get_string(data.link.target_table) << ", " - << "target = " << format_pk(get_key(data.link.target)); - break; - }; - case Type::ObjectId: - os << data.object_id; - break; - } - return os << ")"; -} - -std::ostream& Changeset::print_path(std::ostream& os, const Instruction::Path& path) const noexcept -{ - bool first = true; - for (auto& element : path) { - if (!first) { - os << '.'; - } - first = false; - auto print = overload{ - [&](uint32_t index) { - os << index; - }, - [&](InternString str) { - os << get_string(str); - }, - }; - mpark::visit(print, element); - } - return os; -} - -std::ostream& Changeset::print_path(std::ostream& os, InternString table, const Instruction::PrimaryKey& pk, - util::Optional field, const Instruction::Path* path) const -{ - os << get_string(table) << "[" << format_pk(get_key(pk)) << "]"; - if (field) { - os << "." << get_string(*field); - } - if (path) { - for (auto& element : *path) { - if (auto subfield = mpark::get_if(&element)) { - os << "." << get_string(*subfield); - } - else if (auto index = mpark::get_if(&element)) { - os << "[" << *index << "]"; - } - else { - REALM_TERMINATE("Invalid path"); - } - } - } - return os; -} - -std::ostream& realm::sync::operator<<(std::ostream& os, const Changeset& changeset) -{ -#if REALM_DEBUG // LCOV_EXCL_START - changeset.print(os); - return os; -#else - return os << "[changeset with " << changeset.size() << " instructions]"; -#endif -} - - -#if REALM_DEBUG // LCOV_EXCL_START -void Changeset::print(std::ostream& os) const -{ - Changeset::Printer printer{os}; - Changeset::Reflector reflector{printer, *this}; - os << std::left << std::setw(16) << "InternStrings"; - for (size_t i = 0; i < m_strings.size(); ++i) { - os << i << "=\"" << get_string(m_strings.at(i)) << '"'; - if (i + 1 != m_strings.size()) - os << ", "; - } - os << "\n"; - - reflector.visit_all(); -} - -void Changeset::print() const -{ - print(std::cerr); -} - - -void Changeset::verify() const -{ - for (size_t i = 0; i < m_strings.size(); ++i) { - auto& range = m_strings.at(i); - REALM_ASSERT(range.offset <= m_string_buffer.size()); - REALM_ASSERT(range.offset + range.size <= m_string_buffer.size()); - } - - auto verify_string_range = [&](StringBufferRange range) { - REALM_ASSERT(range.offset <= m_string_buffer.size()); - REALM_ASSERT(range.offset + range.size <= m_string_buffer.size()); - }; - - auto verify_intern_string = [&](InternString str) { - auto range = get_intern_string(str); - verify_string_range(range); - }; - - auto verify_key = [&](const Instruction::PrimaryKey& key) { - mpark::visit(util::overload{[&](InternString str) { - verify_intern_string(str); - }, - [](auto&&) {}}, - key); - }; - - auto verify_payload = [&](const Instruction::Payload& payload) { - using Type = Instruction::Payload::Type; - switch (payload.type) { - case Type::String: { - return verify_string_range(payload.data.str); - } - case Type::Binary: { - return verify_string_range(payload.data.binary); - } - case Type::Link: { - verify_intern_string(payload.data.link.target_table); - return verify_key(payload.data.link.target); - } - default: - return; - } - }; - - auto verify_path = [&](const Instruction::Path& path) { - for (auto& element : path) { - mpark::visit(util::overload{[&](InternString str) { - verify_intern_string(str); - }, - [](auto&&) {}}, - element); - } - }; - - for (auto instr : *this) { - if (!instr) - continue; - - if (auto table_instr = instr->get_if()) { - verify_intern_string(table_instr->table); - if (auto object_instr = instr->get_if()) { - verify_key(object_instr->object); - - if (auto path_instr = instr->get_if()) { - verify_path(path_instr->path); - } - - if (auto set_instr = instr->get_if()) { - verify_payload(set_instr->value); - } - else if (auto insert_instr = instr->get_if()) { - verify_payload(insert_instr->value); - } - } - else if (auto add_table_instr = instr->get_if()) { - mpark::visit(util::overload{ - [&](const Instruction::AddTable::TopLevelTable& spec) { - REALM_ASSERT(is_valid_key_type(spec.pk_type)); - verify_intern_string(spec.pk_field); - }, - [](const Instruction::AddTable::EmbeddedTable&) {}, - }, - add_table_instr->type); - } - else if (auto add_column_instr = instr->get_if()) { - verify_intern_string(add_column_instr->field); - if (add_column_instr->type == Instruction::Payload::Type::Link) { - verify_intern_string(add_column_instr->link_target_table); - } - } - else if (auto erase_column_instr = instr->get_if()) { - verify_intern_string(erase_column_instr->field); - } - } - else { - REALM_TERMINATE("Corrupt instruction type"); - } - } -} - -void Changeset::Reflector::operator()(const Instruction::AddTable& p) const -{ - m_tracer.name("AddTable"); - table_instr(p); - auto trace = util::overload{ - [&](const Instruction::AddTable::TopLevelTable& spec) { - m_tracer.field("pk_field", spec.pk_field); - m_tracer.field("pk_type", spec.pk_type); - m_tracer.field("pk_nullable", spec.pk_nullable); - m_tracer.field("is_asymmetric", spec.is_asymmetric); - }, - [&](const Instruction::AddTable::EmbeddedTable&) { - m_tracer.field("embedded", true); - }, - }; - mpark::visit(trace, p.type); -} - -void Changeset::Reflector::operator()(const Instruction::EraseTable& p) const -{ - m_tracer.name("EraseTable"); - table_instr(p); -} - -void Changeset::Reflector::operator()(const Instruction::Update& p) const -{ - m_tracer.name("Update"); - path_instr(p); - m_tracer.field("value", p.value); - if (p.is_array_update()) { - m_tracer.field("prior_size", p.prior_size); - } - else { - m_tracer.field("default", p.is_default); - } -} - -void Changeset::Reflector::operator()(const Instruction::AddInteger& p) const -{ - m_tracer.name("AddInteger"); - path_instr(p); - m_tracer.field("value", Instruction::Payload{p.value}); -} - -void Changeset::Reflector::operator()(const Instruction::CreateObject& p) const -{ - m_tracer.name("CreateObject"); - object_instr(p); -} - -void Changeset::Reflector::operator()(const Instruction::EraseObject& p) const -{ - m_tracer.name("EraseObject"); - object_instr(p); -} - -void Changeset::Reflector::operator()(const Instruction::ArrayInsert& p) const -{ - m_tracer.name("ArrayInsert"); - path_instr(p); - m_tracer.field("value", p.value); - m_tracer.field("prior_size", p.prior_size); -} - -void Changeset::Reflector::operator()(const Instruction::ArrayMove& p) const -{ - m_tracer.name("ArrayMove"); - path_instr(p); - m_tracer.field("ndx_2", p.ndx_2); - m_tracer.field("prior_size", p.prior_size); -} - -void Changeset::Reflector::operator()(const Instruction::ArrayErase& p) const -{ - m_tracer.name("ArrayErase"); - path_instr(p); - m_tracer.field("prior_size", p.prior_size); -} - -void Changeset::Reflector::operator()(const Instruction::Clear& p) const -{ - m_tracer.name("Clear"); - path_instr(p); - m_tracer.field("collection_type", p.collection_type); -} - -void Changeset::Reflector::operator()(const Instruction::SetInsert& p) const -{ - m_tracer.name("SetInsert"); - path_instr(p); - m_tracer.field("value", p.value); -} - -void Changeset::Reflector::operator()(const Instruction::SetErase& p) const -{ - m_tracer.name("SetErase"); - path_instr(p); - m_tracer.field("value", p.value); -} - -void Changeset::Reflector::operator()(const Instruction::AddColumn& p) const -{ - m_tracer.name("AddColumn"); - m_tracer.field("table", p.table); - m_tracer.field("field", p.field); - if (p.type != Instruction::Payload::Type::Null) { - m_tracer.field("type", p.type); - } - else { - m_tracer.field("type", Instruction::Payload::Type::Null); - } - m_tracer.field("nullable", p.nullable); - m_tracer.field("collection_type", p.collection_type); - if (p.type == Instruction::Payload::Type::Link) { - m_tracer.field("target_table", p.link_target_table); - } - if (p.collection_type == Instruction::CollectionType::Dictionary) { - m_tracer.field("key_type", p.key_type); - } -} - -void Changeset::Reflector::operator()(const Instruction::EraseColumn& p) const -{ - m_tracer.name("EraseColumn"); - m_tracer.field("table", p.table); - m_tracer.field("field", p.field); -} - -void Changeset::Reflector::table_instr(const Instruction::TableInstruction& p) const -{ - m_tracer.field("path", p.table); -} - -void Changeset::Reflector::object_instr(const Instruction::ObjectInstruction& p) const -{ - m_tracer.path("path", p.table, p.object, util::none, nullptr); -} - -void Changeset::Reflector::path_instr(const Instruction::PathInstruction& p) const -{ - m_tracer.path("path", p.table, p.object, p.field, &p.path); -} - -void Changeset::Reflector::visit_all() const -{ - m_tracer.set_changeset(&m_changeset); - for (auto instr : m_changeset) { - if (!instr) - continue; - m_tracer.before_each(); - instr->visit(*this); - m_tracer.after_each(); - } - m_tracer.set_changeset(nullptr); -} - -void Changeset::Printer::name(StringData n) -{ - pad_or_ellipsis(n, 16); -} - -void Changeset::Printer::print_field(StringData name, std::string value) -{ - if (!m_first) { - m_out << ", "; - } - m_first = false; - m_out << name << "=" << value; -} - -void Changeset::Printer::path(StringData name, InternString table, const Instruction::PrimaryKey& pk, - util::Optional field, const Instruction::Path* path) -{ - std::stringstream ss; - m_changeset->print_path(ss, table, pk, field, path); - print_field(name, ss.str()); -} - -void Changeset::Printer::field(StringData n, InternString value) -{ - std::stringstream ss; - ss << "\"" << m_changeset->get_string(value) << "\""; - print_field(n, ss.str()); -} - -void Changeset::Printer::field(StringData n, Instruction::Payload::Type type) -{ - print_field(n, get_type_name(type)); -} - -void Changeset::Printer::field(StringData n, Instruction::CollectionType type) -{ - print_field(n, get_collection_type(type)); -} - -std::string Changeset::Printer::primary_key_to_string(const Instruction::PrimaryKey& key) -{ - auto convert = overload{ - [&](const mpark::monostate&) { - return std::string("NULL"); - }, - [&](int64_t value) { - std::stringstream ss; - ss << value; - return ss.str(); - }, - [&](InternString str) { - std::stringstream ss; - ss << "\"" << m_changeset->get_string(str) << "\""; - return ss.str(); - }, - [&](GlobalKey key) { - std::stringstream ss; - ss << key; - return ss.str(); - }, - [&](ObjectId id) { - std::stringstream ss; - ss << id; - return ss.str(); - }, - [&](UUID uuid) { - return uuid.to_string(); - }, - }; - return mpark::visit(convert, key); -} - -void Changeset::Printer::field(StringData n, const Instruction::PrimaryKey& key) -{ - std::stringstream ss; - ss << format_pk(m_changeset->get_key(key)); - print_field(n, ss.str()); -} - -void Changeset::Printer::field(StringData n, const Instruction::Payload& value) -{ - std::stringstream ss; - m_changeset->print_value(ss, value); - print_field(n, ss.str()); -} - -void Changeset::Printer::field(StringData n, const Instruction::Path& path) -{ - std::stringstream ss; - ss << "["; - bool first = true; - for (auto& element : path) { - if (!first) { - ss << "."; - } - first = false; - - auto print = util::overload{ - [&](InternString field) { - ss << m_changeset->get_string(field); - }, - [&](uint32_t index) { - ss << index; - }, - }; - mpark::visit(print, element); - } - ss << "]"; - print_field(n, ss.str()); -} - -void Changeset::Printer::field(StringData n, uint32_t value) -{ - std::stringstream ss; - ss << value; - print_field(n, ss.str()); -} - -void Changeset::Printer::after_each() -{ - m_out << "\n"; - m_first = true; -} - -void Changeset::Printer::pad_or_ellipsis(StringData s, int width) const -{ - // FIXME: Does not work with UTF-8. - std::string str = s; // FIXME: StringData doesn't work with iomanip because it calls ios_base::write() directly - if (str.size() > size_t(width)) { - m_out << str.substr(0, width - 1) << "~"; - } - else { - m_out << std::left << std::setw(width) << str; - } -} - -#endif // REALM_DEBUG LCOV_EXCL_STOP diff --git a/src/realm/sync/changeset.hpp b/src/realm/sync/changeset.hpp deleted file mode 100644 index 4e8a79bb647..00000000000 --- a/src/realm/sync/changeset.hpp +++ /dev/null @@ -1,643 +0,0 @@ - -#ifndef REALM_SYNC_CHANGESET_HPP -#define REALM_SYNC_CHANGESET_HPP - -#include -#include - -#include - -namespace realm { -namespace sync { - -using InternStrings = std::vector; - -struct BadChangesetError : Exception { - BadChangesetError(const std::string& msg) - : Exception(ErrorCodes::BadChangeset, util::format("%1. Please contact support.", msg)) - { - } -}; - -struct Changeset { - struct Range; - using timestamp_type = uint_fast64_t; - using file_ident_type = uint_fast64_t; - using version_type = uint_fast64_t; // FIXME: Get from `History`. - - InternString intern_string(StringData); // Slow! - InternString find_string(StringData) const noexcept; // Slow! - StringData string_data() const noexcept; - - std::string& string_buffer() noexcept; - const std::string& string_buffer() const noexcept; - const InternStrings& interned_strings() const noexcept; - InternStrings& interned_strings() noexcept; - - StringBufferRange get_intern_string(InternString) const noexcept; - util::Optional try_get_intern_string(InternString) const noexcept; - util::Optional try_get_string(StringBufferRange) const noexcept; - util::Optional try_get_string(InternString) const noexcept; - StringData get_string(StringBufferRange) const noexcept; - StringData get_string(InternString) const noexcept; - StringBufferRange append_string(StringData); - - PrimaryKey get_key(const Instruction::PrimaryKey& value) const noexcept; - std::ostream& print_value(std::ostream& os, const Instruction::Payload& value) const noexcept; - std::ostream& print_path(std::ostream& os, const Instruction::Path& value) const noexcept; - std::ostream& print_path(std::ostream& os, InternString table, const Instruction::PrimaryKey& pk, - util::Optional field = util::none, - const Instruction::Path* path = nullptr) const; - - /// Mark the changeset as "dirty" (i.e. modified by the merge algorithm). - void set_dirty(bool dirty = true) noexcept; - - /// Whether or not the changeset is "dirty" (i.e. has been modified by the - /// merge algorithm). - bool is_dirty() const noexcept; - - // Interface to imitate std::vector: - template - struct IteratorImpl; - using iterator = IteratorImpl; - using const_iterator = IteratorImpl; - iterator begin() noexcept; - iterator end() noexcept; - const_iterator begin() const noexcept; - const_iterator end() const noexcept; - const_iterator cbegin() const noexcept; - const_iterator cend() const noexcept; - bool empty() const noexcept; - - /// Size of the Changeset, not counting tombstones. - /// - /// FIXME: This is an O(n) operation. - size_t size() const noexcept; - - void clear() noexcept; - - //@{ - /// Insert instructions, invalidating all iterators. - iterator insert(const_iterator pos, Instruction); - template - iterator insert(const_iterator pos, InputIt begin, InputIt end); - //@} - - /// Erase an instruction, invalidating all iterators. - iterator erase(const_iterator); - - /// Insert an instruction at the end, invalidating all iterators. - void push_back(const Instruction&); - - //@{ - /// Insert instructions at \a position without invalidating other - /// iterators. - /// - /// Only iterators created before any call to `insert_stable()` may be - /// considered stable across calls to `insert_stable()`. In addition, - /// "iterator stability" has a very specific meaning here: Other copies of - /// \a position in the program will point to the newly inserted elements - /// after calling `insert_stable()`, rather than point to the value at the - /// position prior to insertion. This is different from, say, a tree - /// structure, where iterator stability signifies the property that - /// iterators keep pointing to the same element after insertion before or - /// after that position. - /// - /// For the purpose of supporting `ChangesetIndex`, and the OT merge - /// algorithm, these semantics are acceptable, since prepended instructions - /// can never create new object or table references. - iterator insert_stable(const_iterator position, Instruction); - template - iterator insert_stable(const_iterator position, InputIt begin, InputIt end); - //@} - - /// Erase instruction at \a position without invalidating other iterators. - /// If erasing the object would invalidate other iterators, it is turned - /// into a tombstone instead, and subsequent derefencing of the iterator - /// will return `nullptr`. An iterator pointing to a tombstone remains valid - /// and can be incremented. - /// - /// Only iterators created before any call to `insert_stable()` may be - /// considered stable across calls to `erase_stable()`. If other copies of - /// \a position exist in the program, they will either point to the - /// subsequent element if that element was previously inserted with - /// `insert_stable()`, or otherwise it will be turned into a tombstone. - iterator erase_stable(const_iterator position); - -#if REALM_DEBUG - struct Reflector; - struct Printer; - void verify() const; - void print(std::ostream&) const; - void print() const; // prints to std::err -#endif - - /// The version that this changeset produced. Note: This may not be the - /// version produced by this changeset on the client on which this changeset - /// originated, but may for instance be the version produced on the server - /// after receiving and re-sending this changeset to another client. - /// - /// FIXME: The explanation above is confusing. The truth is that if this - /// changeset was received by a client from the server, then \a version is - /// the version that was produced on the server by this changeset. - /// - /// FIXME: This property, as well as \a last_integrated_remote_version, \a - /// origin_timestamp, and \a origin_file_ident should probably be removed - /// from this class, as they are not a logical part of a changeset, and also - /// are difficult to document without knowing more about what context the - /// changeset object occurs. Also, functions such as - /// InstructionApplier::apply() that a changeset as argument, but do not - /// care about those properties. - version_type version = 0; - - /// On clients, the last integrated server version. On the server, this is - /// the last integrated client version. - /// - /// FIXME: The explanation above is confusing. The truth is that if this - /// changeset was received by a client from the server, then \a - /// last_integrated_remote_version is the last client version that was - /// integrated by the server at the server version referencened by \a - /// version. - version_type last_integrated_remote_version = 0; - - /// Timestamp at origin when the original untransformed changeset was - /// produced. - timestamp_type origin_timestamp = 0; - - /// The identifier of the file in the context of which the original - /// untransformed changeset was produced. - file_ident_type origin_file_ident = 0; - - /// Must be set before passing this Changeset to Transformer::transform_remote_changesets - /// to the index of this changeset within the received changesets. - /// - /// In FLX sync the server may send multiple idempotent changesets with the same server version - /// when bootstrapping data. Internal data structures within the OT Transformer require the - /// input changesets to be sortable in the order that they were received. If the version number - /// is not increasing, this will be used to determine the correct sort order. - /// - /// FIXME: This is a hack that we need to figure out a better way of fixing. This can maybe - /// be part of refactoring the ChangesetIndex - size_t transform_sequence = 0; - - /// If the changeset was compacted during download, the size of the original - /// changeset. Only applies to changesets sent by the server. - std::size_t original_changeset_size = 0; - - /// Compare for exact equality, including that interned strings have the - /// same integer values, and there is the same number of interned strings, - /// same topology of tombstones, etc. - bool operator==(const Changeset& that) const noexcept; - bool operator!=(const Changeset& that) const noexcept; - -private: - std::vector m_instructions; - std::string m_string_buffer; - InternStrings m_strings; - bool m_is_dirty = false; - - iterator const_iterator_to_iterator(const_iterator); -}; - -std::ostream& operator<<(std::ostream&, const Changeset& changeset); - -/// An iterator type that hides the implementation details of the support for -/// iterator stability. -/// -/// A `Changeset::iterator` is composed of an -/// `std::vector::iterator` and a `size_t` representing -/// the index into the current `InstructionContainer`. If that container is -/// empty, and the position is zero, the iterator is pointing to a tombstone. -template -struct Changeset::IteratorImpl { - using list_type = std::vector; - using inner_iterator_type = std::conditional_t; - - // reference_type is a pointer because we have no way to create a reference - // to a tombstone instruction. Alternatively, it could have been - // `util::Optional`, but that runs into other issues. - using reference_type = std::conditional_t; - - using pointer_type = std::conditional_t; - using difference_type = std::ptrdiff_t; - - IteratorImpl() - : m_pos(0) - { - } - template - IteratorImpl(const IteratorImpl& other, std::enable_if_t* = nullptr) - : m_inner(other.m_inner) - , m_pos(other.m_pos) - { - } - IteratorImpl(inner_iterator_type inner, size_t pos = 0) - : m_inner(inner) - , m_pos(pos) - { - } - - inline IteratorImpl& operator++() - { - ++m_pos; - if (m_pos >= m_inner->size()) { - ++m_inner; - m_pos = 0; - } - return *this; - } - - IteratorImpl operator++(int) - { - auto copy = *this; - ++(*this); - return copy; - } - - IteratorImpl& operator--() - { - if (m_pos == 0) { - --m_inner; - m_pos = m_inner->size(); - if (m_pos != 0) - --m_pos; - } - else { - --m_pos; - } - return *this; - } - - IteratorImpl operator--(int) - { - auto copy = *this; - --(*this); - return copy; - } - - reference_type operator*() const - { - if (m_inner->size()) { - return &m_inner->at(m_pos); - } - // It was a tombstone. - return nullptr; - } - - pointer_type operator->() const - { - if (m_inner->size()) { - return &m_inner->at(m_pos); - } - // It was a tombstone. - return nullptr; - } - - bool operator==(const IteratorImpl& other) const - { - return m_inner == other.m_inner && m_pos == other.m_pos; - } - - bool operator!=(const IteratorImpl& other) const - { - return !(*this == other); - } - - bool operator<(const IteratorImpl& other) const - { - if (m_inner == other.m_inner) - return m_pos < other.m_pos; - return m_inner < other.m_inner; - } - - bool operator<=(const IteratorImpl& other) const - { - if (m_inner == other.m_inner) - return m_pos <= other.m_pos; - return m_inner < other.m_inner; - } - - bool operator>(const IteratorImpl& other) const - { - if (m_inner == other.m_inner) - return m_pos > other.m_pos; - return m_inner > other.m_inner; - } - - bool operator>=(const IteratorImpl& other) const - { - if (m_inner == other.m_inner) - return m_pos >= other.m_pos; - return m_inner > other.m_inner; - } - - inner_iterator_type m_inner; - size_t m_pos; -}; - -struct Changeset::Range { - iterator begin; - iterator end; -}; - -#if REALM_DEBUG -struct Changeset::Reflector { - struct Tracer { - virtual void name(StringData) = 0; - virtual void path(StringData, InternString table, const Instruction::PrimaryKey& object_key, - util::Optional field, const Instruction::Path* path) = 0; - virtual void field(StringData, InternString) = 0; - virtual void field(StringData, Instruction::Payload::Type) = 0; - virtual void field(StringData, Instruction::CollectionType) = 0; - virtual void field(StringData, const Instruction::PrimaryKey&) = 0; - virtual void field(StringData, const Instruction::Payload&) = 0; - virtual void field(StringData, const Instruction::Path&) = 0; - virtual void field(StringData, uint32_t) = 0; - virtual void set_changeset(const Changeset*) = 0; - virtual void after_each() {} - virtual void before_each() {} - }; - - Reflector(Tracer& tracer, const Changeset& changeset) - : m_tracer(tracer) - , m_changeset(changeset) - { - tracer.set_changeset(&changeset); - } - - void visit_all() const; - -private: - Tracer& m_tracer; - const Changeset& m_changeset; - - void table_instr(const Instruction::TableInstruction&) const; - void object_instr(const Instruction::ObjectInstruction&) const; - void path_instr(const Instruction::PathInstruction&) const; - - friend struct Instruction; -#define REALM_DEFINE_REFLECTOR_VISITOR(X) void operator()(const Instruction::X&) const; - REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_REFLECTOR_VISITOR) -#undef REALM_DEFINE_REFLECTOR_VISITOR -}; - -struct Changeset::Printer : Changeset::Reflector::Tracer { - explicit Printer(std::ostream& os) - : m_out(os) - { - } - - // ChangesetReflector::Tracer interface: - void name(StringData) final; - void path(StringData, InternString table, const Instruction::PrimaryKey&, util::Optional field, - const Instruction::Path* path) final; - void field(StringData, InternString) final; - void field(StringData, Instruction::Payload::Type) final; - void field(StringData, Instruction::CollectionType) final; - void field(StringData, const Instruction::PrimaryKey&) final; - void field(StringData, const Instruction::Payload&) final; - void field(StringData, const Instruction::Path&) final; - void field(StringData, uint32_t) final; - void set_changeset(const Changeset* changeset) final - { - m_changeset = changeset; - } - void after_each() final; - -private: - std::ostream& m_out; - bool m_first = true; - const Changeset* m_changeset = nullptr; - void pad_or_ellipsis(StringData, int width) const; - void print_field(StringData name, std::string value); - - std::string primary_key_to_string(const Instruction::PrimaryKey&); -}; -#endif // REALM_DEBUG - - -/// Implementation: - -inline Changeset::iterator Changeset::begin() noexcept -{ - return m_instructions.begin(); -} - -inline Changeset::iterator Changeset::end() noexcept -{ - return m_instructions.end(); -} - -inline Changeset::const_iterator Changeset::begin() const noexcept -{ - return m_instructions.begin(); -} - -inline Changeset::const_iterator Changeset::end() const noexcept -{ - return m_instructions.end(); -} - -inline Changeset::const_iterator Changeset::cbegin() const noexcept -{ - return m_instructions.cbegin(); -} - -inline Changeset::const_iterator Changeset::cend() const noexcept -{ - return m_instructions.end(); -} - -inline bool Changeset::empty() const noexcept -{ - return size() == 0; -} - -inline size_t Changeset::size() const noexcept -{ - size_t sum = 0; - for (auto& x : m_instructions) - sum += x.size(); - return sum; -} - -inline void Changeset::clear() noexcept -{ - m_instructions.clear(); -} - -inline util::Optional Changeset::try_get_intern_string(InternString string) const noexcept -{ - if (string.value >= m_strings.size()) - return util::none; - return m_strings[string.value]; -} - -inline StringBufferRange Changeset::get_intern_string(InternString string) const noexcept -{ - REALM_ASSERT(string.value < m_strings.size()); - return m_strings[string.value]; -} - -inline InternStrings& Changeset::interned_strings() noexcept -{ - return m_strings; -} - -inline const InternStrings& Changeset::interned_strings() const noexcept -{ - return m_strings; -} - -inline auto Changeset::string_buffer() noexcept -> std::string& -{ - return m_string_buffer; -} - -inline auto Changeset::string_buffer() const noexcept -> const std::string& -{ - return m_string_buffer; -} - -inline util::Optional Changeset::try_get_string(StringBufferRange range) const noexcept -{ - if (range.offset > m_string_buffer.size()) - return util::none; - if (range.offset + range.size > m_string_buffer.size()) - return util::none; - return StringData{m_string_buffer.data() + range.offset, range.size}; -} - -inline util::Optional Changeset::try_get_string(InternString str) const noexcept -{ - if (auto range = try_get_intern_string(str)) { - return try_get_string(*range); - } - return util::none; -} - -inline StringData Changeset::get_string(StringBufferRange range) const noexcept -{ - auto string = try_get_string(range); - REALM_ASSERT(string); - return *string; -} - -inline StringData Changeset::get_string(InternString string) const noexcept -{ - return get_string(get_intern_string(string)); -} - -inline StringData Changeset::string_data() const noexcept -{ - return StringData{m_string_buffer.data(), m_string_buffer.size()}; -} - -inline StringBufferRange Changeset::append_string(StringData string) -{ - // We expect more strings. Only do this at the beginning because until C++20, reserve - // will shrink_to_fit if the request is less than the current capacity. - constexpr size_t small_string_buffer_size = 1024; - if (m_string_buffer.capacity() < small_string_buffer_size) { - m_string_buffer.reserve(small_string_buffer_size); - } - size_t offset = m_string_buffer.size(); - m_string_buffer.append(string.data(), string.size()); - return StringBufferRange{uint32_t(offset), uint32_t(string.size())}; -} - -inline bool Changeset::is_dirty() const noexcept -{ - return m_is_dirty; -} - -inline void Changeset::set_dirty(bool dirty) noexcept -{ - m_is_dirty = dirty; -} - -inline Changeset::iterator Changeset::insert(const_iterator pos, Instruction instr) -{ - Instruction* p = &instr; - return insert(pos, p, p + 1); -} - -template -inline Changeset::iterator Changeset::insert(const_iterator pos, InputIt begin, InputIt end) -{ - if (pos.m_pos == 0) - return m_instructions.insert(pos.m_inner, begin, end); - return insert_stable(pos, begin, end); -} - -inline Changeset::iterator Changeset::erase(const_iterator pos) -{ - if (pos.m_inner->size() <= 1) - return m_instructions.erase(pos.m_inner); - return erase_stable(pos); -} - -inline Changeset::iterator Changeset::insert_stable(const_iterator pos, Instruction instr) -{ - Instruction* p = &instr; - return insert_stable(pos, p, p + 1); -} - -template -inline Changeset::iterator Changeset::insert_stable(const_iterator cpos, InputIt begin, InputIt end) -{ - iterator pos = const_iterator_to_iterator(cpos); - size_t i = 0; - for (auto it = begin; it != end; ++it, ++i) { - pos.m_inner->insert(pos.m_pos + i, *it); - } - return pos; -} - -inline Changeset::iterator Changeset::erase_stable(const_iterator cpos) -{ - auto pos = const_iterator_to_iterator(cpos); - auto begin = m_instructions.begin(); - auto end = m_instructions.end(); - REALM_ASSERT(pos.m_inner >= begin); - REALM_ASSERT(pos.m_inner < end); - pos.m_inner->erase(pos.m_pos); - if (pos.m_pos >= pos.m_inner->size()) { - do { - ++pos.m_inner; - } while (pos.m_inner != end && pos.m_inner->is_empty()); - pos.m_pos = 0; - } - return pos; -} - -inline void Changeset::push_back(const Instruction& instr) -{ - m_instructions.emplace_back(instr); -} - -inline auto Changeset::const_iterator_to_iterator(const_iterator cpos) -> iterator -{ - size_t offset = cpos.m_inner - m_instructions.cbegin(); - return iterator{m_instructions.begin() + offset, cpos.m_pos}; -} - -inline bool Changeset::operator!=(const Changeset& that) const noexcept -{ - return !(*this == that); -} - -} // namespace sync -} // namespace realm - -namespace std { - -template -struct iterator_traits> { - using difference_type = std::ptrdiff_t; - using iterator_category = std::bidirectional_iterator_tag; -}; - -} // namespace std - -#endif // REALM_SYNC_CHANGESET_HPP diff --git a/src/realm/sync/changeset_encoder.cpp b/src/realm/sync/changeset_encoder.cpp deleted file mode 100644 index 46e2a98e0e3..00000000000 --- a/src/realm/sync/changeset_encoder.cpp +++ /dev/null @@ -1,437 +0,0 @@ -#include -#include - -using namespace realm; -using namespace realm::sync; - -void ChangesetEncoder::operator()(const Instruction::AddTable& instr) -{ - auto spec = mpark::get_if(&instr.type); - const bool is_embedded = (spec == nullptr); - Table::Type table_type; - if (!is_embedded) { - if (spec->is_asymmetric) { - table_type = Table::Type::TopLevelAsymmetric; - } - else { - table_type = Table::Type::TopLevel; - } - } - else { - table_type = Table::Type::Embedded; - } - auto table_type_int = static_cast(table_type); - append(Instruction::Type::AddTable, instr.table, table_type_int); - if (!is_embedded) { - append_value(spec->pk_field); - append_value(spec->pk_type); - append_value(spec->pk_nullable); - } -} - -void ChangesetEncoder::operator()(const Instruction::EraseTable& instr) -{ - append(Instruction::Type::EraseTable, instr.table); -} - -void ChangesetEncoder::operator()(const Instruction::CreateObject& instr) -{ - append(Instruction::Type::CreateObject, instr.table, instr.object); -} - -void ChangesetEncoder::operator()(const Instruction::EraseObject& instr) -{ - append(Instruction::Type::EraseObject, instr.table, instr.object); -} - -void ChangesetEncoder::operator()(const Instruction::Update& instr) -{ - if (instr.is_array_update()) { - append_path_instr(Instruction::Type::Update, instr, instr.value, instr.prior_size); - } - else { - append_path_instr(Instruction::Type::Update, instr, instr.value, instr.is_default); - } -} - -// Appends sequence [value-type, dumb-value] -void ChangesetEncoder::append_value(const Instruction::Payload& payload) -{ - using Type = Instruction::Payload::Type; - - append_value(payload.type); - const auto& data = payload.data; - - switch (payload.type) { - case Type::GlobalKey: { - return append_value(data.key); - } - case Type::Int: { - return append_value(data.integer); - } - case Type::Bool: { - return append_value(data.boolean); - } - case Type::String: { - return append_string(data.str); - } - case Type::Binary: { - return append_string(data.binary); - } - case Type::Timestamp: { - return append_value(data.timestamp); - } - case Type::Float: { - return append_value(data.fnum); - } - case Type::Double: { - return append_value(data.dnum); - } - case Type::Decimal: { - return append_value(data.decimal); - } - case Type::ObjectId: { - return append_value(data.object_id); - } - case Type::UUID: { - return append_value(data.uuid); - } - case Type::Link: { - return append_value(data.link); - } - case Type::Erased: - [[fallthrough]]; - case Type::Set: - [[fallthrough]]; - case Type::List: - [[fallthrough]]; - case Type::Dictionary: - [[fallthrough]]; - case Type::ObjectValue: - [[fallthrough]]; - case Type::Null: - // The payload type does not carry additional data. - return; - } - REALM_TERMINATE("Invalid payload type."); -} - -void ChangesetEncoder::append_value(Instruction::Payload::Type type) -{ - append_value(int64_t(type)); -} - -void ChangesetEncoder::append_value(Instruction::CollectionType type) -{ - append_value(uint8_t(type)); -} - -void ChangesetEncoder::append_value(const Instruction::Payload::Link& link) -{ - append_value(link.target_table); - append_value(link.target); -} - -void ChangesetEncoder::append_value(const Instruction::PrimaryKey& pk) -{ - using Type = Instruction::Payload::Type; - auto append = util::overload{ - [&](mpark::monostate) { - append_value(Type::Null); - }, - [&](int64_t value) { - append_value(Type::Int); - append_value(value); - }, - [&](InternString str) { - // Note: Contextual difference. In payloads, Type::String denotes a - // StringBufferRange, but here it denotes to an InternString. - append_value(Type::String); - append_value(str); - }, - [&](GlobalKey key) { - append_value(Type::GlobalKey); - append_value(key); - }, - [&](ObjectId id) { - append_value(Type::ObjectId); - append_value(id); - }, - [&](UUID uuid) { - append_value(Type::UUID); - append_value(uuid); - }, - }; - mpark::visit(std::move(append), pk); -} - -void ChangesetEncoder::append_value(const Instruction::Path& path) -{ - append_value(uint32_t(path.size())); - for (auto& element : path) { - // Integer path elements are encoded as their integer values. - // String path elements are encoded as [-1, intern_string_id]. - if (auto index = mpark::get_if(&element)) { - append_value(int64_t(*index)); - } - else if (auto name = mpark::get_if(&element)) { - // Since indices cannot be negative, use -1 to indicate that the path element is a - // string. - append_value(int64_t(-1)); - append_value(*name); - } - } -} - -void ChangesetEncoder::operator()(const Instruction::AddInteger& instr) -{ - append_path_instr(Instruction::Type::AddInteger, instr, instr.value); -} - -void ChangesetEncoder::operator()(const Instruction::AddColumn& instr) -{ - bool is_dictionary = (instr.collection_type == Instruction::CollectionType::Dictionary); - // Mixed columns are always nullable. - REALM_ASSERT(instr.type != Instruction::Payload::Type::Null || instr.nullable || is_dictionary); - append(Instruction::Type::AddColumn, instr.table, instr.field, instr.type, instr.nullable, instr.collection_type); - - if (instr.type == Instruction::Payload::Type::Link) { - append_value(instr.link_target_table); - } - if (is_dictionary) { - append_value(instr.key_type); - } -} - -void ChangesetEncoder::operator()(const Instruction::EraseColumn& instr) -{ - append(Instruction::Type::EraseColumn, instr.table, instr.field); -} - -void ChangesetEncoder::operator()(const Instruction::ArrayInsert& instr) -{ - append_path_instr(Instruction::Type::ArrayInsert, instr, instr.value, instr.prior_size); -} - -void ChangesetEncoder::operator()(const Instruction::ArrayMove& instr) -{ - append_path_instr(Instruction::Type::ArrayMove, instr, instr.ndx_2, instr.prior_size); -} - -void ChangesetEncoder::operator()(const Instruction::ArrayErase& instr) -{ - append_path_instr(Instruction::Type::ArrayErase, instr, instr.prior_size); -} - -void ChangesetEncoder::operator()(const Instruction::Clear& instr) -{ - append_path_instr(Instruction::Type::Clear, instr, instr.collection_type); -} - -void ChangesetEncoder::operator()(const Instruction::SetInsert& instr) -{ - append_path_instr(Instruction::Type::SetInsert, instr, instr.value); -} - -void ChangesetEncoder::operator()(const Instruction::SetErase& instr) -{ - append_path_instr(Instruction::Type::SetErase, instr, instr.value); -} - -InternString ChangesetEncoder::intern_string(StringData str) -{ - auto it = m_intern_strings_rev.find(static_cast(str)); - if (it == m_intern_strings_rev.end()) { - size_t index = m_intern_strings_rev.size(); - // FIXME: Assert might be able to be removed after refactoring of changeset_parser types? - REALM_ASSERT_RELEASE_EX(index <= std::numeric_limits::max(), index); - bool inserted; - std::tie(it, inserted) = m_intern_strings_rev.insert({std::string{str}, uint32_t(index)}); - REALM_ASSERT_RELEASE_EX(inserted, str); - - StringBufferRange range = add_string_range(str); - set_intern_string(uint32_t(index), range); - } - - return InternString{it->second}; -} - -void ChangesetEncoder::set_intern_string(uint32_t index, StringBufferRange range) -{ - // Emit InternString metainstruction: - append_int(uint64_t(InstrTypeInternString)); - append_int(index); - append_string(range); -} - -StringBufferRange ChangesetEncoder::add_string_range(StringData data) -{ - m_string_range = static_cast(data); - REALM_ASSERT(data.size() <= std::numeric_limits::max()); - return StringBufferRange{0, uint32_t(data.size())}; -} - -void ChangesetEncoder::append_bytes(const void* bytes, size_t size) -{ - // FIXME: It would be better to move ownership of `m_buffer` to the caller, - // potentially reducing the number of allocations to zero (amortized). - m_buffer.reserve(1024); // lower the amount of reallocations - m_buffer.append(static_cast(bytes), size); -} - -void ChangesetEncoder::append_string(StringBufferRange str) -{ - REALM_ASSERT(str.offset + str.size <= m_string_range.size()); - append_value(uint64_t(str.size)); - append_bytes(m_string_range.data() + str.offset, str.size); -} - -template -void ChangesetEncoder::append(Instruction::Type t, Args&&... args) -{ - append_value(uint8_t(t)); - int unpack[] = {0, (append_value(args), 0)...}; - static_cast(unpack); -} - -template -void ChangesetEncoder::append_path_instr(Instruction::Type t, const Instruction::PathInstruction& instr, - Args&&... args) -{ - append_value(uint8_t(t)); - append_value(instr.table); - append_value(instr.object); - append_value(instr.field); - append_value(instr.path); - (append_value(std::forward(args)), ...); -} - -template -void ChangesetEncoder::append_int(T integer) -{ - char buffer[_impl::encode_int_max_bytes()]; - std::size_t n = _impl::encode_int(buffer, integer); - append_bytes(buffer, n); -} - -void ChangesetEncoder::append_value(DataType type) -{ - append_value(uint64_t(type)); -} - -void ChangesetEncoder::append_value(bool v) -{ - // Reduce template instantiations of append_int - append_value(uint8_t(v)); -} - -void ChangesetEncoder::append_value(uint8_t integer) -{ - // Reduce template instantiations of append_int - append_value(uint64_t(integer)); -} - -void ChangesetEncoder::append_value(uint32_t integer) -{ - // Reduce template instantiations of append_int - append_value(uint64_t(integer)); -} - -void ChangesetEncoder::append_value(uint64_t integer) -{ - append_int(integer); -} - -void ChangesetEncoder::append_value(int64_t integer) -{ - append_int(integer); -} - -void ChangesetEncoder::append_value(float number) -{ - append_bytes(&number, sizeof(number)); -} - -void ChangesetEncoder::append_value(double number) -{ - append_bytes(&number, sizeof(number)); -} - -void ChangesetEncoder::append_value(InternString str) -{ - REALM_ASSERT(str != InternString::npos); - append_value(str.value); -} - -void ChangesetEncoder::append_value(GlobalKey oid) -{ - append_value(oid.hi()); - append_value(oid.lo()); -} - -void ChangesetEncoder::append_value(Timestamp timestamp) -{ - append_value(timestamp.get_seconds()); - append_value(int64_t(timestamp.get_nanoseconds())); -} - -void ChangesetEncoder::append_value(ObjectId id) -{ - append_bytes(&id, sizeof(id)); -} - -void ChangesetEncoder::append_value(UUID id) -{ - const auto bytes = id.to_bytes(); - append_bytes(bytes.data(), bytes.size()); -} - -void ChangesetEncoder::append_value(Decimal128 id) -{ - Decimal128::Bid128 cx; - int exp; - bool sign; - id.unpack(cx, exp, sign); - constexpr int max_bytes = 17; // 113 bits / 7 - char buffer[max_bytes]; - _impl::Bid128 tmp; - memcpy(&tmp, &cx, sizeof(Decimal128::Bid128)); - auto n = _impl::encode_int(buffer, tmp); - REALM_ASSERT(n <= max_bytes); - append_bytes(buffer, n); - append_value(int64_t(exp)); - append_value(sign); -} - -auto ChangesetEncoder::release() noexcept -> Buffer -{ - m_intern_strings_rev.clear(); - Buffer buffer; - std::swap(buffer, m_buffer); - return buffer; -} - -void ChangesetEncoder::reset() noexcept -{ - m_intern_strings_rev.clear(); - m_buffer.clear(); -} - -void ChangesetEncoder::encode_single(const Changeset& log) -{ - // Checking if the log is empty avoids serialized interned strings in a - // changeset where all meaningful instructions have been discarded due to - // merge or compaction. - if (!log.empty()) { - add_string_range(log.string_data()); - const auto& strings = log.interned_strings(); - for (size_t i = 0; i < strings.size(); ++i) { - set_intern_string(uint32_t(i), strings[i]); // Throws - } - for (auto instr : log) { - if (!instr) - continue; - (*this)(*instr); // Throws - } - } -} diff --git a/src/realm/sync/changeset_encoder.hpp b/src/realm/sync/changeset_encoder.hpp deleted file mode 100644 index 412c429e684..00000000000 --- a/src/realm/sync/changeset_encoder.hpp +++ /dev/null @@ -1,110 +0,0 @@ - -#ifndef REALM_SYNC_CHANGESET_ENCODER_HPP -#define REALM_SYNC_CHANGESET_ENCODER_HPP - -#include - -namespace realm { -namespace sync { - -struct ChangesetEncoder { - using Buffer = util::AppendBuffer; - - Buffer release() noexcept; - void reset() noexcept; - Buffer& buffer() noexcept; - InternString intern_string(StringData); - - void set_intern_string(uint32_t index, StringBufferRange); - // FIXME: This doesn't copy the input, but the drawback is that there can - // only be a single StringBufferRange per instruction. Luckily, no - // instructions exist that require two or more. - StringBufferRange add_string_range(StringData); - void operator()(const Instruction&); - -#define REALM_DEFINE_INSTRUCTION_HANDLER(X) void operator()(const Instruction::X&); - REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_INSTRUCTION_HANDLER) -#undef REALM_DEFINE_INSTRUCTION_HANDLER - - void encode_single(const Changeset& log); - -protected: - template - static void encode(E& encoder, const Instruction&); - - StringData get_string(StringBufferRange) const noexcept; - -private: - template - void append(Instruction::Type t, Args&&...); - template - void append_path_instr(Instruction::Type t, const Instruction::PathInstruction&, Args&&...); - void append_string(StringBufferRange); // does not intern the string - void append_bytes(const void*, size_t); - - template - void append_int(T); - void append_value(const Instruction::PrimaryKey&); - void append_value(const Instruction::Payload&); - void append_value(const Instruction::Payload::Link&); - void append_value(Instruction::Payload::Type); - void append_value(util::Optional); - void append_value(Instruction::CollectionType); - void append_value(const Instruction::Path&); - void append_value(DataType); - void append_value(bool); - void append_value(uint8_t); - void append_value(int64_t); - void append_value(uint32_t); - void append_value(uint64_t); - void append_value(float); - void append_value(double); - void append_value(InternString); - void append_value(GlobalKey); - void append_value(Timestamp); - void append_value(ObjectId); - void append_value(Decimal128); - void append_value(UUID); - - Buffer m_buffer; - std::map> m_intern_strings_rev; - std::string_view m_string_range; -}; - -// Implementation - -inline auto ChangesetEncoder::buffer() noexcept -> Buffer& -{ - return m_buffer; -} - -inline void ChangesetEncoder::operator()(const Instruction& instr) -{ - encode(*this, instr); // Throws -} - -template -inline void ChangesetEncoder::encode(E& encoder, const Instruction& instr) -{ - instr.visit(encoder); // Throws -} - -inline StringData ChangesetEncoder::get_string(StringBufferRange range) const noexcept -{ - const char* data = m_string_range.data() + range.offset; - std::size_t size = std::size_t(range.size); - return StringData{data, size}; -} - -inline void encode_changeset(const Changeset& changeset, ChangesetEncoder::Buffer& out_buffer) -{ - ChangesetEncoder encoder; - swap(encoder.buffer(), out_buffer); - encoder.encode_single(changeset); // Throws - swap(encoder.buffer(), out_buffer); -} - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_CHANGESET_ENCODER_HPP diff --git a/src/realm/sync/changeset_parser.cpp b/src/realm/sync/changeset_parser.cpp deleted file mode 100644 index 7d8f541c669..00000000000 --- a/src/realm/sync/changeset_parser.cpp +++ /dev/null @@ -1,725 +0,0 @@ - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace realm; -using namespace realm::sync; - -namespace { - -struct State { - util::InputStream& m_input; - InstructionHandler& m_handler; - - explicit State(util::InputStream& input, InstructionHandler& handler) - : m_input(input) - , m_handler(handler) - { - } - - // pointer into transaction log, each instruction is parsed from m_input_begin and onwards. - // Each instruction are assumed to be contiguous in memory. - const char* m_input_begin = nullptr; - // pointer to one past current instruction log chunk. If m_input_begin reaches m_input_end, - // a call to next_input_buffer will move m_input_begin and m_input_end to a new chunk of - // memory. Setting m_input_end to 0 disables this check, and is used if it is already known - // that all of the instructions are in memory. - const char* m_input_end = nullptr; - - std::string m_buffer; - // Cannot use StringData as key type since m_input_begin may start pointing - // to a new chunk of memory. - std::unordered_set m_intern_strings; - - - void parse_one(); // Throws - bool has_next() noexcept; - - // Advance m_input_begin and m_input_end to reflect the next block of - // instructions. - // Returns false if no more input was available - bool next_input_buffer() noexcept; - - template - T read_int(); // Throws - - util::Optional read_optional_payload_type(); - Instruction::Payload::Type read_payload_type(); - Instruction::CollectionType read_collection_type(); - Instruction::Payload read_payload(); - Instruction::Payload::Link read_link(); - Instruction::PrimaryKey read_object_key(); - Instruction::Path read_path(); - bool read_char(char& c) noexcept; - void read_bytes(char* data, size_t size); // Throws - bool read_bool(); // Throws - float read_float(); // Throws - double read_double(); // Throws - InternString read_intern_string(); // Throws - GlobalKey read_global_key(); // Throws - Timestamp read_timestamp(); // Throws - ObjectId read_object_id(); // Throws - Decimal128 read_decimal(); // Throws - UUID read_uuid(); // Throws - - void read_path_instr(Instruction::PathInstruction& instr); - - // Reads a string value from the stream. The returned value is only valid - // until the next call to `read_string()` or `read_binary()`. - StringData read_string(); // Throws - - // Reads a binary blob value from the stream. The returned value is only - // valid until the next call to `read_string()` or `read_binary()`. - BinaryData read_binary(); // Throws - - BinaryData read_buffer(size_t size); - - REALM_NORETURN void parser_error(std::string_view complaint); // Throws - REALM_NORETURN void parser_error() - { - parser_error("Bad input"); - } // Throws -}; - -struct UnreachableInstructionHandler : public InstructionHandler { - void set_intern_string(uint32_t, StringBufferRange) override - { - REALM_UNREACHABLE(); - } - - StringBufferRange add_string_range(StringData) override - { - REALM_UNREACHABLE(); - } - - void operator()(const Instruction&) override - { - REALM_UNREACHABLE(); - } -}; - -struct InstructionBuilder : InstructionHandler { - explicit InstructionBuilder(Changeset& log) - : m_log(log) - { - log.interned_strings().clear(); - } - Changeset& m_log; - - void operator()(const Instruction& instr) final - { - m_log.push_back(instr); - } - - StringBufferRange add_string_range(StringData string) final - { - return m_log.append_string(string); - } - - void set_intern_string(uint32_t index, StringBufferRange range) final - { - InternStrings& strings = m_log.interned_strings(); - REALM_ASSERT(index == strings.size()); - strings.push_back(range); - } -}; - -Instruction::Payload::Type State::read_payload_type() -{ - using Type = Instruction::Payload::Type; - auto type = Instruction::Payload::Type(read_int()); - // Validate the type. - switch (type) { - case Type::GlobalKey: - [[fallthrough]]; - case Type::Erased: - [[fallthrough]]; - case Type::Set: - [[fallthrough]]; - case Type::List: - [[fallthrough]]; - case Type::Dictionary: - [[fallthrough]]; - case Type::ObjectValue: - [[fallthrough]]; - case Type::Null: - [[fallthrough]]; - case Type::Int: - [[fallthrough]]; - case Type::Bool: - [[fallthrough]]; - case Type::String: - [[fallthrough]]; - case Type::Binary: - [[fallthrough]]; - case Type::Timestamp: - [[fallthrough]]; - case Type::Float: - [[fallthrough]]; - case Type::Double: - [[fallthrough]]; - case Type::Decimal: - [[fallthrough]]; - case Type::Link: - [[fallthrough]]; - case Type::ObjectId: - [[fallthrough]]; - case Type::UUID: - return type; - } - parser_error("Unsupported data type"); -} - -Instruction::CollectionType State::read_collection_type() -{ - using CollectionType = Instruction::CollectionType; - auto type = Instruction::CollectionType(read_int()); - // Validate the type. - switch (type) { - case CollectionType::Single: - [[fallthrough]]; - case CollectionType::List: - [[fallthrough]]; - case CollectionType::Dictionary: - [[fallthrough]]; - case CollectionType::Set: - return type; - } - parser_error("Unsupported collection type"); -} - -Instruction::Payload State::read_payload() -{ - using Type = Instruction::Payload::Type; - - Instruction::Payload payload; - payload.type = read_payload_type(); - auto& data = payload.data; - switch (payload.type) { - case Type::GlobalKey: { - parser_error("Unsupported payload data type"); - } - case Type::Int: { - data.integer = read_int(); - return payload; - } - case Type::Bool: { - data.boolean = read_bool(); - return payload; - } - case Type::Float: { - data.fnum = read_float(); - return payload; - } - case Type::Double: { - data.dnum = read_double(); - return payload; - } - case Type::String: { - StringData value = read_string(); - data.str = m_handler.add_string_range(value); - return payload; - } - case Type::Binary: { - BinaryData value = read_binary(); - data.binary = m_handler.add_string_range(StringData{value.data(), value.size()}); - return payload; - } - case Type::Timestamp: { - data.timestamp = read_timestamp(); - return payload; - } - case Type::ObjectId: { - data.object_id = read_object_id(); - return payload; - } - case Type::Decimal: { - data.decimal = read_decimal(); - return payload; - } - case Type::UUID: { - data.uuid = read_uuid(); - return payload; - } - case Type::Link: { - data.link = read_link(); - return payload; - } - - case Type::Null: - [[fallthrough]]; - case Type::Set: - [[fallthrough]]; - case Type::List: - [[fallthrough]]; - case Type::Dictionary: - [[fallthrough]]; - case Type::Erased: - [[fallthrough]]; - case Type::ObjectValue: - return payload; - } - - parser_error("Unsupported payload type"); -} - -Instruction::PrimaryKey State::read_object_key() -{ - using Type = Instruction::Payload::Type; - Type type = read_payload_type(); - switch (type) { - case Type::Null: - return mpark::monostate{}; - case Type::Int: - return read_int(); - case Type::String: - return read_intern_string(); - case Type::GlobalKey: - return read_global_key(); - case Type::ObjectId: - return read_object_id(); - case Type::UUID: - return read_uuid(); - default: - break; - } - parser_error("Unsupported object key type"); -} - -Instruction::Payload::Link State::read_link() -{ - auto target_class = read_intern_string(); - auto key = read_object_key(); - return Instruction::Payload::Link{target_class, key}; -} - -Instruction::Path State::read_path() -{ - Instruction::Path path; - size_t path_len = read_int(); - - // Note: Not reserving `path_len`, because a corrupt changeset could cause std::bad_alloc to be thrown. - if (path_len != 0) - path.reserve(16); - - for (size_t i = 0; i < path_len; ++i) { - int64_t element = read_int(); - if (element >= 0) { - // Integer path element - path.push_back(uint32_t(element)); - } - else { - // String path element - path.push_back(read_intern_string()); - } - } - - return path; -} - -void State::read_path_instr(Instruction::PathInstruction& instr) -{ - instr.table = read_intern_string(); - instr.object = read_object_key(); - instr.field = read_intern_string(); - instr.path = read_path(); -} - -void State::parse_one() -{ - uint64_t t = read_int(); - - if (t == InstrTypeInternString) { - uint32_t index = read_int(); - if (index != m_intern_strings.size()) { - parser_error(util::format("Unexpected intern index: %1", index)); - } - StringData str = read_string(); - if (!m_intern_strings.insert(str).second) { - parser_error(util::format("Unexpected intern string: %1", str)); - } - StringBufferRange range = m_handler.add_string_range(str); - m_handler.set_intern_string(index, range); - return; - } - - switch (Instruction::Type(t)) { - case Instruction::Type::AddTable: { - Instruction::AddTable instr; - instr.table = read_intern_string(); - auto table_type = Table::Type(read_int()); - switch (table_type) { - case Table::Type::TopLevel: - case Table::Type::TopLevelAsymmetric: { - Instruction::AddTable::TopLevelTable spec; - spec.pk_field = read_intern_string(); - spec.pk_type = read_payload_type(); - if (!is_valid_key_type(spec.pk_type)) { - parser_error(util::format("Invalid primary key type in AddTable: %1", - static_cast(spec.pk_type))); - } - spec.pk_nullable = read_bool(); - spec.is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric); - instr.type = spec; - break; - } - case Table::Type::Embedded: { - instr.type = Instruction::AddTable::EmbeddedTable{}; - break; - } - default: - parser_error(util::format("AddTable: unknown table type: %1", table_type)); - } - m_handler(instr); - return; - } - case Instruction::Type::EraseTable: { - Instruction::EraseTable instr; - instr.table = read_intern_string(); - m_handler(instr); - return; - } - case Instruction::Type::CreateObject: { - Instruction::CreateObject instr; - instr.table = read_intern_string(); - instr.object = read_object_key(); - m_handler(instr); - return; - } - case Instruction::Type::EraseObject: { - Instruction::EraseObject instr; - instr.table = read_intern_string(); - instr.object = read_object_key(); - m_handler(instr); - return; - } - case Instruction::Type::Update: { - Instruction::Update instr; - read_path_instr(instr); - instr.value = read_payload(); - - // If the last path element is a string, we are setting a field. Otherwise, we are setting an array - // element. - if (!instr.is_array_update()) { - instr.is_default = read_bool(); - } - else { - instr.prior_size = read_int(); - } - m_handler(instr); - return; - } - case Instruction::Type::AddInteger: { - Instruction::AddInteger instr; - read_path_instr(instr); - instr.value = read_int(); - m_handler(instr); - return; - } - case Instruction::Type::AddColumn: { - Instruction::AddColumn instr; - instr.table = read_intern_string(); - instr.field = read_intern_string(); - instr.type = read_payload_type(); - instr.nullable = read_bool(); - instr.collection_type = read_collection_type(); - if (instr.type == Instruction::Payload::Type::Link) { - instr.link_target_table = read_intern_string(); - } - if (instr.collection_type == Instruction::CollectionType::Dictionary) { - instr.key_type = read_payload_type(); - } - else { - instr.key_type = Instruction::Payload::Type::Null; - } - m_handler(instr); - return; - } - case Instruction::Type::EraseColumn: { - Instruction::EraseColumn instr; - instr.table = read_intern_string(); - instr.field = read_intern_string(); - m_handler(instr); - return; - } - case Instruction::Type::ArrayInsert: { - Instruction::ArrayInsert instr; - read_path_instr(instr); - if (!instr.path.is_array_index()) { - parser_error("ArrayInsert without an index"); - } - instr.value = read_payload(); - instr.prior_size = read_int(); - m_handler(instr); - return; - } - case Instruction::Type::ArrayMove: { - Instruction::ArrayMove instr; - read_path_instr(instr); - if (!instr.path.is_array_index()) { - parser_error("ArrayMove without an index"); - } - instr.ndx_2 = read_int(); - instr.prior_size = read_int(); - m_handler(instr); - return; - } - case Instruction::Type::ArrayErase: { - Instruction::ArrayErase instr; - read_path_instr(instr); - if (!instr.path.is_array_index()) { - parser_error("ArrayErase without an index"); - } - instr.prior_size = read_int(); - m_handler(instr); - return; - } - case Instruction::Type::Clear: { - Instruction::Clear instr; - read_path_instr(instr); - instr.collection_type = read_collection_type(); - m_handler(instr); - return; - } - case Instruction::Type::SetInsert: { - Instruction::SetInsert instr; - read_path_instr(instr); - instr.value = read_payload(); - m_handler(instr); - return; - } - case Instruction::Type::SetErase: { - Instruction::SetErase instr; - read_path_instr(instr); - instr.value = read_payload(); - m_handler(instr); - return; - } - } - - parser_error(util::format("Unknown instruction type: %1", t)); -} - - -bool State::has_next() noexcept -{ - return m_input_begin != m_input_end || next_input_buffer(); -} - -bool State::next_input_buffer() noexcept -{ - auto next = m_input.next_block(); - m_input_begin = next.begin(); - m_input_end = next.end(); - return m_input_begin != m_input_end; -} - -template -T State::read_int() -{ - T value = 0; - if (REALM_LIKELY(_impl::decode_int(*this, value))) - return value; - parser_error("bad changeset - integer decoding failure"); -} - -bool State::read_char(char& c) noexcept -{ - if (m_input_begin == m_input_end && !next_input_buffer()) - return false; - c = *m_input_begin++; - return true; -} - -void State::read_bytes(char* data, size_t size) -{ - for (;;) { - const size_t avail = m_input_end - m_input_begin; - if (size <= avail) - break; - std::copy_n(m_input_begin, avail, data); - if (!next_input_buffer()) - parser_error("truncated input"); - data += avail; - size -= avail; - } - const char* to = m_input_begin + size; - std::copy_n(m_input_begin, size, data); - m_input_begin = to; -} - -bool State::read_bool() -{ - return read_int(); // Throws -} - -float State::read_float() -{ - static_assert(std::numeric_limits::is_iec559 && - sizeof(float) * std::numeric_limits::digits == 32, - "Unsupported 'float' representation"); - float value; - read_bytes(reinterpret_cast(&value), sizeof value); // Throws - return value; -} - -double State::read_double() -{ - static_assert(std::numeric_limits::is_iec559 && - sizeof(double) * std::numeric_limits::digits == 64, - "Unsupported 'double' representation"); - double value; - read_bytes(reinterpret_cast(&value), sizeof value); // Throws - return value; -} - -InternString State::read_intern_string() -{ - uint32_t index = read_int(); // Throws - if (index >= m_intern_strings.size()) - parser_error("Invalid interned string"); - return InternString{index}; -} - -GlobalKey State::read_global_key() -{ - uint64_t hi = read_int(); // Throws - uint64_t lo = read_int(); // Throws - return GlobalKey{hi, lo}; -} - -Timestamp State::read_timestamp() -{ - int64_t seconds = read_int(); // Throws - int64_t nanoseconds = read_int(); // Throws - if (nanoseconds > std::numeric_limits::max()) - parser_error("timestamp out of range"); - return Timestamp{seconds, int32_t(nanoseconds)}; -} - -ObjectId State::read_object_id() -{ - // FIXME: This is completely wrong and unsafe. - ObjectId id; - read_bytes(reinterpret_cast(&id), sizeof(id)); - return id; -} - -UUID State::read_uuid() -{ - UUID::UUIDBytes bytes{}; - read_bytes(reinterpret_cast(bytes.data()), bytes.size()); - return UUID(bytes); -} - -Decimal128 State::read_decimal() -{ - _impl::Bid128 cx; - if (!_impl::decode_int(*this, cx)) - parser_error("bad changeset - decimal decoding failure"); - - int exp = read_int(); - bool sign = read_int() != 0; - Decimal128::Bid128 tmp; - memcpy(&tmp, &cx, sizeof(Decimal128::Bid128)); - return Decimal128(tmp, exp, sign); -} - -StringData State::read_string() -{ - uint64_t size = read_int(); // Throws - - if (size > realm::Table::max_string_size) - parser_error("string too long"); // Throws - if (size > std::numeric_limits::max()) - parser_error("invalid length"); // Throws - - BinaryData buffer = read_buffer(size_t(size)); - return StringData{buffer.data(), size_t(size)}; -} - -BinaryData State::read_binary() -{ - uint64_t size = read_int(); // Throws - - if (size > std::numeric_limits::max()) - parser_error("invalid binary length"); // Throws - - return read_buffer(size_t(size)); -} - -BinaryData State::read_buffer(size_t size) -{ - const size_t avail = m_input_end - m_input_begin; - if (avail >= size) { - m_input_begin += size; - return BinaryData(m_input_begin - size, size); - } - - m_buffer.clear(); - m_buffer.resize(size); // Throws - read_bytes(m_buffer.data(), size); - return BinaryData(m_buffer.data(), size); -} - -void State::parser_error(std::string_view complaints) -{ - throw BadChangesetError{std::string(complaints)}; -} - -} // anonymous namespace - -namespace realm::sync { - -void parse_changeset(util::InputStream& input, Changeset& out_log) -{ - InstructionBuilder builder{out_log}; - State state{input, builder}; - - while (state.has_next()) - state.parse_one(); -} - -OwnedMixed parse_base64_encoded_primary_key(std::string_view str) -{ - auto bin_encoded = util::base64_decode_to_vector(str); - if (!bin_encoded) { - throw BadChangesetError("invalid base64 in base64-encoded primary key"); - } - util::SimpleInputStream stream(*bin_encoded); - UnreachableInstructionHandler fake_encoder; - State state{stream, fake_encoder}; - using Type = Instruction::Payload::Type; - Type type = state.read_payload_type(); - switch (type) { - case Type::Null: - return OwnedMixed{}; - case Type::Int: - return OwnedMixed{state.read_int()}; - case Type::String: { - auto str = state.read_string(); - return OwnedMixed{std::string{str.data(), str.size()}}; - } - case Type::GlobalKey: - // GlobalKey's are not actually used as primary keys in sync. We currently have wire protocol support - // for them, but we've never sent them to the sync server. - REALM_UNREACHABLE(); - case Type::ObjectId: - return OwnedMixed{state.read_object_id()}; - case Type::UUID: - return OwnedMixed{state.read_uuid()}; - default: - throw BadChangesetError(util::format("invalid primary key type %1", static_cast(type))); - } -} - -} // namespace realm::sync diff --git a/src/realm/sync/changeset_parser.hpp b/src/realm/sync/changeset_parser.hpp deleted file mode 100644 index c88c0c5853c..00000000000 --- a/src/realm/sync/changeset_parser.hpp +++ /dev/null @@ -1,18 +0,0 @@ - -#ifndef REALM_SYNC_CHANGESET_PARSER_HPP -#define REALM_SYNC_CHANGESET_PARSER_HPP - -#include -#include -#include - -namespace realm::sync { -void parse_changeset(util::InputStream&, Changeset& out_log); - -// The server may send us primary keys of objects in json-encoded error messages as base64-encoded changeset payloads. -// This function takes such a base64-encoded payload and returns it parsed as an owned Mixed value. If it cannot -// be decoded, this throws a BadChangeset exception. -OwnedMixed parse_base64_encoded_primary_key(std::string_view str); -} // namespace realm::sync - -#endif // REALM_SYNC_CHANGESET_PARSER_HPP diff --git a/src/realm/sync/client.cpp b/src/realm/sync/client.cpp deleted file mode 100644 index 214a938969c..00000000000 --- a/src/realm/sync/client.cpp +++ /dev/null @@ -1,1954 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace realm::sync { -namespace { -using namespace realm::util; - - -// clang-format off -using SessionImpl = ClientImpl::Session; -using SyncTransactCallback = Session::SyncTransactCallback; -using ProgressHandler = Session::ProgressHandler; -using WaitOperCompletionHandler = Session::WaitOperCompletionHandler; -using ConnectionStateChangeListener = Session::ConnectionStateChangeListener; -using port_type = Session::port_type; -using connection_ident_type = std::int_fast64_t; -using ProxyConfig = SyncConfig::ProxyConfig; -// clang-format on - -} // unnamed namespace - - -// Life cycle states of a session wrapper: -// -// The session wrapper begins life with an associated Client, but no underlying -// SessionImpl. On construction, it begins the actualization process by posting -// a job to the client's event loop. That job will set `m_sess` to a session impl -// and then set `m_actualized = true`. Once this happens `m_actualized` will -// never change again. -// -// When the external reference to the session (`sync::Session`, which in -// non-test code is always owned by a `SyncSession`) is destroyed, the wrapper -// begins finalization. If the wrapper has not yet been actualized this takes -// place immediately and `m_finalized = true` is set directly on the calling -// thread. If it has been actualized, a job is posted to the client's event loop -// which will tear down the session and then set `m_finalized = true`. Regardless -// of whether or not the session has been actualized, `m_abandoned = true` is -// immediately set when the external reference is released. -// -// When the associated Client is destroyed it calls force_close() on all -// actualized wrappers from its event loop. This causes the wrapper to tear down -// the session, but not not make it proceed to the finalized state. In normal -// usage the client will outlive all sessions, but in tests getting the teardown -// correct and race-free can be tricky so we permit either order. -// -// The wrapper will exist with `m_abandoned = true` and `m_finalized = false` -// only while waiting for finalization to happen. It will exist with -// `m_finalized = true` only while there are pending post handlers yet to be -// executed. -class SessionWrapper final : public util::AtomicRefCountBase, DB::CommitListener { -public: - SessionWrapper(ClientImpl&, DBRef db, std::shared_ptr, std::shared_ptr, - Session::Config&&); - ~SessionWrapper() noexcept; - - ClientReplication& get_replication() noexcept; - ClientImpl& get_client() noexcept; - - bool has_flx_subscription_store() const; - SubscriptionStore* get_flx_subscription_store(); - PendingBootstrapStore* get_flx_pending_bootstrap_store(); - - MigrationStore* get_migration_store(); - - // Immediately initiate deactivation of the wrapped session. Sets m_closed - // but *not* m_finalized. - // Must be called from event loop thread. - void force_close(); - - // Can be called from any thread. - void on_commit(version_type new_version) override; - // Can be called from any thread. - void cancel_reconnect_delay(); - - // Can be called from any thread. - void async_wait_for(bool upload_completion, bool download_completion, WaitOperCompletionHandler); - // Can be called from any thread. - bool wait_for_upload_complete_or_client_stopped(); - // Can be called from any thread. - bool wait_for_download_complete_or_client_stopped(); - - // Can be called from any thread. - void refresh(std::string_view signed_access_token); - - // Can be called from any thread. - static void abandon(util::bind_ptr) noexcept; - - // These are called from ClientImpl - // Must be called from event loop thread. - void actualize(); - void finalize(); - void finalize_before_actualization() noexcept; - - // Can be called from any thread. - util::Future send_test_command(std::string body); - - void handle_pending_client_reset_acknowledgement(); - - // Can be called from any thread. - std::string get_appservices_connection_id(); - - // Can be called from any thread, but inherently cannot be called - // concurrently with calls to any of the other non-confined functions. - bool mark_abandoned(); - -private: - ClientImpl& m_client; - DBRef m_db; - Replication* m_replication; - - const ProtocolEnvelope m_protocol_envelope; - const std::string m_server_address; - const port_type m_server_port; - const bool m_server_verified; - const std::string m_user_id; - const SyncServerMode m_sync_mode; - const std::string m_authorization_header_name; - const std::map m_custom_http_headers; - const bool m_verify_servers_ssl_certificate; - const bool m_simulate_integration_error; - const std::optional m_ssl_trust_certificate_path; - const std::function m_ssl_verify_callback; - const size_t m_flx_bootstrap_batch_size_bytes; - const std::string m_http_request_path_prefix; - const std::string m_virt_path; - const std::optional m_proxy_config; - - // This one is different from null when, and only when the session wrapper - // is in ClientImpl::m_abandoned_session_wrappers. - SessionWrapper* m_next = nullptr; - - // These may only be accessed by the event loop thread. - std::string m_signed_access_token; - std::optional m_client_reset_config; - - struct ReportedProgress { - uint64_t snapshot; - uint64_t uploaded; - uint64_t uploadable; - uint64_t downloaded; - uint64_t downloadable; - int64_t query_version = 0; - double download_estimate; - - // Does not check snapshot - bool operator==(const ReportedProgress& p) const noexcept - { - return uploaded == p.uploaded && uploadable == p.uploadable && downloaded == p.downloaded && - downloadable == p.downloadable && query_version == p.query_version && - download_estimate == p.download_estimate; - } - }; - std::optional m_reported_progress; - uint64_t m_final_uploaded = 0; - uint64_t m_final_downloaded = 0; - - const util::UniqueFunction m_progress_handler; - util::UniqueFunction m_connection_state_change_listener; - - const util::UniqueFunction m_debug_hook; - bool m_in_debug_hook = false; - - const SessionReason m_session_reason; - - // If false, QUERY and MARK messages are allowed but UPLOAD messages will not - // be sent to the server. - const bool m_allow_upload_messages; - - const uint64_t m_schema_version; - - std::shared_ptr m_flx_subscription_store; - std::unique_ptr m_flx_pending_bootstrap_store; - std::shared_ptr m_migration_store; - - // Set to true when this session wrapper is actualized (i.e. the wrapped - // session is created), or when the wrapper is finalized before actualization. - // It is then never modified again. - // - // Actualization is scheduled during the construction of SessionWrapper, and - // so a session specific post handler will always find that `m_actualized` - // is true as the handler will always be run after the actualization job. - // This holds even if the wrapper is finalized or closed before actualization. - bool m_actualized = false; - - // Set to true when session deactivation is begun, either via force_close() - // or finalize(). - bool m_closed = false; - - // Set to true in on_suspended() and then false in on_resumed(). Used to - // suppress spurious connection state and error reporting while the session - // is already in an error state. - bool m_suspended = false; - - // Set when the session has been abandoned. After this point none of the - // public API functions should be called again. - bool m_abandoned = false; - // Has the SessionWrapper been finalized? - bool m_finalized = false; - - // Set to true when the first DOWNLOAD message is received to indicate that - // the byte-level download progress parameters can be considered reasonable - // reliable. Before that, a lot of time may have passed, so our record of - // the download progress is likely completely out of date. - bool m_reliable_download_progress = false; - - // Set to point to an activated session object during actualization of the - // session wrapper. Set to null during finalization of the session - // wrapper. Both modifications are guaranteed to be performed by the event - // loop thread. - // - // If a session specific post handler, that is submitted after the - // initiation of the session wrapper, sees that `m_sess` is null, it can - // conclude that the session wrapper has either been force closed or has - // been both abandoned and finalized. - // - // Must only be accessed from the event loop thread. - SessionImpl* m_sess = nullptr; - - // These must only be accessed from the event loop thread. - std::vector m_upload_completion_handlers; - std::vector m_download_completion_handlers; - std::vector m_sync_completion_handlers; - - version_type m_upload_completion_requested_version = -1; - - void on_download_completion(); - void on_suspended(const SessionErrorInfo& error_info); - void on_resumed(); - void on_connection_state_changed(ConnectionState, const std::optional&); - void on_flx_sync_progress(int64_t new_version, DownloadBatchState batch_state); - - void init_progress_handler(); - void check_progress(); - void report_progress(ReportedProgress& p, DownloadableProgress downloadable); - void report_upload_completion(version_type); - - friend class SessionWrapperStack; - friend class ClientImpl::Session; -}; - - -// ################ SessionWrapperStack ################ - -inline bool SessionWrapperStack::empty() const noexcept -{ - return !m_back; -} - - -inline void SessionWrapperStack::push(util::bind_ptr w) noexcept -{ - REALM_ASSERT(!w->m_next); - w->m_next = m_back; - m_back = w.release(); -} - - -inline util::bind_ptr SessionWrapperStack::pop() noexcept -{ - util::bind_ptr w{m_back, util::bind_ptr_base::adopt_tag{}}; - if (m_back) { - m_back = m_back->m_next; - w->m_next = nullptr; - } - return w; -} - - -inline void SessionWrapperStack::clear() noexcept -{ - while (m_back) { - util::bind_ptr w{m_back, util::bind_ptr_base::adopt_tag{}}; - m_back = w->m_next; - } -} - - -inline bool SessionWrapperStack::erase(SessionWrapper* w) noexcept -{ - SessionWrapper** p = &m_back; - while (*p && *p != w) { - p = &(*p)->m_next; - } - if (!*p) { - return false; - } - *p = w->m_next; - util::bind_ptr{w, util::bind_ptr_base::adopt_tag{}}; - return true; -} - - -SessionWrapperStack::~SessionWrapperStack() -{ - clear(); -} - - -// ################ ClientImpl ################ - -ClientImpl::~ClientImpl() -{ - // Since no other thread is allowed to be accessing this client or any of - // its subobjects at this time, no mutex locking is necessary. - - shutdown_and_wait(); - // Session wrappers are removed from m_unactualized_session_wrappers as they - // are abandoned. - REALM_ASSERT(m_stopped); - REALM_ASSERT(m_unactualized_session_wrappers.empty()); - REALM_ASSERT(m_abandoned_session_wrappers.empty()); -} - - -void ClientImpl::cancel_reconnect_delay() -{ - // Thread safety required - post([this] { - for (auto& p : m_server_slots) { - ServerSlot& slot = p.second; - if (m_one_connection_per_session) { - REALM_ASSERT(!slot.connection); - for (const auto& p : slot.alt_connections) { - ClientImpl::Connection& conn = *p.second; - conn.resume_active_sessions(); // Throws - conn.cancel_reconnect_delay(); // Throws - } - } - else { - REALM_ASSERT(slot.alt_connections.empty()); - if (slot.connection) { - ClientImpl::Connection& conn = *slot.connection; - conn.resume_active_sessions(); // Throws - conn.cancel_reconnect_delay(); // Throws - } - else { - slot.reconnect_info.reset(); - } - } - } - }); // Throws -} - - -void ClientImpl::voluntary_disconnect_all_connections() -{ - auto done_pf = util::make_promise_future(); - post([this, promise = std::move(done_pf.promise)]() mutable { - try { - for (auto& p : m_server_slots) { - ServerSlot& slot = p.second; - if (m_one_connection_per_session) { - REALM_ASSERT(!slot.connection); - for (const auto& [_, conn] : slot.alt_connections) { - conn->voluntary_disconnect(); - } - } - else { - REALM_ASSERT(slot.alt_connections.empty()); - if (slot.connection) { - slot.connection->voluntary_disconnect(); - } - } - } - } - catch (...) { - promise.set_error(exception_to_status()); - return; - } - promise.emplace_value(); - }); - done_pf.future.get(); -} - - -bool ClientImpl::wait_for_session_terminations_or_client_stopped() -{ - // Thread safety required - - { - util::CheckedLockGuard lock{m_mutex}; - m_sessions_terminated = false; - } - - // The technique employed here relies on the fact that - // actualize_and_finalize_session_wrappers() must get to execute at least - // once before the post handler submitted below gets to execute, but still - // at a time where all session wrappers, that are abandoned prior to the - // execution of wait_for_session_terminations_or_client_stopped(), have been - // added to `m_abandoned_session_wrappers`. - // - // To see that this is the case, consider a session wrapper that was - // abandoned before wait_for_session_terminations_or_client_stopped() was - // invoked. Then the session wrapper will have been added to - // `m_abandoned_session_wrappers`, and an invocation of - // actualize_and_finalize_session_wrappers() will have been scheduled. The - // guarantees mentioned in the documentation of Trigger then ensure - // that at least one execution of actualize_and_finalize_session_wrappers() - // will happen after the session wrapper has been added to - // `m_abandoned_session_wrappers`, but before the post handler submitted - // below gets to execute. - post([this] { - { - util::CheckedLockGuard lock{m_mutex}; - m_sessions_terminated = true; - } - m_wait_or_client_stopped_cond.notify_all(); - }); // Throws - - bool completion_condition_was_satisfied; - { - util::CheckedUniqueLock lock{m_mutex}; - m_wait_or_client_stopped_cond.wait(lock.native_handle(), [&]() REQUIRES(m_mutex) { - return m_sessions_terminated || m_stopped; - }); - completion_condition_was_satisfied = !m_stopped; - } - return completion_condition_was_satisfied; -} - - -// This relies on the same assumptions and guarantees as wait_for_session_terminations_or_client_stopped(). -util::Future ClientImpl::notify_session_terminated() -{ - auto pf = util::make_promise_future(); - post([promise = std::move(pf.promise)](Status status) mutable { - // Includes operation_aborted - if (!status.is_ok()) { - promise.set_error(status); - return; - } - - promise.emplace_value(); - }); - - return std::move(pf.future); -} - -void ClientImpl::drain_connections_on_loop() -{ - post([this](Status status) { - REALM_ASSERT(status.is_ok()); - drain_connections(); - }); -} - -void ClientImpl::shutdown_and_wait() -{ - shutdown(); - util::CheckedUniqueLock lock{m_drain_mutex}; - if (m_drained) { - return; - } - - logger.debug("Waiting for %1 connections to drain", m_num_connections); - m_drain_cv.wait(lock.native_handle(), [&]() REQUIRES(m_drain_mutex) { - return m_num_connections == 0 && m_outstanding_posts == 0; - }); - - m_drained = true; -} - -void ClientImpl::shutdown() noexcept -{ - { - util::CheckedLockGuard lock{m_mutex}; - if (m_stopped) - return; - m_stopped = true; - } - m_wait_or_client_stopped_cond.notify_all(); - - drain_connections_on_loop(); -} - - -void ClientImpl::register_unactualized_session_wrapper(SessionWrapper* wrapper) -{ - // Thread safety required. - { - util::CheckedLockGuard lock{m_mutex}; - // We can't actualize the session if we've already been stopped, so - // just finalize it immediately. - if (m_stopped) { - wrapper->finalize_before_actualization(); - return; - } - - REALM_ASSERT(m_actualize_and_finalize); - m_unactualized_session_wrappers.push(util::bind_ptr(wrapper)); - } - m_actualize_and_finalize->trigger(); -} - - -void ClientImpl::register_abandoned_session_wrapper(util::bind_ptr wrapper) noexcept -{ - // Thread safety required. - { - util::CheckedLockGuard lock{m_mutex}; - REALM_ASSERT(m_actualize_and_finalize); - // The wrapper may have already been finalized before being abandoned - // if we were stopped when it was created. - if (wrapper->mark_abandoned()) - return; - - // If the session wrapper has not yet been actualized (on the event loop - // thread), it can be immediately finalized. This ensures that we will - // generally not actualize a session wrapper that has already been - // abandoned. - if (m_unactualized_session_wrappers.erase(wrapper.get())) { - wrapper->finalize_before_actualization(); - return; - } - m_abandoned_session_wrappers.push(std::move(wrapper)); - } - m_actualize_and_finalize->trigger(); -} - - -// Must be called from the event loop thread. -void ClientImpl::actualize_and_finalize_session_wrappers() -{ - // We need to pop from the wrapper stacks while holding the lock to ensure - // that all updates to `SessionWrapper:m_next` are thread-safe, but then - // release the lock before finalizing or actualizing because those functions - // invoke user callbacks which may try to access the client and reacquire - // the lock. - // - // Finalization must always happen before actualization because we may be - // finalizing and actualizing sessions for the same Realm file, and - // actualizing first would result in overlapping sessions. Because we're - // releasing the lock new sessions may come in as we're looping, so we need - // a single loop that checks both fields. - while (true) { - bool finalize = true; - bool stopped; - util::bind_ptr wrapper; - { - util::CheckedLockGuard lock{m_mutex}; - wrapper = m_abandoned_session_wrappers.pop(); - if (!wrapper) { - wrapper = m_unactualized_session_wrappers.pop(); - finalize = false; - } - stopped = m_stopped; - } - if (!wrapper) - break; - if (finalize) - wrapper->finalize(); // Throws - else if (stopped) - wrapper->finalize_before_actualization(); - else - wrapper->actualize(); // Throws - } -} - - -ClientImpl::Connection& ClientImpl::get_connection(ServerEndpoint endpoint, - const std::string& authorization_header_name, - const std::map& custom_http_headers, - bool verify_servers_ssl_certificate, - Optional ssl_trust_certificate_path, - std::function ssl_verify_callback, - Optional proxy_config, bool& was_created) -{ - auto&& [server_slot_it, inserted] = - m_server_slots.try_emplace(endpoint, ReconnectInfo(m_reconnect_mode, m_reconnect_backoff_info, get_random())); - ServerSlot& server_slot = server_slot_it->second; // Throws - - // TODO: enable multiplexing with proxies - if (server_slot.connection && !m_one_connection_per_session && !proxy_config) { - // Use preexisting connection - REALM_ASSERT(server_slot.alt_connections.empty()); - return *server_slot.connection; - } - - // Create a new connection - REALM_ASSERT(!server_slot.connection); - connection_ident_type ident = m_prev_connection_ident + 1; - std::unique_ptr conn_2 = std::make_unique( - *this, ident, std::move(endpoint), authorization_header_name, custom_http_headers, - verify_servers_ssl_certificate, std::move(ssl_trust_certificate_path), std::move(ssl_verify_callback), - std::move(proxy_config), server_slot.reconnect_info); // Throws - ClientImpl::Connection& conn = *conn_2; - if (!m_one_connection_per_session) { - server_slot.connection = std::move(conn_2); - } - else { - server_slot.alt_connections[ident] = std::move(conn_2); // Throws - } - m_prev_connection_ident = ident; - was_created = true; - { - util::CheckedLockGuard lk(m_drain_mutex); - ++m_num_connections; - } - return conn; -} - - -void ClientImpl::remove_connection(ClientImpl::Connection& conn) noexcept -{ - const ServerEndpoint& endpoint = conn.get_server_endpoint(); - auto i = m_server_slots.find(endpoint); - REALM_ASSERT(i != m_server_slots.end()); // Must be found - ServerSlot& server_slot = i->second; - if (!m_one_connection_per_session) { - REALM_ASSERT(server_slot.alt_connections.empty()); - REALM_ASSERT(&*server_slot.connection == &conn); - server_slot.reconnect_info = conn.get_reconnect_info(); - server_slot.connection.reset(); - } - else { - REALM_ASSERT(!server_slot.connection); - connection_ident_type ident = conn.get_ident(); - auto j = server_slot.alt_connections.find(ident); - REALM_ASSERT(j != server_slot.alt_connections.end()); // Must be found - REALM_ASSERT(&*j->second == &conn); - server_slot.alt_connections.erase(j); - } - - bool notify; - { - util::CheckedLockGuard lk(m_drain_mutex); - REALM_ASSERT(m_num_connections); - notify = --m_num_connections <= 0; - } - if (notify) { - m_drain_cv.notify_all(); - } -} - - -// ################ SessionImpl ################ - -void SessionImpl::force_close() -{ - // Allow force_close() if session is active or hasn't been activated yet. - if (m_state == SessionImpl::Active || m_state == SessionImpl::Unactivated) { - m_wrapper.force_close(); - } -} - -void SessionImpl::on_connection_state_changed(ConnectionState state, - const std::optional& error_info) -{ - // Only used to report errors back to the SyncSession while the Session is active - if (m_state == SessionImpl::Active) { - m_wrapper.on_connection_state_changed(state, error_info); // Throws - } -} - - -const std::string& SessionImpl::get_virt_path() const noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.m_virt_path; -} - -const std::string& SessionImpl::get_realm_path() const noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.m_db->get_path(); -} - -DBRef SessionImpl::get_db() const noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.m_db; -} - -ClientReplication& SessionImpl::get_repl() const noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.get_replication(); -} - -ClientHistory& SessionImpl::get_history() const noexcept -{ - return get_repl().get_history(); -} - -std::optional& SessionImpl::get_client_reset_config() noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.m_client_reset_config; -} - -SessionReason SessionImpl::get_session_reason() noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.m_session_reason; -} - -uint64_t SessionImpl::get_schema_version() noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.m_schema_version; -} - -bool SessionImpl::upload_messages_allowed() noexcept -{ - // Can only be called if the session is active or being activated - REALM_ASSERT_EX(m_state == State::Active || m_state == State::Unactivated, m_state); - return m_wrapper.m_allow_upload_messages; -} - -void SessionImpl::initiate_integrate_changesets(std::uint_fast64_t downloadable_bytes, DownloadBatchState batch_state, - const SyncProgress& progress, const ReceivedChangesets& changesets) -{ - // Ignore the call if the session is not active - if (m_state != State::Active) { - return; - } - - try { - bool simulate_integration_error = (m_wrapper.m_simulate_integration_error && !changesets.empty()); - if (simulate_integration_error) { - throw IntegrationException(ErrorCodes::BadChangeset, "simulated failure", ProtocolError::bad_changeset); - } - version_type client_version; - if (REALM_LIKELY(!get_client().is_dry_run())) { - VersionInfo version_info; - integrate_changesets(progress, downloadable_bytes, changesets, version_info, batch_state); // Throws - client_version = version_info.realm_version; - } - else { - // Fake it for "dry run" mode - client_version = m_last_version_available + 1; - } - on_changesets_integrated(client_version, progress); // Throws - } - catch (const IntegrationException& e) { - on_integration_failure(e); - } -} - - -void SessionImpl::on_download_completion() -{ - // Ignore the call if the session is not active - if (m_state == State::Active) { - m_wrapper.on_download_completion(); // Throws - } -} - - -void SessionImpl::on_suspended(const SessionErrorInfo& error_info) -{ - // Ignore the call if the session is not active - if (m_state == State::Active) { - m_wrapper.on_suspended(error_info); // Throws - } -} - - -void SessionImpl::on_resumed() -{ - // Ignore the call if the session is not active - if (m_state == State::Active) { - m_wrapper.on_resumed(); // Throws - } -} - -void SessionImpl::handle_pending_client_reset_acknowledgement() -{ - // Ignore the call if the session is not active - if (m_state == State::Active) { - m_wrapper.handle_pending_client_reset_acknowledgement(); - } -} - -bool SessionImpl::process_flx_bootstrap_message(const DownloadMessage& message) -{ - // Ignore the message if the session is not active or a steady state message - if (m_state != State::Active || message.batch_state == DownloadBatchState::SteadyState) { - return false; - } - - REALM_ASSERT(m_is_flx_sync_session); - - auto bootstrap_store = m_wrapper.get_flx_pending_bootstrap_store(); - std::optional maybe_progress; - if (message.batch_state == DownloadBatchState::LastInBatch) { - maybe_progress = message.progress; - } - - try { - bootstrap_store->add_batch(*message.query_version, maybe_progress, message.downloadable, message.changesets); - } - catch (const LogicError& ex) { - if (ex.code() == ErrorCodes::LimitExceeded) { - IntegrationException ex(ErrorCodes::LimitExceeded, - "bootstrap changeset too large to store in pending bootstrap store", - ProtocolError::bad_changeset_size); - on_integration_failure(ex); - return true; - } - throw; - } - - auto hook_action = call_debug_hook(SyncClientHookEvent::BootstrapMessageProcessed, message.progress, - *message.query_version, message.batch_state, message.changesets.size()); - if (hook_action == SyncClientHookAction::EarlyReturn) { - return true; - } - REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); - - if (message.batch_state == DownloadBatchState::MoreToCome) { - return true; - } - - try { - process_pending_flx_bootstrap(); // throws - } - catch (const IntegrationException& e) { - on_integration_failure(e); - } - catch (...) { - on_integration_failure(IntegrationException(exception_to_status())); - } - - return true; -} - - -void SessionImpl::process_pending_flx_bootstrap() -{ - // Ignore the call if not a flx session or session is not active - if (!m_is_flx_sync_session || m_state != State::Active) { - return; - } - auto bootstrap_store = m_wrapper.get_flx_pending_bootstrap_store(); - if (!bootstrap_store->has_pending()) { - return; - } - - auto pending_batch_stats = bootstrap_store->pending_stats(); - logger.info("Begin processing pending FLX bootstrap for query version %1. (changesets: %2, original total " - "changeset size: %3)", - pending_batch_stats.query_version, pending_batch_stats.pending_changesets, - pending_batch_stats.pending_changeset_bytes); - auto& history = get_repl().get_history(); - VersionInfo new_version; - SyncProgress progress; - int64_t query_version = -1; - size_t changesets_processed = 0; - - // Used to commit each batch after it was transformed. - TransactionRef transact = get_db()->start_write(); - while (bootstrap_store->has_pending()) { - auto start_time = std::chrono::steady_clock::now(); - auto pending_batch = bootstrap_store->peek_pending(*transact, m_wrapper.m_flx_bootstrap_batch_size_bytes); - if (!pending_batch.progress) { - logger.info("Incomplete pending bootstrap found for query version %1", pending_batch.query_version); - bootstrap_store->clear(*transact, pending_batch.query_version); - transact->commit(); - return; - } - - auto batch_state = - pending_batch.remaining_changesets > 0 ? DownloadBatchState::MoreToCome : DownloadBatchState::LastInBatch; - query_version = pending_batch.query_version; - bool simulate_integration_error = - (m_wrapper.m_simulate_integration_error && !pending_batch.changesets.empty()); - if (simulate_integration_error) { - throw IntegrationException(ErrorCodes::BadChangeset, "simulated failure", ProtocolError::bad_changeset); - } - - call_debug_hook(SyncClientHookEvent::BootstrapBatchAboutToProcess, *pending_batch.progress, query_version, - batch_state, pending_batch.changesets.size()); - - history.integrate_server_changesets( - *pending_batch.progress, 1.0, pending_batch.changesets, new_version, batch_state, logger, transact, - [&](const Transaction& tr, util::Span changesets_applied) { - REALM_ASSERT_3(changesets_applied.size(), <=, pending_batch.changesets.size()); - bootstrap_store->pop_front_pending(tr, changesets_applied.size()); - }); - progress = *pending_batch.progress; - changesets_processed += pending_batch.changesets.size(); - auto duration = std::chrono::steady_clock::now() - start_time; - - auto action = call_debug_hook(SyncClientHookEvent::DownloadMessageIntegrated, progress, query_version, - batch_state, pending_batch.changesets.size()); - REALM_ASSERT_EX(action == SyncClientHookAction::NoAction, action); - - logger.info("Integrated %1 changesets from pending bootstrap for query version %2, producing client version " - "%3 in %4 ms. %5 changesets remaining in bootstrap", - pending_batch.changesets.size(), pending_batch.query_version, new_version.realm_version, - std::chrono::duration_cast(duration).count(), - pending_batch.remaining_changesets); - } - - REALM_ASSERT_3(query_version, !=, -1); - - on_changesets_integrated(new_version.realm_version, progress); - auto action = call_debug_hook(SyncClientHookEvent::BootstrapProcessed, progress, query_version, - DownloadBatchState::LastInBatch, changesets_processed); - // NoAction/EarlyReturn are both valid no-op actions to take here. - REALM_ASSERT_EX(action == SyncClientHookAction::NoAction || action == SyncClientHookAction::EarlyReturn, action); -} - -void SessionImpl::on_flx_sync_error(int64_t version, std::string_view err_msg) -{ - // Ignore the call if the session is not active - if (m_state == State::Active) { - get_flx_subscription_store()->set_error(version, err_msg); - } -} - -SubscriptionStore* SessionImpl::get_flx_subscription_store() -{ - // Should never be called if session is not active - REALM_ASSERT_EX(m_state == State::Active, m_state); - return m_wrapper.get_flx_subscription_store(); -} - -MigrationStore* SessionImpl::get_migration_store() -{ - // Should never be called if session is not active - REALM_ASSERT_EX(m_state == State::Active, m_state); - return m_wrapper.get_migration_store(); -} - -SyncClientHookAction SessionImpl::call_debug_hook(const SyncClientHookData& data) -{ - // Should never be called if session is not active - REALM_ASSERT_EX(m_state == State::Active, m_state); - - // Make sure we don't call the debug hook recursively. - if (m_wrapper.m_in_debug_hook) { - return SyncClientHookAction::NoAction; - } - m_wrapper.m_in_debug_hook = true; - auto in_hook_guard = util::make_scope_exit([&]() noexcept { - m_wrapper.m_in_debug_hook = false; - }); - - auto action = m_wrapper.m_debug_hook(data); - switch (action) { - case realm::SyncClientHookAction::SuspendWithRetryableError: { - SessionErrorInfo err_info(Status{ErrorCodes::RuntimeError, "hook requested error"}, IsFatal{false}); - err_info.server_requests_action = ProtocolErrorInfo::Action::Transient; - - auto err_processing_err = receive_error_message(err_info); - REALM_ASSERT_EX(err_processing_err.is_ok(), err_processing_err); - return SyncClientHookAction::EarlyReturn; - } - case realm::SyncClientHookAction::TriggerReconnect: { - get_connection().voluntary_disconnect(); - return SyncClientHookAction::EarlyReturn; - } - default: - return action; - } -} - -SyncClientHookAction SessionImpl::call_debug_hook(SyncClientHookEvent event, const SyncProgress& progress, - int64_t query_version, DownloadBatchState batch_state, - size_t num_changesets) -{ - if (REALM_LIKELY(!m_wrapper.m_debug_hook)) { - return SyncClientHookAction::NoAction; - } - if (REALM_UNLIKELY(m_state != State::Active)) { - return SyncClientHookAction::NoAction; - } - - SyncClientHookData data; - data.event = event; - data.batch_state = batch_state; - data.progress = progress; - data.num_changesets = num_changesets; - data.query_version = query_version; - - return call_debug_hook(data); -} - -SyncClientHookAction SessionImpl::call_debug_hook(SyncClientHookEvent event, const ProtocolErrorInfo* error_info) -{ - if (REALM_LIKELY(!m_wrapper.m_debug_hook)) { - return SyncClientHookAction::NoAction; - } - if (REALM_UNLIKELY(m_state != State::Active)) { - return SyncClientHookAction::NoAction; - } - - SyncClientHookData data; - data.event = event; - data.batch_state = DownloadBatchState::SteadyState; - data.progress = m_progress; - data.num_changesets = 0; - data.query_version = m_last_sent_flx_query_version; - data.error_info = error_info; - - return call_debug_hook(data); -} - -void SessionImpl::init_progress_handler() -{ - REALM_ASSERT_EX(m_state == State::Unactivated || m_state == State::Active, m_state); - m_wrapper.init_progress_handler(); -} - -void SessionImpl::enable_progress_notifications() -{ - m_wrapper.m_reliable_download_progress = true; -} - -util::Future SessionImpl::send_test_command(std::string body) -{ - if (m_state != State::Active) { - return Status{ErrorCodes::RuntimeError, "Cannot send a test command for a session that is not active"}; - } - - try { - auto json_body = nlohmann::json::parse(body.begin(), body.end()); - if (auto it = json_body.find("command"); it == json_body.end() || !it->is_string()) { - return Status{ErrorCodes::LogicError, - "Must supply command name in \"command\" field of test command json object"}; - } - if (json_body.size() > 1 && json_body.find("args") == json_body.end()) { - return Status{ErrorCodes::LogicError, "Only valid fields in a test command are \"command\" and \"args\""}; - } - } - catch (const nlohmann::json::parse_error& e) { - return Status{ErrorCodes::LogicError, util::format("Invalid json input to send_test_command: %1", e.what())}; - } - - auto pf = util::make_promise_future(); - get_client().post([this, promise = std::move(pf.promise), body = std::move(body)](Status status) mutable { - // Includes operation_aborted - if (!status.is_ok()) { - promise.set_error(status); - return; - } - - auto id = ++m_last_pending_test_command_ident; - m_pending_test_commands.push_back(PendingTestCommand{id, std::move(body), std::move(promise)}); - ensure_enlisted_to_send(); - }); - - return std::move(pf.future); -} - -// ################ SessionWrapper ################ - -// The SessionWrapper class is held by a sync::Session (which is owned by the SyncSession instance) and -// provides a link to the ClientImpl::Session that creates and receives messages with the server with -// the ClientImpl::Connection that owns the ClientImpl::Session. -SessionWrapper::SessionWrapper(ClientImpl& client, DBRef db, std::shared_ptr flx_sub_store, - std::shared_ptr migration_store, Session::Config&& config) - : m_client{client} - , m_db(std::move(db)) - , m_replication(m_db->get_replication()) - , m_protocol_envelope{config.protocol_envelope} - , m_server_address{std::move(config.server_address)} - , m_server_port{config.server_port} - , m_server_verified{config.server_verified} - , m_user_id(std::move(config.user_id)) - , m_sync_mode(flx_sub_store ? SyncServerMode::FLX : SyncServerMode::PBS) - , m_authorization_header_name{config.authorization_header_name} - , m_custom_http_headers{std::move(config.custom_http_headers)} - , m_verify_servers_ssl_certificate{config.verify_servers_ssl_certificate} - , m_simulate_integration_error{config.simulate_integration_error} - , m_ssl_trust_certificate_path{std::move(config.ssl_trust_certificate_path)} - , m_ssl_verify_callback{std::move(config.ssl_verify_callback)} - , m_flx_bootstrap_batch_size_bytes(config.flx_bootstrap_batch_size_bytes) - , m_http_request_path_prefix{std::move(config.service_identifier)} - , m_virt_path{std::move(config.realm_identifier)} - , m_proxy_config{std::move(config.proxy_config)} - , m_signed_access_token{std::move(config.signed_user_token)} - , m_client_reset_config{std::move(config.client_reset_config)} - , m_progress_handler(std::move(config.progress_handler)) - , m_connection_state_change_listener(std::move(config.connection_state_change_listener)) - , m_debug_hook(std::move(config.on_sync_client_event_hook)) - , m_session_reason(m_client_reset_config || config.fresh_realm_download ? SessionReason::ClientReset - : SessionReason::Sync) - , m_allow_upload_messages(!config.fresh_realm_download) - , m_schema_version(config.schema_version) - , m_flx_subscription_store(std::move(flx_sub_store)) - , m_migration_store(std::move(migration_store)) -{ - REALM_ASSERT(m_db); - REALM_ASSERT(m_db->get_replication()); - REALM_ASSERT(dynamic_cast(m_db->get_replication())); - - // SessionWrapper begins at +1 retain count because Client retains and - // releases it while performing async operations, and these need to not - // take it to 0 or it could be deleted before the caller can retain it. - bind_ptr(); - m_client.register_unactualized_session_wrapper(this); -} - -SessionWrapper::~SessionWrapper() noexcept -{ - // We begin actualization in the constructor and do not delete the wrapper - // until both the Client is done with it and the Session has abandoned it, - // so at this point we must have actualized, finalized, and been abandoned. - REALM_ASSERT(m_actualized); - REALM_ASSERT(m_abandoned); - REALM_ASSERT(m_finalized); - REALM_ASSERT(m_closed); - REALM_ASSERT(!m_db); -} - - -inline ClientReplication& SessionWrapper::get_replication() noexcept -{ - REALM_ASSERT(m_db); - return static_cast(*m_replication); -} - - -inline ClientImpl& SessionWrapper::get_client() noexcept -{ - return m_client; -} - -bool SessionWrapper::has_flx_subscription_store() const -{ - return static_cast(m_flx_subscription_store); -} - -SubscriptionStore* SessionWrapper::get_flx_subscription_store() -{ - REALM_ASSERT(!m_finalized); - return m_flx_subscription_store.get(); -} - -PendingBootstrapStore* SessionWrapper::get_flx_pending_bootstrap_store() -{ - REALM_ASSERT(!m_finalized); - return m_flx_pending_bootstrap_store.get(); -} - -MigrationStore* SessionWrapper::get_migration_store() -{ - REALM_ASSERT(!m_finalized); - return m_migration_store.get(); -} - -inline bool SessionWrapper::mark_abandoned() -{ - REALM_ASSERT(!m_abandoned); - m_abandoned = true; - return m_finalized; -} - - -void SessionWrapper::on_commit(version_type new_version) -{ - // Thread safety required - m_client.post([self = util::bind_ptr{this}, new_version] { - REALM_ASSERT(self->m_actualized); - if (REALM_UNLIKELY(!self->m_sess)) - return; // Already finalized - SessionImpl& sess = *self->m_sess; - sess.recognize_sync_version(new_version); // Throws - self->check_progress(); // Throws - }); -} - - -void SessionWrapper::cancel_reconnect_delay() -{ - // Thread safety required - - m_client.post([self = util::bind_ptr{this}] { - REALM_ASSERT(self->m_actualized); - if (REALM_UNLIKELY(self->m_closed)) { - return; - } - - if (REALM_UNLIKELY(!self->m_sess)) - return; // Already finalized - SessionImpl& sess = *self->m_sess; - sess.cancel_resumption_delay(); // Throws - ClientImpl::Connection& conn = sess.get_connection(); - conn.cancel_reconnect_delay(); // Throws - }); // Throws -} - -void SessionWrapper::async_wait_for(bool upload_completion, bool download_completion, - WaitOperCompletionHandler handler) -{ - REALM_ASSERT(upload_completion || download_completion); - - m_client.post([self = util::bind_ptr{this}, handler = std::move(handler), upload_completion, - download_completion](Status status) mutable { - REALM_ASSERT(self->m_actualized); - if (!status.is_ok()) { - handler(status); // Throws - return; - } - if (REALM_UNLIKELY(!self->m_sess)) { - // Already finalized - handler({ErrorCodes::OperationAborted, "Session finalized before callback could run"}); // Throws - return; - } - if (upload_completion) { - self->m_upload_completion_requested_version = self->m_db->get_version_of_latest_snapshot(); - if (download_completion) { - // Wait for upload and download completion - self->m_sync_completion_handlers.push_back(std::move(handler)); // Throws - } - else { - // Wait for upload completion only - self->m_upload_completion_handlers.push_back(std::move(handler)); // Throws - } - } - else { - // Wait for download completion only - self->m_download_completion_handlers.push_back(std::move(handler)); // Throws - } - SessionImpl& sess = *self->m_sess; - if (upload_completion) - self->check_progress(); - if (download_completion) - sess.request_download_completion_notification(); // Throws - }); // Throws -} - - -bool SessionWrapper::wait_for_upload_complete_or_client_stopped() -{ - // Thread safety required - REALM_ASSERT(!m_abandoned); - - auto pf = util::make_promise_future(); - async_wait_for(true, false, [promise = std::move(pf.promise)](Status status) mutable { - promise.emplace_value(status.is_ok()); - }); - return pf.future.get(); -} - - -bool SessionWrapper::wait_for_download_complete_or_client_stopped() -{ - // Thread safety required - REALM_ASSERT(!m_abandoned); - - auto pf = util::make_promise_future(); - async_wait_for(false, true, [promise = std::move(pf.promise)](Status status) mutable { - promise.emplace_value(status.is_ok()); - }); - return pf.future.get(); -} - - -void SessionWrapper::refresh(std::string_view signed_access_token) -{ - // Thread safety required - REALM_ASSERT(!m_abandoned); - - m_client.post([self = util::bind_ptr{this}, token = std::string(signed_access_token)] { - REALM_ASSERT(self->m_actualized); - if (REALM_UNLIKELY(!self->m_sess)) - return; // Already finalized - self->m_signed_access_token = std::move(token); - SessionImpl& sess = *self->m_sess; - ClientImpl::Connection& conn = sess.get_connection(); - // FIXME: This only makes sense when each session uses a separate connection. - conn.update_connect_info(self->m_http_request_path_prefix, self->m_signed_access_token); // Throws - sess.cancel_resumption_delay(); // Throws - conn.cancel_reconnect_delay(); // Throws - }); -} - - -void SessionWrapper::abandon(util::bind_ptr wrapper) noexcept -{ - ClientImpl& client = wrapper->m_client; - client.register_abandoned_session_wrapper(std::move(wrapper)); -} - - -// Must be called from event loop thread -void SessionWrapper::actualize() -{ - // actualize() can only ever be called once - REALM_ASSERT(!m_actualized); - REALM_ASSERT(!m_sess); - // The client should have removed this wrapper from those pending - // actualization if it called force_close() or finalize_before_actualize() - REALM_ASSERT(!m_finalized); - REALM_ASSERT(!m_closed); - - m_actualized = true; - - ScopeExitFail close_on_error([&]() noexcept { - m_closed = true; - }); - - m_db->claim_sync_agent(); - m_db->add_commit_listener(this); - ScopeExitFail remove_commit_listener([&]() noexcept { - m_db->remove_commit_listener(this); - }); - - ServerEndpoint endpoint{m_protocol_envelope, m_server_address, m_server_port, - m_user_id, m_sync_mode, m_server_verified}; - bool was_created = false; - ClientImpl::Connection& conn = m_client.get_connection( - std::move(endpoint), m_authorization_header_name, m_custom_http_headers, m_verify_servers_ssl_certificate, - m_ssl_trust_certificate_path, m_ssl_verify_callback, m_proxy_config, - was_created); // Throws - ScopeExitFail remove_connection([&]() noexcept { - if (was_created) - m_client.remove_connection(conn); - }); - - // FIXME: This only makes sense when each session uses a separate connection. - conn.update_connect_info(m_http_request_path_prefix, m_signed_access_token); // Throws - std::unique_ptr sess = std::make_unique(*this, conn); // Throws - if (m_sync_mode == SyncServerMode::FLX) { - m_flx_pending_bootstrap_store = - std::make_unique(m_db, sess->logger, m_flx_subscription_store); - } - - sess->logger.info("Binding '%1' to '%2'", m_db->get_path(), m_virt_path); // Throws - m_sess = sess.get(); - ScopeExitFail clear_sess([&]() noexcept { - m_sess = nullptr; - }); - conn.activate_session(std::move(sess)); // Throws - - if (was_created) - conn.activate(); // Throws - - if (m_connection_state_change_listener) { - ConnectionState state = conn.get_state(); - if (state != ConnectionState::disconnected) { - m_connection_state_change_listener(ConnectionState::connecting, util::none); // Throws - if (state == ConnectionState::connected) - m_connection_state_change_listener(ConnectionState::connected, util::none); // Throws - } - } - - if (!m_client_reset_config) - check_progress(); // Throws -} - -void SessionWrapper::force_close() -{ - if (m_closed) { - return; - } - REALM_ASSERT(m_actualized); - REALM_ASSERT(m_sess); - m_closed = true; - - ClientImpl::Connection& conn = m_sess->get_connection(); - conn.initiate_session_deactivation(m_sess); // Throws - - // We need to keep the DB open until finalization, but we no longer want to - // know when commits are made - m_db->remove_commit_listener(this); - - // Delete the pending bootstrap store since it uses a reference to the logger in m_sess - m_flx_pending_bootstrap_store.reset(); - // Clear the subscription and migration store refs since they are owned by SyncSession - m_flx_subscription_store.reset(); - m_migration_store.reset(); - m_sess = nullptr; - // Everything is being torn down, no need to report connection state anymore - m_connection_state_change_listener = {}; - - // All outstanding wait operations must be canceled - while (!m_upload_completion_handlers.empty()) { - auto handler = std::move(m_upload_completion_handlers.back()); - m_upload_completion_handlers.pop_back(); - handler({ErrorCodes::OperationAborted, "Sync session is being closed before upload was complete"}); // Throws - } - while (!m_download_completion_handlers.empty()) { - auto handler = std::move(m_download_completion_handlers.back()); - m_download_completion_handlers.pop_back(); - handler( - {ErrorCodes::OperationAborted, "Sync session is being closed before download was complete"}); // Throws - } - while (!m_sync_completion_handlers.empty()) { - auto handler = std::move(m_sync_completion_handlers.back()); - m_sync_completion_handlers.pop_back(); - handler({ErrorCodes::OperationAborted, "Sync session is being closed before sync was complete"}); // Throws - } -} - -// Must be called from event loop thread -// -// `m_client.m_mutex` is not held while this is called, but it is guaranteed to -// have been acquired at some point in between the final read or write ever made -// from a different thread and when this is called. -void SessionWrapper::finalize() -{ - REALM_ASSERT(m_actualized); - REALM_ASSERT(m_abandoned); - REALM_ASSERT(!m_finalized); - - force_close(); - - m_finalized = true; - - // The Realm file can be closed now, as no access to the Realm file is - // supposed to happen on behalf of a session after initiation of - // deactivation. - m_db->release_sync_agent(); - m_db = nullptr; -} - - -// Must be called only when an unactualized session wrapper becomes abandoned. -// -// Called with a lock on `m_client.m_mutex`. -inline void SessionWrapper::finalize_before_actualization() noexcept -{ - REALM_ASSERT(!m_finalized); - REALM_ASSERT(!m_sess); - m_actualized = true; - m_finalized = true; - m_closed = true; - m_db->remove_commit_listener(this); - m_db->release_sync_agent(); - m_db = nullptr; -} - -void SessionWrapper::on_download_completion() -{ - // Ensure that progress handlers get called before completion handlers. The - // download completing performed a commit and will trigger progress - // notifications asynchronously, but they would arrive after the download - // completion without this. - check_progress(); - - if (m_flx_subscription_store) { - m_flx_subscription_store->download_complete(); - } - - while (!m_download_completion_handlers.empty()) { - auto handler = std::move(m_download_completion_handlers.back()); - m_download_completion_handlers.pop_back(); - handler(Status::OK()); // Throws - } - while (!m_sync_completion_handlers.empty()) { - auto handler = std::move(m_sync_completion_handlers.back()); - m_upload_completion_handlers.push_back(std::move(handler)); // Throws - m_sync_completion_handlers.pop_back(); - } -} - - -void SessionWrapper::on_suspended(const SessionErrorInfo& error_info) -{ - REALM_ASSERT(!m_finalized); - m_suspended = true; - if (m_connection_state_change_listener) { - m_connection_state_change_listener(ConnectionState::disconnected, error_info); // Throws - } -} - - -void SessionWrapper::on_resumed() -{ - REALM_ASSERT(!m_finalized); - m_suspended = false; - if (m_connection_state_change_listener) { - ClientImpl::Connection& conn = m_sess->get_connection(); - if (conn.get_state() != ConnectionState::disconnected) { - m_connection_state_change_listener(ConnectionState::connecting, util::none); // Throws - if (conn.get_state() == ConnectionState::connected) - m_connection_state_change_listener(ConnectionState::connected, util::none); // Throws - } - } -} - - -void SessionWrapper::on_connection_state_changed(ConnectionState state, - const std::optional& error_info) -{ - if (m_connection_state_change_listener && !m_suspended) { - m_connection_state_change_listener(state, error_info); // Throws - } -} - -void SessionWrapper::init_progress_handler() -{ - ClientHistory::get_upload_download_state(m_db.get(), m_final_downloaded, m_final_uploaded); -} - -void SessionWrapper::check_progress() -{ - REALM_ASSERT(!m_finalized); - REALM_ASSERT(m_sess); - - // Check if there's anything which even wants progress or completion information - bool has_progress_handler = m_progress_handler && m_reliable_download_progress; - bool has_completion_handler = !m_upload_completion_handlers.empty() || !m_sync_completion_handlers.empty(); - if (!m_flx_subscription_store && !has_progress_handler && !has_completion_handler) - return; - - // The order in which we report each type of completion or progress is important, - // and changing it needs to be avoided as it'd be a breaking change to the APIs - - TransactionRef tr; - ReportedProgress p; - if (m_flx_subscription_store) { - m_flx_subscription_store->report_progress(tr); - } - - if (!has_progress_handler && !has_completion_handler) - return; - // The subscription store may have started a read transaction that we'll - // reuse, but it may not have needed to or may not exist - if (!tr) - tr = m_db->start_read(); - - version_type uploaded_version; - DownloadableProgress downloadable; - ClientHistory::get_upload_download_state(*tr, m_db->get_alloc(), p.downloaded, downloadable, p.uploaded, - p.uploadable, p.snapshot, uploaded_version); - if (m_flx_subscription_store && has_progress_handler) - p.query_version = m_flx_subscription_store->get_downloading_query_version(*tr); - - report_progress(p, downloadable); - report_upload_completion(uploaded_version); -} - -void SessionWrapper::report_upload_completion(version_type uploaded_version) -{ - if (uploaded_version < m_upload_completion_requested_version) - return; - - std::move(m_sync_completion_handlers.begin(), m_sync_completion_handlers.end(), - std::back_inserter(m_download_completion_handlers)); - m_sync_completion_handlers.clear(); - - while (!m_upload_completion_handlers.empty()) { - auto handler = std::move(m_upload_completion_handlers.back()); - m_upload_completion_handlers.pop_back(); - handler(Status::OK()); // Throws - } -} - -void SessionWrapper::report_progress(ReportedProgress& p, DownloadableProgress downloadable) -{ - if (!m_progress_handler) - return; - - // Ignore progress messages from before we first receive a DOWNLOAD message - if (!m_reliable_download_progress) - return; - - auto calculate_progress = [](uint64_t transferred, uint64_t transferable, uint64_t final_transferred) { - REALM_ASSERT_DEBUG_EX(final_transferred <= transferred, final_transferred, transferred, transferable); - REALM_ASSERT_DEBUG_EX(transferred <= transferable, final_transferred, transferred, transferable); - - // The effect of this calculation is that if new bytes are added for download/upload, - // the progress estimate doesn't go back to zero, but it goes back to some non-zero percentage. - // This calculation allows a clean progression from 0 to 1.0 even if the new data is added for the sync - // before progress has reached 1.0. - // Then once it is at 1.0 the next batch of changes will restart the estimate at 0. - // Example for upload progress reported: - // 0 -> 1.0 -> new data added -> 0.0 -> 0.1 ...sync... -> 0.4 -> new data added -> 0.3 ...sync.. -> 1.0 - - double progress_estimate = 1.0; - if (final_transferred < transferable && transferred < transferable) - progress_estimate = (transferred - final_transferred) / double(transferable - final_transferred); - return progress_estimate; - }; - - bool upload_completed = p.uploaded == p.uploadable; - double upload_estimate = 1.0; - if (!upload_completed) - upload_estimate = calculate_progress(p.uploaded, p.uploadable, m_final_uploaded); - - bool download_completed = p.downloaded == 0; - p.download_estimate = 1.00; - if (m_flx_pending_bootstrap_store) { - p.download_estimate = downloadable.as_estimate(); - if (m_flx_pending_bootstrap_store->has_pending()) { - p.downloaded += m_flx_pending_bootstrap_store->pending_stats().pending_changeset_bytes; - } - download_completed = p.download_estimate >= 1.0; - - // for flx with download estimate these bytes are not known - // provide some sensible value for non-streaming version of object-store callbacks - // until these field are completely removed from the api after pbs deprecation - p.downloadable = p.downloaded; - if (p.download_estimate > 0 && p.download_estimate < 1.0 && p.downloaded > m_final_downloaded) - p.downloadable = m_final_downloaded + uint64_t((p.downloaded - m_final_downloaded) / p.download_estimate); - } - else { - // uploadable_bytes is uploaded + remaining to upload, while downloadable_bytes - // is only the remaining to download. This is confusing, so make them use - // the same units. - p.downloadable = downloadable.as_bytes() + p.downloaded; - if (!download_completed) - p.download_estimate = calculate_progress(p.downloaded, p.downloadable, m_final_downloaded); - } - - if (download_completed) - m_final_downloaded = p.downloaded; - if (upload_completed) - m_final_uploaded = p.uploaded; - - if (p == m_reported_progress) - return; - - m_reported_progress = p; - - if (m_sess->logger.would_log(Logger::Level::debug)) { - auto to_str = [](double d) { - std::ostringstream ss; - // progress estimate string in the DOWNLOAD message isn't expected to have more than 4 digits of precision - ss << std::fixed << std::setprecision(4) << d; - return ss.str(); - }; - m_sess->logger.debug( - "Progress handler called, downloaded = %1, downloadable = %2, estimate = %3, " - "uploaded = %4, uploadable = %5, estimate = %6, snapshot version = %7, query_version = %8", - p.downloaded, p.downloadable, to_str(p.download_estimate), p.uploaded, p.uploadable, - to_str(upload_estimate), p.snapshot, p.query_version); - } - - m_progress_handler(p.downloaded, p.downloadable, p.uploaded, p.uploadable, p.snapshot, p.download_estimate, - upload_estimate, p.query_version); -} - -util::Future SessionWrapper::send_test_command(std::string body) -{ - if (!m_sess) { - return Status{ErrorCodes::RuntimeError, "session must be activated to send a test command"}; - } - - return m_sess->send_test_command(std::move(body)); -} - -void SessionWrapper::handle_pending_client_reset_acknowledgement() -{ - REALM_ASSERT(!m_finalized); - - auto has_pending_reset = PendingResetStore::has_pending_reset(*m_db->start_frozen()); - if (!has_pending_reset) { - return; // nothing to do - } - - m_sess->logger.info(util::LogCategory::reset, "Tracking %1", *has_pending_reset); - - // Now that the client reset merge is complete, wait for the changes to synchronize with the server - async_wait_for( - true, true, [self = util::bind_ptr(this), pending_reset = std::move(*has_pending_reset)](Status status) { - if (status == ErrorCodes::OperationAborted) { - return; - } - auto& logger = self->m_sess->logger; - if (!status.is_ok()) { - logger.error(util::LogCategory::reset, "Error while tracking client reset acknowledgement: %1", - status); - return; - } - - logger.debug(util::LogCategory::reset, "Server has acknowledged %1", pending_reset); - - auto tr = self->m_db->start_write(); - auto cur_pending_reset = PendingResetStore::has_pending_reset(*tr); - if (!cur_pending_reset) { - logger.debug(util::LogCategory::reset, "Client reset cycle detection tracker already removed."); - return; - } - if (*cur_pending_reset == pending_reset) { - logger.debug(util::LogCategory::reset, "Removing client reset cycle detection tracker."); - } - else { - logger.info(util::LogCategory::reset, "Found new %1", cur_pending_reset); - } - PendingResetStore::clear_pending_reset(*tr); - tr->commit(); - }); -} - -std::string SessionWrapper::get_appservices_connection_id() -{ - auto pf = util::make_promise_future(); - - m_client.post([self = util::bind_ptr{this}, promise = std::move(pf.promise)](Status status) mutable { - if (!status.is_ok()) { - promise.set_error(status); - return; - } - - if (!self->m_sess) { - promise.set_error({ErrorCodes::RuntimeError, "session already finalized"}); - return; - } - - promise.emplace_value(self->m_sess->get_connection().get_active_appservices_connection_id()); - }); - - return pf.future.get(); -} - -// ################ ClientImpl::Connection ################ - -ClientImpl::Connection::Connection(ClientImpl& client, connection_ident_type ident, ServerEndpoint endpoint, - const std::string& authorization_header_name, - const std::map& custom_http_headers, - bool verify_servers_ssl_certificate, - Optional ssl_trust_certificate_path, - std::function ssl_verify_callback, - Optional proxy_config, ReconnectInfo reconnect_info) - : logger{make_logger(ident, std::nullopt, client.logger.base_logger)} // Throws - , m_client{client} - , m_verify_servers_ssl_certificate{verify_servers_ssl_certificate} // DEPRECATED - , m_ssl_trust_certificate_path{std::move(ssl_trust_certificate_path)} // DEPRECATED - , m_ssl_verify_callback{std::move(ssl_verify_callback)} // DEPRECATED - , m_proxy_config{std::move(proxy_config)} // DEPRECATED - , m_reconnect_info{reconnect_info} - , m_ident{ident} - , m_server_endpoint{std::move(endpoint)} - , m_authorization_header_name{authorization_header_name} // DEPRECATED - , m_custom_http_headers{custom_http_headers} // DEPRECATED -{ - m_on_idle = m_client.create_trigger([this](Status status) { - if (status == ErrorCodes::OperationAborted) - return; - else if (!status.is_ok()) - throw Exception(status); - - REALM_ASSERT(m_activated); - if (m_state == ConnectionState::disconnected && m_num_active_sessions == 0) { - on_idle(); // Throws - // Connection object may be destroyed now. - } - }); -} - -inline connection_ident_type ClientImpl::Connection::get_ident() const noexcept -{ - return m_ident; -} - - -inline const ServerEndpoint& ClientImpl::Connection::get_server_endpoint() const noexcept -{ - return m_server_endpoint; -} - -inline void ClientImpl::Connection::update_connect_info(const std::string& http_request_path_prefix, - const std::string& signed_access_token) -{ - m_http_request_path_prefix = http_request_path_prefix; // Throws (copy) - m_signed_access_token = signed_access_token; // Throws (copy) -} - - -void ClientImpl::Connection::resume_active_sessions() -{ - auto handler = [=](ClientImpl::Session& sess) { - sess.cancel_resumption_delay(); // Throws - }; - for_each_active_session(std::move(handler)); // Throws -} - -void ClientImpl::Connection::on_idle() -{ - logger.debug(util::LogCategory::session, "Destroying connection object"); - ClientImpl& client = get_client(); - client.remove_connection(*this); - // NOTE: This connection object is now destroyed! -} - - -std::string ClientImpl::Connection::get_http_request_path() const -{ - using namespace std::string_view_literals; - const auto param = m_http_request_path_prefix.find('?') == std::string::npos ? "?baas_at="sv : "&baas_at="sv; - - std::string path; - path.reserve(m_http_request_path_prefix.size() + param.size() + m_signed_access_token.size()); - path += m_http_request_path_prefix; - path += param; - path += m_signed_access_token; - - return path; -} - - -std::shared_ptr ClientImpl::Connection::make_logger(connection_ident_type ident, - std::optional coid, - std::shared_ptr base_logger) -{ - std::string prefix = - coid ? util::format("Connection[%1:%2] ", ident, *coid) : util::format("Connection[%1] ", ident); - return std::make_shared(util::LogCategory::session, std::move(prefix), base_logger); -} - - -void ClientImpl::Connection::report_connection_state_change(ConnectionState state, - std::optional error_info) -{ - if (m_force_closed) { - return; - } - auto handler = [=](ClientImpl::Session& sess) { - SessionImpl& sess_2 = static_cast(sess); - sess_2.on_connection_state_changed(state, error_info); // Throws - }; - for_each_active_session(std::move(handler)); // Throws -} - - -Client::Client(Config config) - : m_impl{new ClientImpl{std::move(config)}} // Throws -{ -} - - -Client::Client(Client&& client) noexcept - : m_impl{std::move(client.m_impl)} -{ -} - - -Client::~Client() noexcept {} - - -void Client::shutdown() noexcept -{ - m_impl->shutdown(); -} - -void Client::shutdown_and_wait() -{ - m_impl->shutdown_and_wait(); -} - -void Client::cancel_reconnect_delay() -{ - m_impl->cancel_reconnect_delay(); -} - -void Client::voluntary_disconnect_all_connections() -{ - m_impl->voluntary_disconnect_all_connections(); -} - -bool Client::wait_for_session_terminations_or_client_stopped() -{ - return m_impl->wait_for_session_terminations_or_client_stopped(); -} - -util::Future Client::notify_session_terminated() -{ - return m_impl->notify_session_terminated(); -} - -bool Client::decompose_server_url(const std::string& url, ProtocolEnvelope& protocol, std::string& address, - port_type& port, std::string& path) const -{ - return m_impl->decompose_server_url(url, protocol, address, port, path); // Throws -} - - -Session::Session(Client& client, DBRef db, std::shared_ptr flx_sub_store, - std::shared_ptr migration_store, Config&& config) -{ - m_impl = new SessionWrapper{*client.m_impl, std::move(db), std::move(flx_sub_store), std::move(migration_store), - std::move(config)}; // Throws -} - - -void Session::nonsync_transact_notify(version_type new_version) -{ - m_impl->on_commit(new_version); // Throws -} - - -void Session::cancel_reconnect_delay() -{ - m_impl->cancel_reconnect_delay(); // Throws -} - - -void Session::async_wait_for(bool upload_completion, bool download_completion, WaitOperCompletionHandler handler) -{ - m_impl->async_wait_for(upload_completion, download_completion, std::move(handler)); // Throws -} - - -bool Session::wait_for_upload_complete_or_client_stopped() -{ - return m_impl->wait_for_upload_complete_or_client_stopped(); // Throws -} - - -bool Session::wait_for_download_complete_or_client_stopped() -{ - return m_impl->wait_for_download_complete_or_client_stopped(); // Throws -} - - -void Session::refresh(std::string_view signed_access_token) -{ - m_impl->refresh(signed_access_token); // Throws -} - - -void Session::abandon() noexcept -{ - REALM_ASSERT(m_impl); - // Reabsorb the ownership assigned to the applications naked pointer by - // Session constructor - util::bind_ptr wrapper{m_impl, util::bind_ptr_base::adopt_tag{}}; - SessionWrapper::abandon(std::move(wrapper)); -} - -util::Future Session::send_test_command(std::string body) -{ - return m_impl->send_test_command(std::move(body)); -} - -std::string Session::get_appservices_connection_id() -{ - return m_impl->get_appservices_connection_id(); -} - -std::ostream& operator<<(std::ostream& os, ProxyConfig::Type proxyType) -{ - switch (proxyType) { - case ProxyConfig::Type::HTTP: - return os << "HTTP"; - case ProxyConfig::Type::HTTPS: - return os << "HTTPS"; - } - REALM_TERMINATE("Invalid Proxy Type object."); -} - -} // namespace realm::sync diff --git a/src/realm/sync/client.hpp b/src/realm/sync/client.hpp deleted file mode 100644 index fb4e3f0c8ee..00000000000 --- a/src/realm/sync/client.hpp +++ /dev/null @@ -1,693 +0,0 @@ -#ifndef REALM_SYNC_CLIENT_HPP -#define REALM_SYNC_CLIENT_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace realm::sync { - -class MigrationStore; -class SubscriptionStore; - -class Client { -public: - using port_type = sync::port_type; - - using Config = ClientConfig; - - /// \throw util::EventLoop::Implementation::NotAvailable if no event loop - /// implementation was specified, and - /// util::EventLoop::Implementation::get_default() throws it. - Client(Config = {}); - Client(Client&&) noexcept; - ~Client() noexcept; - - /// Thread-safe. - void shutdown() noexcept; - - /// Forces all connections to close and waits for any pending work on the event - /// loop to complete. All sessions must be destroyed before calling shutdown_and_wait. - void shutdown_and_wait(); - - /// \brief Cancel current or next reconnect delay for all servers. - /// - /// This corresponds to calling Session::cancel_reconnect_delay() on all - /// bound sessions, but will also cancel reconnect delays applying to - /// servers for which there are currently no bound sessions. - /// - /// Thread-safe. - void cancel_reconnect_delay(); - - /// Forces all open connections to disconnect/reconnect. To be used in testing. - void voluntary_disconnect_all_connections(); - - /// \brief Wait for session termination to complete. - /// - /// Wait for termination of all sessions whose termination was initiated - /// prior this call (the completion condition), or until the client's event - /// loop thread exits from Client::run(), whichever happens - /// first. Termination of a session can be initiated implicitly (e.g., via - /// destruction of the session object), or explicitly by Session::detach(). - /// - /// Note: After session termination (when this function returns true) no - /// session specific callback function can be called or continue to execute, - /// and the client is guaranteed to no longer have a Realm file open on - /// behalf of the terminated session. - /// - /// CAUTION: If run() returns while a wait operation is in progress, this - /// waiting function will return immediately, even if the completion - /// condition is not yet satisfied. The completion condition is guaranteed - /// to be satisfied only when these functions return true. If it returns - /// false, session specific callback functions may still be executing or get - /// called, and the associated Realm files may still not have been closed. - /// - /// If a new wait operation is initiated while another wait operation is in - /// progress by another thread, the waiting period of fist operation may, or - /// may not get extended. The application must not assume either. - /// - /// Note: Session termination does not imply that the client has received an - /// UNBOUND message from the server (see the protocol specification). This - /// may happen later. - /// - /// \return True only if the completion condition was satisfied. False if - /// the client's event loop thread exited from Client::run() in which case - /// the completion condition may, or may not have been satisfied. - /// - /// Note: These functions are fully thread-safe. That is, they may be called - /// by any thread, and by multiple threads concurrently. - bool wait_for_session_terminations_or_client_stopped(); - - /// Async version of wait_for_session_terminations_or_client_stopped(). - util::Future notify_session_terminated(); - - /// Returns false if the specified URL is invalid. - bool decompose_server_url(const std::string& url, ProtocolEnvelope& protocol, std::string& address, - port_type& port, std::string& path) const; - -private: - std::unique_ptr m_impl; - friend class Session; -}; - - -/// \brief Client-side representation of a Realm file synchronization session. -/// -/// A synchronization session deals with precisely one local Realm file. To -/// synchronize multiple local Realm files, you need multiple sessions. -/// -/// A session object is always associated with a particular client object (\ref -/// Client). Destroying the client while sessions still exist will forcibly -/// close the sessions. This is intended only for the convenience of code which -/// finds it difficult to ensure that objects are torn down in the correct -/// order, and using closed sessions has unspecified results. -/// -/// At most one session is allowed to exist for a particular local Realm file -/// (file system inode) at any point in time. Two session objects for the same -/// file are allowed to exist at different times, if they have no overlap in -/// time as long as they are associated with the same client object, or with -/// two different client objects that do not overlap in time. This means, in -/// particular, that it is an error to create two session objects for the same -/// local Realm file, if they are associated with two different client objects -/// that overlap in time, even if the session objects do not overlap in time -/// (in their bound state). It is the responsibility of the application to -/// ensure that these rules are adhered to. The consequences of a violation are -/// unspecified. -/// -/// Thread-safety: It is safe for multiple threads to construct, use (with some -/// exceptions), and destroy session objects concurrently, regardless of whether -/// those session objects are associated with the same, or with different Client -/// objects. Please note that some of the public member functions are fully -/// thread-safe, while others are not. -/// -/// Callback semantics: All session specific callback functions will be executed -/// by the event loop thread, i.e., the thread that calls Client::run(). Callback -/// functions may start to execute before Session's constructor returns, as long -/// as some thread is executing Client::run(). Likewise, completion handlers, -/// such as those passed to async_wait_for_sync_completion() may start to -/// execute before the submitting function returns. All session specific -/// callback functions (including completion handlers) are guaranteed to no -/// longer be executing when session termination completes, and they are -/// guaranteed to not be called after session termination completes. Termination -/// is an event that completes asynchronously with respect to the application, -/// but is initiated by calling detach(), or implicitly by destroying a session -/// object. After having initiated one or more session terminations, the -/// application can wait for those terminations to complete by calling -/// Client::wait_for_session_terminations_or_client_stopped(). Since callback -/// functions are always executed by the event loop thread, they are also -/// guaranteed to not be executing after Client::run() has returned. -class Session { -public: - using ErrorInfo = SessionErrorInfo; - using port_type = sync::port_type; - using SyncTransactCallback = void(VersionID old_version, VersionID new_version); - using ProgressHandler = void(std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes, - std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes, - std::uint_fast64_t snapshot_version, double download_estimate, - double upload_estimate, int64_t query_version); - using ConnectionStateChangeListener = void(ConnectionState, std::optional); - using WaitOperCompletionHandler = util::UniqueFunction; - using SSLVerifyCallback = bool(const std::string& server_address, port_type server_port, const char* pem_data, - size_t pem_size, int preverify_ok, int depth); - - struct Config { - Config() {} - - /// server_address is the fully qualified host name, or IP address of - /// the server. - std::string server_address = "localhost"; - - /// server_port is the port at which the server listens. If server_port - /// is zero, the default port for the specified protocol is used. See - /// ProtocolEnvelope for information on default ports. - port_type server_port = 0; - - /// realm_identifier is the virtual path by which the server identifies the - /// Realm. - /// When connecting to the mock C++ server, this path must always be an - /// absolute path, and must therefore always contain a leading slash (`/`). - /// Furthermore, each segment of the virtual path must consist of one or - /// more characters that are either alpha-numeric or in (`_`, `-`, `.`), - /// and each segment is not allowed to equal `.` or `..`, and must not end - /// with `.realm`, `.realm.lock`, or `.realm.management`. These rules are - /// necessary because the C++ server currently reserves the right to use the - /// specified path as part of the file system path of a Realm file. - /// On the MongoDB Realm-based Sync server, virtual paths are not coupled - /// to file system paths, and thus, these restrictions do not apply. - std::string realm_identifier = ""; - - // If the client has successfully contacted the server, then this will be - // set to true, otherwise it is false and the sync sessions will attempt - // to update the location info if the connection fails. - bool server_verified = false; - - /// The user id of the logged in user for this sync session. This will be used - /// along with the server_address/server_port/protocol_envelope to determine - /// which connection to the server this session will use. - std::string user_id; - - /// The protocol used for communicating with the server. See - /// ProtocolEnvelope. - ProtocolEnvelope protocol_envelope = ProtocolEnvelope::realm; - - /// service_identifier is a prefix that is prepended to the realm_identifier - /// in the HTTP GET request that initiates a sync connection. The value - /// specified here must match with the server's expectation. Changing - /// the value of service_identifier should be matched with a corresponding - /// change in the C++ mock server. - std::string service_identifier = ""; - - /// - /// DEPRECATED - Will be removed in a future release - /// - /// authorization_header_name is the name of the HTTP header containing - /// the Realm access token. The value of the HTTP header is "Bearer ". - /// authorization_header_name does not participate in session - /// multiplexing partitioning. - std::string authorization_header_name = "Authorization"; - - /// - /// DEPRECATED - Will be removed in a future release - /// - /// custom_http_headers is a map of custom HTTP headers. The keys of the map - /// are HTTP header names, and the values are the corresponding HTTP - /// header values. - /// If "Authorization" is used as a custom header name, - /// authorization_header_name must be set to anther value. - std::map custom_http_headers; - - /// - /// DEPRECATED - Will be removed in a future release - /// - /// Controls whether the server certificate is verified for SSL - /// connections. It should generally be true in production. - bool verify_servers_ssl_certificate = true; - - /// - /// DEPRECATED - Will be removed in a future release - /// - /// ssl_trust_certificate_path is the path of a trust/anchor - /// certificate used by the client to verify the server certificate. - /// ssl_trust_certificate_path is only used if the protocol is ssl and - /// verify_servers_ssl_certificate is true. - /// - /// A server certificate is verified by first checking that the - /// certificate has a valid signature chain back to a trust/anchor - /// certificate, and secondly checking that the server_address matches - /// a host name contained in the certificate. The host name of the - /// certificate is stored in either Common Name or the Alternative - /// Subject Name (DNS section). - /// - /// If ssl_trust_certificate_path is None (default), ssl_verify_callback - /// (see below) is used if set, and the default device trust/anchor - /// store is used otherwise. - std::optional ssl_trust_certificate_path; - - /// - /// DEPRECATED - Will be removed in a future release - /// - /// If Client::Config::ssl_verify_callback is set, that function is called - /// to verify the certificate, unless verify_servers_ssl_certificate is - /// false. - - /// ssl_verify_callback is used to implement custom SSL certificate - /// verification. it is only used if the protocol is SSL, - /// verify_servers_ssl_certificate is true and ssl_trust_certificate_path - /// is None. - /// - /// The signature of ssl_verify_callback is - /// - /// bool(const std::string& server_address, - /// port_type server_port, - /// const char* pem_data, - /// size_t pem_size, - /// int preverify_ok, - /// int depth); - /// - /// server address and server_port is the address and port of the server - /// that a SSL connection is being established to. They are identical to - /// the server_address and server_port set in this config file and are - /// passed for convenience. - /// pem_data is the certificate of length pem_size in - /// the PEM format. preverify_ok is OpenSSL's preverification of the - /// certificate. preverify_ok is either 0, or 1. If preverify_ok is 1, - /// OpenSSL has accepted the certificate and it will generally be safe - /// to trust that certificate. depth represents the position of the - /// certificate in the certificate chain sent by the server. depth = 0 - /// represents the actual server certificate that should contain the - /// host name(server address) of the server. The highest depth is the - /// root certificate. - /// The callback function will receive the certificates starting from - /// the root certificate and moving down the chain until it reaches the - /// server's own certificate with a host name. The depth of the last - /// certificate is 0. The depth of the first certificate is chain - /// length - 1. - /// - /// The return value of the callback function decides whether the - /// client accepts the certificate. If the return value is false, the - /// processing of the certificate chain is interrupted and the SSL - /// connection is rejected. If the return value is true, the verification - /// process continues. If the callback function returns true for all - /// presented certificates including the depth == 0 certificate, the - /// SSL connection is accepted. - /// - /// A recommended way of using the callback function is to return true - /// if preverify_ok = 1 and depth > 0, - /// always check the host name if depth = 0, - /// and use an independent verification step if preverify_ok = 0. - /// - /// Another possible way of using the callback is to collect all the - /// certificates until depth = 0, and present the entire chain for - /// independent verification. - std::function ssl_verify_callback; - - /// signed_user_token is a cryptographically signed token describing the - /// identity and access rights of the current user. - std::string signed_user_token; - - using ClientReset = sync::ClientReset; - std::optional client_reset_config; - - /// - /// DEPRECATED - Will be removed in a future release - /// - std::optional proxy_config; - - /// When integrating a flexible sync bootstrap, process this many bytes of - /// changeset data in a single integration attempt. - size_t flx_bootstrap_batch_size_bytes = 1024 * 1024; - - /// Set to true to cause the integration of the first received changeset - /// (in a DOWNLOAD message) to fail. - /// - /// This feature exists exclusively for testing purposes at this time. - bool simulate_integration_error = false; - - util::UniqueFunction on_sync_client_event_hook; - - - /// Set a handler to monitor the state of download and upload progress. - /// - /// The handler must have signature - /// - /// void(uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes, - /// uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, - /// uint_fast64_t progress_version); - /// - /// downloaded_bytes is the size in bytes of all downloaded changesets. - /// downloadable_bytes is equal to downloaded_bytes plus an estimate of - /// the size of the remaining server history. - /// - /// uploaded_bytes is the size in bytes of all locally produced changesets - /// that have been received and acknowledged by the server. - /// uploadable_bytes is the size in bytes of all locally produced changesets. - /// - /// Due to the nature of the merge rules, it is possible that the size of an - /// uploaded changeset uploaded from one client is not equal to the size of - /// the changesets that other clients will download. - /// - /// Typical uses of this function: - /// - /// Upload completion can be checked by - /// - /// bool upload_complete = (uploaded_bytes == uploadable_bytes); - /// - /// Download completion could be checked by - /// - /// bool download_complete = (downloaded_bytes == downloadable_bytes); - /// - /// However, download completion might never be reached because the server - /// can receive new changesets from other clients. downloadable_bytes can - /// decrease for two reasons: server side compaction and changesets of - /// local origin. Code using downloadable_bytes must not assume that it - /// is increasing. - /// - /// Upload progress can be calculated by caching an initial value of - /// uploaded_bytes from the last, or next, callback. Then - /// - /// double upload_progress = - /// (uploaded_bytes - initial_uploaded_bytes) - /// ------------------------------------------- - /// (uploadable_bytes - initial_uploaded_bytes) - /// - /// Download progress can be calculates similarly: - /// - /// double download_progress = - /// (downloaded_bytes - initial_downloaded_bytes) - /// ----------------------------------------------- - /// (downloadable_bytes - initial_downloaded_bytes) - /// - /// progress_version is 0 at the start of a session. When at least one - /// DOWNLOAD message has been received from the server, progress_version is - /// positive. progress_version can be used to ensure that the reported - /// progress contains information obtained from the server in the current - /// session. The server will send a message as soon as possible, and the - /// progress handler will eventually be called with a positive progress_version - /// unless the session is interrupted before a message from the server has - /// been received. - /// - /// The handler is called on the event loop thread.The handler after bind(), - /// after each DOWNLOAD message, and after each local transaction - /// (nonsync_transact_notify). - util::UniqueFunction progress_handler; - - /// Install a connection state change listener. - /// - /// Sets a function to be called whenever the state of the underlying - /// network connection changes between "disconnected", "connecting", and - /// "connected". The initial state is always "disconnected". The next state - /// after "disconnected" is always "connecting". The next state after - /// "connecting" is either "connected" or "disconnected". The next state - /// after "connected" is always "disconnected". A switch to the - /// "disconnected" state only happens when an error occurs. - /// - /// Whenever the installed function is called, an SessionErrorInfo object is passed - /// when, and only when the passed state is ConnectionState::disconnected. - /// - /// When multiple sessions share a single connection, the state changes will - /// be reported for each session in turn. - /// - /// The callback function will always be called by the thread that executes - /// the event loop (Client::run()). If the - /// callback function throws an exception, that exception will "travel" out - /// through Client::run(). - util::UniqueFunction connection_state_change_listener; - - /// Is this session being opened for a realm whose path ends in ".fresh"? If so, - /// it will be downloading a fresh copy of the realm data from the server. - bool fresh_realm_download = false; - - /// Schema version - /// - /// Note: Currently set only for FLX sync. - uint64_t schema_version = -1; // = ObjectStore::NotVersioned - }; - - /// \brief Start a new session for the specified client-side Realm. - Session(Client&, std::shared_ptr, std::shared_ptr, std::shared_ptr, - Config&& = {}); - - /// This leaves the right-hand side session object detached. See "Thread - /// safety" section under detach(). - Session(Session&&) noexcept; - - /// Create a detached session object (see detach()). - Session() noexcept = default; - - /// Implies detachment. See "Thread safety" section under detach(). - ~Session() noexcept; - - /// Detach the object on the left-hand side, then "steal" the session from - /// the object on the right-hand side, if there is one. This leaves the - /// object on the right-hand side detached. See "Thread safety" section - /// under detach(). - Session& operator=(Session&&) noexcept; - - /// Detach this session object from the client object (Client). If the - /// session object is already detached, this function has no effect - /// (idempotency). - /// - /// Detachment initiates session termination, which is an event that takes - /// place shortly thereafter in the context of the client's event loop - /// thread. - /// - /// A detached session object may be destroyed, move-assigned to, and moved - /// from. Apart from that, it is an error to call any function other than - /// detach() on a detached session object. - /// - /// Thread safety: Detachment is not a thread-safe operation. This means - /// that detach() may not be executed by two threads concurrently, and may - /// not execute concurrently with object destruction. Additionally, - /// detachment must not execute concurrently with a moving operation - /// involving the session object on the left or right-hand side. See move - /// constructor and assignment operator. - void detach() noexcept; - - /// @} - - /// \brief Refresh the access token associated with this session. - /// - /// This causes the REFRESH protocol message to be sent to the server. See - /// ProtocolEnvelope. It is an error to pass a token with a different user - /// identity than the token used to initiate the session. - /// - /// In an on-going session the application may expect the access token to - /// expire at a certain time and schedule acquisition of a fresh access - /// token (using a refresh token or by other means) in due time to provide a - /// better user experience, and seamless connectivity to the server. - /// - /// If the application does not proactively refresh an expiring token, the - /// session will eventually be disconnected. The application can detect this - /// by monitoring the connection state - /// (set_connection_state_change_listener()), and check whether the error - /// code is `ProtocolError::token_expired`. Such a session can then be - /// revived by calling refresh() with a newly acquired access token. - /// - /// Due to protocol techicalities, a race condition exists that can cause a - /// session to become, and remain disconnected after a new access token has - /// been passed to refresh(). The application can work around this race - /// condition by detecting the `ProtocolError::token_expired` error, and - /// always initiate a token renewal in this case. - /// - /// Note: This function is thread-safe. - /// - /// \param signed_user_token A cryptographically signed token describing the - /// identity and access rights of the current user. See ProtocolEnvelope. - void refresh(std::string_view signed_user_token); - - /// \brief Inform the synchronization agent about changes of local origin. - /// - /// This function must be called by the application after a transaction - /// performed on its behalf, that is, after a transaction that is not - /// performed to integrate a changeset that was downloaded from the server. - /// - /// Note: This function is fully thread-safe. That is, it may be called by - /// any thread, and by multiple threads concurrently. - void nonsync_transact_notify(version_type new_version); - - /// @{ \brief Wait for upload, download, or upload+download completion. - /// - /// async_wait_for_upload_completion() initiates an asynchronous wait for - /// upload to complete, async_wait_for_download_completion() initiates an - /// asynchronous wait for download to complete, and - /// async_wait_for_sync_completion() initiates an asynchronous wait for - /// upload and download to complete. - /// - /// Upload is considered complete when all non-empty changesets of local - /// origin have been uploaded to the server, and the server has acknowledged - /// reception of them. Changesets of local origin introduced after the - /// initiation of the session will generally not be - /// considered for upload unless they are announced to this client through - /// nonsync_transact_notify() prior to the initiation of the wait operation, - /// i.e., prior to the invocation of async_wait_for_upload_completion() or - /// async_wait_for_sync_completion(). Unannounced changesets may get picked - /// up, but there is no guarantee that they will be, however, if a certain - /// changeset is announced, then all previous changesets are implicitly - /// announced. Also all preexisting changesets are implicitly announced - /// when the session is initiated. - /// - /// Download is considered complete when all non-empty changesets of remote - /// origin have been downloaded from the server, and integrated into the - /// local Realm state. To know what is currently outstanding on the server, - /// the client always sends a special "marker" message to the server, and - /// waits until it has downloaded all outstanding changesets that were - /// present on the server at the time when the server received that marker - /// message. Each call to async_wait_for_download_completion() and - /// async_wait_for_sync_completion() therefore requires a full client <-> - /// server round-trip. - /// - /// If a new wait operation is initiated while another wait operation is in - /// progress by another thread, the waiting period of first operation may, - /// or may not get extended. The application must not assume either. The - /// application may assume, however, that async_wait_for_upload_completion() - /// will not affect the waiting period of - /// async_wait_for_download_completion(), and vice versa. - /// - /// The specified completion handlers will always be executed by the thread - /// that executes the event loop (the thread that calls Client::run()). If - /// the handler throws an exception, that exception will "travel" out - /// through Client::run(). - /// - /// If incomplete wait operations exist when the session is terminated, - /// those wait operations will be canceled. Session termination is an event - /// that happens in the context of the client's event loop thread shortly - /// after the destruction of the session object. The Status - /// argument passed to the completion handler of a canceled wait operation - /// will be `ErrorCodes::OperationAborted`. For uncanceled wait operations - /// it will be `Status::OK()`. Note that as long as the client's event - /// loop thread is running, all completion handlers will be called - /// regardless of whether the operations get canceled or not. - /// - /// CAUTION: The specified completion handlers may get called before the - /// call to the waiting function returns, and it may get called (or continue - /// to execute) after the session object is destroyed. Please see "Callback - /// semantics" section under Session for more on this. - /// - /// Note: These functions are fully thread-safe. That is, they may be called - /// by any thread, and by multiple threads concurrently. - void async_wait_for_sync_completion(WaitOperCompletionHandler); - void async_wait_for_upload_completion(WaitOperCompletionHandler); - void async_wait_for_download_completion(WaitOperCompletionHandler); - /// @} - - /// @{ \brief Synchronous wait for upload or download completion. - /// - /// These functions are synchronous equivalents of - /// async_wait_for_upload_completion() and - /// async_wait_for_download_completion() respectively. This means that they - /// block the caller until the completion condition is satisfied, or the - /// client's event loop thread exits from Client::run(), whichever happens - /// first. - /// - /// CAUTION: If Client::run() returns while a wait operation is in progress, - /// these waiting functions return immediately, even if the completion - /// condition is not yet satisfied. The completion condition is guaranteed - /// to be satisfied only when these functions return true. - /// - /// \return True only if the completion condition was satisfied. False if - /// the client's event loop thread exited from Client::run() in which case - /// the completion condition may, or may not have been satisfied. - /// - /// Note: These functions are fully thread-safe. That is, they may be called - /// by any thread, and by multiple threads concurrently. - bool wait_for_upload_complete_or_client_stopped(); - bool wait_for_download_complete_or_client_stopped(); - /// @} - - /// \brief Cancel the current or next reconnect delay for the server - /// associated with this session. - /// - /// When the network connection is severed, or an attempt to establish - /// connection fails, a certain delay will take effect before the client - /// will attempt to reestablish the connection. This delay will generally - /// grow with the number of unsuccessful reconnect attempts, and can grow to - /// over a minute. In some cases however, the application will know when it - /// is a good time to stop waiting and retry immediately. One example is - /// when a device has been offline for a while, and the operating system - /// then tells the application that network connectivity has been restored. - /// - /// Clearly, this function should not be called too often and over extended - /// periods of time, as that would effectively disable the built-in "server - /// hammering" protection. - /// - /// This function is fully thread-safe. That is, it may be called by any - /// thread, and by multiple threads concurrently. - void cancel_reconnect_delay(); - - util::Future send_test_command(std::string command_body); - - /// Returns the app services connection id if the session is connected, otherwise - /// returns an empty string. This function blocks until the value is set from - /// the event loop thread. If an error occurs, this will throw an ExceptionForStatus - /// with the error. - std::string get_appservices_connection_id(); - -private: - // This is a bare pointer rather than bind_ptr to avoid requiring the - // definition of SessionWrapper here. - SessionWrapper* m_impl = nullptr; - - void abandon() noexcept; - void async_wait_for(bool upload_completion, bool download_completion, WaitOperCompletionHandler); -}; - -std::ostream& operator<<(std::ostream& os, SyncConfig::ProxyConfig::Type); - -// Implementation - -inline Session::Session(Session&& sess) noexcept - : m_impl{sess.m_impl} -{ - sess.m_impl = nullptr; -} - -inline Session::~Session() noexcept -{ - if (m_impl) - abandon(); -} - -inline Session& Session::operator=(Session&& sess) noexcept -{ - if (m_impl) - abandon(); - m_impl = sess.m_impl; - sess.m_impl = nullptr; - return *this; -} - -inline void Session::detach() noexcept -{ - if (m_impl) - abandon(); - m_impl = nullptr; -} - -inline void Session::async_wait_for_sync_completion(WaitOperCompletionHandler handler) -{ - bool upload_completion = true, download_completion = true; - async_wait_for(upload_completion, download_completion, std::move(handler)); // Throws -} - -inline void Session::async_wait_for_upload_completion(WaitOperCompletionHandler handler) -{ - bool upload_completion = true, download_completion = false; - async_wait_for(upload_completion, download_completion, std::move(handler)); // Throws -} - -inline void Session::async_wait_for_download_completion(WaitOperCompletionHandler handler) -{ - bool upload_completion = false, download_completion = true; - async_wait_for(upload_completion, download_completion, std::move(handler)); // Throws -} - -} // namespace realm::sync - -#endif // REALM_SYNC_CLIENT_HPP diff --git a/src/realm/sync/client_base.hpp b/src/realm/sync/client_base.hpp deleted file mode 100644 index 4dafb8bd549..00000000000 --- a/src/realm/sync/client_base.hpp +++ /dev/null @@ -1,288 +0,0 @@ -#ifndef REALM_SYNC_CLIENT_BASE_HPP -#define REALM_SYNC_CLIENT_BASE_HPP - -#include -#include -#include -#include -#include - -namespace realm::sync { -class ClientImpl; -class SessionWrapper; -class SyncSocketProvider; - -/// The presence of the ClientReset config indicates an ongoing or requested client -/// reset operation. If client_reset is util::none or if the local Realm does not -/// exist, an ordinary sync session will take place. -/// -/// A session will perform client reset by downloading a fresh copy of the Realm -/// from the server at a different file path location. After download, the fresh -/// Realm will be integrated into the local Realm in a write transaction. The -/// application is free to read or write to the local realm during the entire client -/// reset. Like a DOWNLOAD message, the application will not be able to perform a -/// write transaction at the same time as the sync client performs its own write -/// transaction. Client reset is not more disturbing for the application than any -/// DOWNLOAD message. The application can listen to change notifications from the -/// client reset exactly as in a DOWNLOAD message. If the application writes to the -/// local realm during client reset but before the client reset operation has -/// obtained a write lock, the changes made by the application may be lost or -/// overwritten depending on the recovery mode selected. -/// -/// Client reset downloads its fresh Realm copy for a Realm at path "xyx.realm" to -/// "xyz.realm.fresh". It is assumed that this path is available for use and if -/// there are any problems the client reset will fail with -/// Client::Error::client_reset_failed. -/// -/// The recommended usage of client reset is after a previous session encountered an -/// error that implies the need for a client reset. It is not recommended to persist -/// the need for a client reset. The application should just attempt to synchronize -/// in the usual fashion and only after hitting an error, start a new session with a -/// client reset. In other words, if the application crashes during a client reset, -/// the application should attempt to perform ordinary synchronization after restart -/// and switch to client reset if needed. -/// -/// Error codes that imply the need for a client reset are the session level error -/// codes described by SyncError::is_client_reset_requested() -/// -/// However, other errors such as bad changeset (UPLOAD) could also be resolved with -/// a client reset. Client reset can even be used without any prior error if so -/// desired. -/// -/// After completion of a client reset, the sync client will continue synchronizing -/// with the server in the usual fashion. -/// -/// The progress of client reset can be tracked with the standard progress handler. -/// -/// Client reset is done when the progress handler arguments satisfy -/// "progress_version > 0". However, if the application wants to ensure that it has -/// all data present on the server, it should wait for download completion using -/// either void async_wait_for_download_completion(WaitOperCompletionHandler) or -/// bool wait_for_download_complete_or_client_stopped(). -struct ClientReset { - realm::ClientResyncMode mode; - DBRef fresh_copy; - Status error; - sync::ProtocolErrorInfo::Action action = sync::ProtocolErrorInfo::Action::ClientReset; - util::UniqueFunction notify_before_client_reset; - util::UniqueFunction notify_after_client_reset; -}; - -static constexpr milliseconds_type default_connect_timeout = 120000; // 2 minutes -static constexpr milliseconds_type default_connection_linger_time = 30000; // 30 seconds -static constexpr milliseconds_type default_ping_keepalive_period = 60000; // 1 minute -static constexpr milliseconds_type default_pong_keepalive_timeout = 120000; // 2 minutes -static constexpr milliseconds_type default_fast_reconnect_limit = 60000; // 1 minute - -using RoundtripTimeHandler = void(milliseconds_type roundtrip_time); - -struct ClientConfig { - /// An optional logger to be used by the client. If no logger is - /// specified, the client will use an instance of util::StderrLogger - /// with the log level threshold set to util::Logger::Level::info. The - /// client does not require a thread-safe logger, and it guarantees that - /// all logging happens either on behalf of the constructor or on behalf - /// of the invocation of run(). - std::shared_ptr logger; - - // The SyncSocket instance used by the Sync Client for event synchronization - // and creating WebSockets. If not provided the default implementation will be used. - std::shared_ptr socket_provider; - - /// Use ports 80 and 443 by default instead of 7800 and 7801 - /// respectively. Ideally, these default ports should have been made - /// available via a different URI scheme instead (http/https or ws/wss). - bool enable_default_port_hack = true; - - /// For testing purposes only. - ReconnectMode reconnect_mode = ReconnectMode::normal; - - /// Create a separate connection for each session. -#if REALM_DISABLE_SYNC_MULTIPLEXING - bool one_connection_per_session = true; -#else - bool one_connection_per_session = false; -#endif - - /// Do not access the local file system. Sessions will act as if - /// initiated on behalf of an empty (or nonexisting) local Realm - /// file. Received DOWNLOAD messages will be accepted, but otherwise - /// ignored. No UPLOAD messages will be generated. For testing purposes - /// only. - /// - /// Many operations, such as serialized transactions, are not suppored - /// in this mode. - bool dry_run = false; - - /// The maximum number of milliseconds to allow for a connection to - /// become fully established. This includes the time to resolve the - /// network address, the TCP connect operation, the SSL handshake, and - /// the WebSocket handshake. - milliseconds_type connect_timeout = default_connect_timeout; - - /// The number of milliseconds to keep a connection open after all - /// sessions have been abandoned (or suspended by errors). - /// - /// The purpose of this linger time is to avoid close/reopen cycles - /// during short periods of time where there are no sessions interested - /// in using the connection. - /// - /// If the connection gets closed due to an error before the linger time - /// expires, the connection will be kept closed until there are sessions - /// willing to use it again. - milliseconds_type connection_linger_time = default_connection_linger_time; - - /// The client will send PING messages periodically to allow the server - /// to detect dead connections (heartbeat). This parameter specifies the - /// time, in milliseconds, between these PING messages. When scheduling - /// the next PING message, the client will deduct a small random amount - /// from the specified value to help spread the load on the server from - /// many clients. - milliseconds_type ping_keepalive_period = default_ping_keepalive_period; - - /// Whenever the server receives a PING message, it is supposed to - /// respond with a PONG messsage to allow the client to detect dead - /// connections (heartbeat). This parameter specifies the time, in - /// milliseconds, that the client will wait for the PONG response - /// message before it assumes that the connection is dead, and - /// terminates it. - milliseconds_type pong_keepalive_timeout = default_pong_keepalive_timeout; - - /// The maximum amount of time, in milliseconds, since the loss of a - /// prior connection, for a new connection to be considered a *fast - /// reconnect*. - /// - /// In general, when a client establishes a connection to the server, - /// the uploading process remains suspended until the initial - /// downloading process completes (as if by invocation of - /// Session::async_wait_for_download_completion()). However, to avoid - /// unnecessary latency in change propagation during ongoing - /// application-level activity, if the new connection is established - /// less than a certain amount of time (`fast_reconnect_limit`) since - /// the client was previously connected to the server, then the - /// uploading process will be activated immediately. - /// - /// For now, the purpose of the general delaying of the activation of - /// the uploading process, is to increase the chance of multiple initial - /// transactions on the client-side, to be uploaded to, and processed by - /// the server as a single unit. In the longer run, the intention is - /// that the client should upload transformed (from reciprocal history), - /// rather than original changesets when applicable to reduce the need - /// for changeset to be transformed on both sides. The delaying of the - /// upload process will increase the number of cases where this is - /// possible. - /// - /// FIXME: Currently, the time between connections is not tracked across - /// sessions, so if the application closes its session, and opens a new - /// one immediately afterwards, the activation of the upload process - /// will be delayed unconditionally. - milliseconds_type fast_reconnect_limit = default_fast_reconnect_limit; - - /// If a connection is disconnected because of an error that isn't a - /// sync protocol ERROR message, this parameter will be used to decide how - /// long to wait between each re-connect attempt. - ResumptionDelayInfo reconnect_backoff_info; - - /// Set to true to completely disable delaying of the upload process. In - /// this mode, the upload process will be activated immediately, and the - /// value of `fast_reconnect_limit` is ignored. - /// - /// For testing purposes only. - bool disable_upload_activation_delay = false; - - /// The specified function will be called whenever a PONG message is - /// received on any connection. The round-trip time in milliseconds will - /// be pased to the function. The specified function will always be - /// called by the client's event loop thread, i.e., the thread that - /// calls `Client::run()`. This feature is mainly for testing purposes. - std::function roundtrip_time_handler; - - /// Disable sync to disk (fsync(), msync()) for all realm files managed - /// by this client. - /// - /// Testing/debugging feature. Should never be enabled in production. - bool disable_sync_to_disk = false; - - /// The sync client supports tables without primary keys by synthesizing a - /// pk using the client file ident, which means that all changesets waiting - /// to be uploaded need to be rewritten with the correct ident the first time - /// we connect to the server. The modern server doesn't support this and - /// requires pks for all tables, so this is now only applicable to old sync - /// tests and so is disabled by default. - bool fix_up_object_ids = false; -}; - -/// \brief Information about an error causing a session to be temporarily -/// disconnected from the server. -/// -/// In general, the connection will be automatically reestablished -/// later. Whether this happens quickly, generally depends on \ref -/// is_fatal. If \ref is_fatal is true, it means that the error is deemed to -/// be of a kind that is likely to persist, and cause all future reconnect -/// attempts to fail. In that case, if another attempt is made at -/// reconnecting, the delay will be substantial (at least an hour). -/// -/// \ref error_code specifies the error that caused the connection to be -/// closed. For the list of errors reported by the server, see \ref -/// ProtocolError (or `protocol.md`). For the list of errors corresponding -/// to protocol violations that are detected by the client, see -/// Client::Error. The error may also be a system level error, or an error -/// from one of the potential intermediate protocol layers (SSL or -/// WebSocket). -/// -/// \ref detailed_message is the most detailed message available to describe -/// the error. It is generally equal to `error_code.message()`, but may also -/// be a more specific message (one that provides extra context). The -/// purpose of this message is mostly to aid in debugging. For non-debugging -/// purposes, `error_code.message()` should generally be considered -/// sufficient. -/// -/// \sa set_connection_state_change_listener(). -struct SessionErrorInfo : public ProtocolErrorInfo { - SessionErrorInfo(const ProtocolErrorInfo& info) - : ProtocolErrorInfo(info) - , status(protocol_error_to_status(static_cast(info.raw_error_code), message)) - { - } - - SessionErrorInfo(const ProtocolErrorInfo& info, Status status) - : ProtocolErrorInfo(info) - , status(std::move(status)) - { - } - - SessionErrorInfo(Status status, IsFatal is_fatal) - : ProtocolErrorInfo(0, {}, is_fatal) - , status(std::move(status)) - { - } - - Status status; -}; - -enum class ConnectionState { disconnected, connecting, connected }; - -inline std::ostream& operator<<(std::ostream& os, ConnectionState state) -{ - switch (state) { - case ConnectionState::disconnected: - return os << "Disconnected"; - case ConnectionState::connecting: - return os << "Connecting"; - case ConnectionState::connected: - return os << "Connected"; - } - REALM_TERMINATE("Invalid ConnectionState value"); -} - -// The reason a synchronization session is used for. -enum class SessionReason { - // Regular synchronization - Sync = 0, - // Download a fresh realm - ClientReset, -}; - -} // namespace realm::sync - -#endif // REALM_SYNC_CLIENT_BASE_HPP diff --git a/src/realm/sync/config.cpp b/src/realm/sync/config.cpp deleted file mode 100644 index d0db93177f8..00000000000 --- a/src/realm/sync/config.cpp +++ /dev/null @@ -1,94 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2015 Realm Inc. -// -// 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 -#include -#include -#include -#include - -#include - -namespace realm { -namespace { - -constexpr static std::string_view s_middle_part(" Logs: "); -std::string format_sync_error_message(const Status& status, std::optional log_url) -{ - if (!log_url) { - return status.reason(); - } - - return util::format("%1%2%3", status.reason(), s_middle_part, *log_url); -} - -} // namespace -// sync defines its own copy of port_type to avoid depending on network.hpp, but they should be the same. -static_assert(std::is_same_v); - -using ProtocolError = realm::sync::ProtocolError; - -SyncError::SyncError(Status orig_status, bool is_fatal, std::optional server_log, - std::vector compensating_writes) - : status(orig_status.code(), format_sync_error_message(orig_status, server_log)) - , is_fatal(is_fatal) - , simple_message(std::string_view(status.reason()).substr(0, orig_status.reason().size())) - , compensating_writes_info(std::move(compensating_writes)) -{ - if (server_log) { - logURL = std::string_view(status.reason()).substr(simple_message.size() + s_middle_part.size()); - } -} - -/// The error indicates a client reset situation. -bool SyncError::is_client_reset_requested() const -{ - if (server_requests_action == sync::ProtocolErrorInfo::Action::ClientReset || - server_requests_action == sync::ProtocolErrorInfo::Action::ClientResetNoRecovery) { - return true; - } - if (status == ErrorCodes::AutoClientResetFailed) { - return true; - } - return false; -} - -SyncConfig::SyncConfig(std::shared_ptr user, bson::Bson partition) - : user(std::move(user)) - , partition_value(partition.to_string()) -{ -} -SyncConfig::SyncConfig(std::shared_ptr user, std::string partition) - : user(std::move(user)) - , partition_value(std::move(partition)) -{ -} -SyncConfig::SyncConfig(std::shared_ptr user, const char* partition) - : user(std::move(user)) - , partition_value(partition) -{ -} - -SyncConfig::SyncConfig(std::shared_ptr user, FLXSyncEnabled) - : user(std::move(user)) - , partition_value() - , flx_sync_requested(true) -{ -} - -} // namespace realm diff --git a/src/realm/sync/config.hpp b/src/realm/sync/config.hpp deleted file mode 100644 index c4ef14ea3c2..00000000000 --- a/src/realm/sync/config.hpp +++ /dev/null @@ -1,259 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_SYNC_CONFIG_HPP -#define REALM_SYNC_CONFIG_HPP - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace realm { - -class SyncUser; -class SyncSession; -class Realm; -class ThreadSafeReference; - -namespace bson { -class Bson; -} - -namespace sync { -using port_type = std::uint_fast16_t; -enum class ProtocolError; -} // namespace sync - -struct SyncError { - enum class ClientResetModeAllowed { DoNotClientReset, RecoveryPermitted, RecoveryNotPermitted }; - - Status status; - - bool is_fatal; - - // The following two string_view's are views into the reason string of the status member. Users of - // SyncError should take care not to modify the status if they are going to access these views into - // the reason string. - // Just the minimal error message, without any log URL. - std::string_view simple_message; - // The URL to the associated server log if any. If not supplied by the server, this will be `empty()`. - std::string_view logURL; - /// A dictionary of extra user information associated with this error. - /// If this is a client reset error, the keys for c_original_file_path_key and c_recovery_file_path_key will be - /// populated with the relevant filesystem paths. - std::unordered_map user_info; - /// The sync server may send down an error that the client does not recognize, - /// whether because of a version mismatch or an oversight. It is still valuable - /// to expose these errors so that users can do something about them. - bool is_unrecognized_by_client = false; - // the server may explicitly send down an action the client should take as part of an error (i.e, client reset) - // if this is set, it overrides the clients interpretation of the error - sync::ProtocolErrorInfo::Action server_requests_action = sync::ProtocolErrorInfo::Action::NoAction; - // If this error resulted from a compensating write, this vector will contain information about each object - // that caused a compensating write and why the write was illegal. - std::vector compensating_writes_info; - - SyncError(Status status, bool is_fatal, std::optional server_log = std::nullopt, - std::vector compensating_writes = {}); - - static constexpr const char c_original_file_path_key[] = "ORIGINAL_FILE_PATH"; - static constexpr const char c_recovery_file_path_key[] = "RECOVERY_FILE_PATH"; - - /// The error indicates a client reset situation. - bool is_client_reset_requested() const; -}; - -using SyncSessionErrorHandler = void(std::shared_ptr, SyncError); - -enum class ReconnectMode { - /// This is the mode that should always be used by SDKs. In this - /// mode the client uses a scheme for determining a reconnect delay that - /// prevents it from creating too many connection requests in a short - /// amount of time (i.e., a server hammering protection mechanism). - normal, - - /// For internal sync-client testing purposes only. - /// - /// Never reconnect automatically after the connection is closed due to - /// an error. Allow immediate reconnect if the connection was closed - /// voluntarily (e.g., due to sessions being abandoned). - /// - /// In this mode, Client::cancel_reconnect_delay() and - /// Session::cancel_reconnect_delay() can still be used to trigger - /// another reconnection attempt (with no delay) after an error has - /// caused the connection to be closed. - testing -}; - -enum class SyncSessionStopPolicy { - Immediately, // Immediately stop the session as soon as all Realms/Sessions go out of scope. - LiveIndefinitely, // Never stop the session. - AfterChangesUploaded, // Once all Realms/Sessions go out of scope, wait for uploads to complete and stop. -}; - -enum class ClientResyncMode : unsigned char { - // Fire a client reset error - Manual, - // Discard local changes, without disrupting accessors or closing the Realm - DiscardLocal, - // Attempt to recover unsynchronized but committed changes. - Recover, - // Attempt recovery and if that fails, discard local. - RecoverOrDiscard, -}; - -// clang-format off -#define REALM_FOR_EACH_SYNC_CLIENT_HOOK_EVENT(X) \ - X(DownloadMessageReceived), \ - X(DownloadMessageIntegrated), \ - X(BootstrapMessageProcessed), \ - X(BootstrapProcessed), \ - X(ErrorMessageReceived), \ - X(SessionActivating), \ - X(SessionSuspended), \ - X(SessionConnected), \ - X(SessionResumed), \ - X(BindMessageSent), \ - X(IdentMessageSent), \ - X(ClientResetMergeComplete), \ - X(BootstrapBatchAboutToProcess), \ - X(UploadMessageSent) -// clang-format on - -enum class SyncClientHookEvent { -#define REALM_DECLARE_SYNC_CLIENT_HOOK_EVENT(X) X - REALM_FOR_EACH_SYNC_CLIENT_HOOK_EVENT(REALM_DECLARE_SYNC_CLIENT_HOOK_EVENT) -#undef REALM_DECLARE_SYNC_CLIENT_HOOK_EVENT -}; - -enum class SyncClientHookAction { - NoAction, - EarlyReturn, - SuspendWithRetryableError, - TriggerReconnect, -}; - -inline std::ostream& operator<<(std::ostream& os, SyncClientHookAction action) -{ - switch (action) { - case SyncClientHookAction::NoAction: - return os << "NoAction"; - case SyncClientHookAction::EarlyReturn: - return os << "EarlyReturn"; - case SyncClientHookAction::SuspendWithRetryableError: - return os << "SuspendWithRetryableError"; - case SyncClientHookAction::TriggerReconnect: - return os << "TriggerReconnect"; - } - REALM_TERMINATE("Invalid SyncClientHookAction value"); -} - -struct SyncClientHookData { - SyncClientHookEvent event; - sync::SyncProgress progress; - int64_t query_version; - sync::DownloadBatchState batch_state; - size_t num_changesets; - const sync::ProtocolErrorInfo* error_info = nullptr; -}; - -struct SyncConfig { - struct FLXSyncEnabled {}; - - struct ProxyConfig { - using port_type = sync::port_type; - enum class Type { HTTP, HTTPS } type; - std::string address; - port_type port; - }; - using SSLVerifyCallback = bool(const std::string& server_address, ProxyConfig::port_type server_port, - const char* pem_data, size_t pem_size, int preverify_ok, int depth); - - std::shared_ptr user; - std::string partition_value; - SyncSessionStopPolicy stop_policy = SyncSessionStopPolicy::AfterChangesUploaded; - std::function error_handler; - bool flx_sync_requested = false; - - // When integrating a flexible sync bootstrap, process this many bytes of changeset data in a single integration - // attempt. This many bytes of changesets will be uncompressed and held in memory while being applied. - size_t flx_bootstrap_batch_size_bytes = 1024 * 1024; - - // {@ - /// DEPRECATED - Will be removed in a future release - // The following parameters are only used by the default SyncSocket implementation. Custom SyncSocket - // implementations must handle these directly, if these features are supported. - util::Optional authorization_header_name; // not used - std::map custom_http_headers; - bool client_validate_ssl = true; - util::Optional ssl_trust_certificate_path; - std::function ssl_verify_callback; - util::Optional proxy_config; - // @} - - // If true, upload/download waits are canceled on any sync error and not just fatal ones - bool cancel_waits_on_nonfatal_error = false; - - // If false, changesets incoming from the server are discarded without - // applying them to the Realm file. This is required when writing objects - // directly to replication, and will break horribly otherwise - bool apply_server_changes = true; - - // The name of the directory which Realms should be backed up to following - // a client reset in ClientResyncMode::Manual mode - util::Optional recovery_directory; - ClientResyncMode client_resync_mode = ClientResyncMode::Manual; - std::function before)> notify_before_client_reset; - std::function frozen_before, ThreadSafeReference after, bool did_recover)> - notify_after_client_reset; - // If true, the Realm passed as the `before` argument to the before reset - // callbacks will be frozen - bool freeze_before_reset_realm = true; - - // Used by core testing to hook into the sync client when various events occur and maybe inject - // errors/disconnects deterministically. - std::function, const SyncClientHookData&)> - on_sync_client_event_hook; - - bool simulate_integration_error = false; - - // callback invoked right after DataInitializationFunction. It is used in order to setup an initial subscription. - using SubscriptionInitializerCallback = std::function)>; - SubscriptionInitializerCallback subscription_initializer; - - // in case the initial subscription contains a dynamic query, the user may want to force - // the query to be run again every time the realm is opened. This flag should be set to true - // in this case. - bool rerun_init_subscription_on_open{false}; - - SyncConfig() = default; - explicit SyncConfig(std::shared_ptr user, bson::Bson partition); - explicit SyncConfig(std::shared_ptr user, std::string partition); - explicit SyncConfig(std::shared_ptr user, const char* partition); - explicit SyncConfig(std::shared_ptr user, FLXSyncEnabled); -}; - -} // namespace realm - -#endif // REALM_SYNC_CONFIG_HPP diff --git a/src/realm/sync/history.cpp b/src/realm/sync/history.cpp deleted file mode 100644 index d314cdec95c..00000000000 --- a/src/realm/sync/history.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -namespace realm::sync { - -std::unique_ptr make_client_replication() -{ - bool apply_server_changes = true; - return std::make_unique(apply_server_changes); // Throws -} - -} // namespace realm::sync diff --git a/src/realm/sync/history.hpp b/src/realm/sync/history.hpp deleted file mode 100644 index b9192ff6b87..00000000000 --- a/src/realm/sync/history.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef REALM_SYNC_HISTORY_HPP -#define REALM_SYNC_HISTORY_HPP - -#include - -#include -#include -#include -#include - -namespace realm { -namespace sync { - -struct VersionInfo { - /// Realm snapshot version. - version_type realm_version = 0; - - /// The synchronization version corresponding to `realm_version`. - /// - /// In the context of the client-side history type `sync_version.version` - /// will currently always be equal to `realm_version` and - /// `sync_version.salt` will always be zero. - SaltedVersion sync_version = {0, 0}; -}; - -timestamp_type generate_changeset_timestamp(); - -// FIXME: in C++17, switch to using std::timespec in place of last two -// arguments. -void map_changeset_timestamp(timestamp_type, std::time_t& seconds_since_epoch, long& nanoseconds) noexcept; - -inline timestamp_type generate_changeset_timestamp() -{ - namespace chrono = std::chrono; - // Unfortunately, C++11 does not specify what the epoch is for - // `chrono::system_clock` (or for any other clock). It is believed, however, - // that there is a de-facto standard, that the Epoch for - // `chrono::system_clock` is the Unix epoch, i.e., 1970-01-01T00:00:00Z. See - // http://stackoverflow.com/a/29800557/1698548. Additionally, it is assumed - // that leap seconds are not included in the value returned by - // time_since_epoch(), i.e., that it conforms to POSIX time. This is known - // to be true on Linux. - // - // FIXME: Investigate under which conditions OS X agrees with POSIX about - // not including leap seconds in the value returned by time_since_epoch(). - // - // FIXME: Investigate whether Microsoft Windows agrees with POSIX about - // about not including leap seconds in the value returned by - // time_since_epoch(). - auto time_since_epoch = chrono::system_clock::now().time_since_epoch(); - std::uint_fast64_t millis_since_epoch = chrono::duration_cast(time_since_epoch).count(); - // `offset_in_millis` is the number of milliseconds between - // 1970-01-01T00:00:00Z and 2015-01-01T00:00:00Z not counting leap seconds. - std::uint_fast64_t offset_in_millis = 1420070400000ULL; - if (millis_since_epoch < offset_in_millis) { - throw RuntimeError(ErrorCodes::SyncLocalClockBeforeEpoch, - "Local clock cannot lag behind 2015-01-01T00:00:00Z"); - } - return timestamp_type(millis_since_epoch - offset_in_millis); -} - -inline void map_changeset_timestamp(timestamp_type timestamp, std::time_t& seconds_since_epoch, - long& nanoseconds) noexcept -{ - std::uint_fast64_t offset_in_millis = 1420070400000ULL; - std::uint_fast64_t millis_since_epoch = std::uint_fast64_t(offset_in_millis + timestamp); - seconds_since_epoch = std::time_t(millis_since_epoch / 1000); - nanoseconds = long(millis_since_epoch % 1000 * 1000000L); -} - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_HISTORY_HPP diff --git a/src/realm/sync/impl/clamped_hex_dump.hpp b/src/realm/sync/impl/clamped_hex_dump.hpp deleted file mode 100644 index 0d5c0a7c135..00000000000 --- a/src/realm/sync/impl/clamped_hex_dump.hpp +++ /dev/null @@ -1,30 +0,0 @@ - -#ifndef REALM_IMPL_CLAMPED_HEX_DUMP_HPP -#define REALM_IMPL_CLAMPED_HEX_DUMP_HPP - -#include -#include - -namespace realm { -namespace _impl { - -/// Limit the amount of dumped data to 1024 bytes. For use in connection with -/// logging. -inline std::string clamped_hex_dump(BinaryData blob, std::size_t max_size = 1024) -{ - bool was_clipped = false; - std::size_t size_2 = blob.size(); - if (size_2 > max_size) { - size_2 = max_size; - was_clipped = true; - } - std::string str = util::hex_dump(blob.data(), size_2); // Throws - if (was_clipped) - str += "..."; // Throws - return str; -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_IMPL_CLAMPED_HEX_DUMP_HPP diff --git a/src/realm/sync/impl/clock.hpp b/src/realm/sync/impl/clock.hpp deleted file mode 100644 index 8c36078f3f4..00000000000 --- a/src/realm/sync/impl/clock.hpp +++ /dev/null @@ -1,33 +0,0 @@ - -#ifndef REALM_IMPL_CLOCK_HPP -#define REALM_IMPL_CLOCK_HPP - -#include -#include - -#include - -namespace realm { -namespace _impl { - -inline sync::milliseconds_type realtime_clock_now() noexcept -{ - using clock = std::chrono::system_clock; - auto time_since_epoch = clock::now().time_since_epoch(); - auto millis_since_epoch = std::chrono::duration_cast(time_since_epoch).count(); - return sync::milliseconds_type(millis_since_epoch); -} - - -inline sync::milliseconds_type monotonic_clock_now() noexcept -{ - using clock = std::chrono::steady_clock; - auto time_since_epoch = clock::now().time_since_epoch(); - auto millis_since_epoch = std::chrono::duration_cast(time_since_epoch).count(); - return sync::milliseconds_type(millis_since_epoch); -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_IMPL_CLOCK_HPP diff --git a/src/realm/sync/instruction_applier.cpp b/src/realm/sync/instruction_applier.cpp deleted file mode 100644 index 60dfa4ae0c9..00000000000 --- a/src/realm/sync/instruction_applier.cpp +++ /dev/null @@ -1,1859 +0,0 @@ -#include -#include -#include - -#include - -namespace realm::sync { -namespace { - -constexpr static std::string_view - s_dict_key_wrong_type_err("%1: Dictionary key is not a string on field '%2' in class '%3'"); -constexpr static std::string_view - s_list_index_wrong_type_err("%1: List index is not an integer on field '%2' in class '%3'"); -constexpr static std::string_view - s_wrong_collection_type_err("%1: Not a list or dictionary on field '%2' in class '%3'"); - -REALM_NORETURN void throw_bad_transaction_log(std::string msg) -{ - throw BadChangesetError{std::move(msg)}; -} - -} // namespace - -REALM_NORETURN void InstructionApplier::bad_transaction_log(const std::string& msg) const -{ - if (m_last_object_key) { - // If the last_object_key is valid then we should have a changeset and a current table - REALM_ASSERT(m_log); - REALM_ASSERT(m_last_table_name); - std::stringstream ss; - util::Optional field_name; - if (m_last_field_name) { - field_name = m_last_field_name; - } - const instr::Path* cur_path = m_current_path ? &(*m_current_path) : nullptr; - m_log->print_path(ss, m_last_table_name, *m_last_object_key, field_name, cur_path); - throw_bad_transaction_log( - util::format("%1 (instruction target: %2, version: %3, last_integrated_remote_version: %4, " - "origin_file_ident: %5, timestamp: %6)", - msg, ss.str(), m_log->version, m_log->last_integrated_remote_version, - m_log->origin_file_ident, m_log->origin_timestamp)); - } - else if (m_last_table_name) { - // We should have a changeset if we have a table name defined. - REALM_ASSERT(m_log); - throw_bad_transaction_log( - util::format("%1 (instruction table: %2, version: %3, last_integrated_remote_version: %4, " - "origin_file_ident: %5, timestamp: %6)", - msg, m_log->get_string(m_last_table_name), m_log->version, - m_log->last_integrated_remote_version, m_log->origin_file_ident, m_log->origin_timestamp)); - } - else if (m_log) { - // If all we have is a changeset, then we should log whatever we can about it. - throw_bad_transaction_log(util::format("%1 (version: %2, last_integrated_remote_version: %3, " - "origin_file_ident: %4, timestamp: %5)", - msg, m_log->version, m_log->last_integrated_remote_version, - m_log->origin_file_ident, m_log->origin_timestamp)); - } - throw_bad_transaction_log(std::move(msg)); -} - -template -REALM_NORETURN void InstructionApplier::bad_transaction_log(const char* msg, Params&&... params) const -{ - // FIXME: Avoid throwing in normal program flow (since changesets can come - // in over the network, defective changesets are part of normal program - // flow). - bad_transaction_log(util::format(msg, std::forward(params)...)); -} - -StringData InstructionApplier::get_string(InternString str) const -{ - auto string = m_log->try_get_intern_string(str); - if (REALM_UNLIKELY(!string)) - bad_transaction_log("string read fails"); - return m_log->get_string(*string); -} - -StringData InstructionApplier::get_string(StringBufferRange range) const -{ - auto string = m_log->try_get_string(range); - if (!string) - bad_transaction_log("string read error"); - return *string; -} - -BinaryData InstructionApplier::get_binary(StringBufferRange range) const -{ - auto string = m_log->try_get_string(range); - if (!string) - bad_transaction_log("binary read error"); - return BinaryData{string->data(), string->size()}; -} - -TableRef InstructionApplier::table_for_class_name(StringData class_name) const -{ - if (class_name.size() > Group::max_class_name_length) - bad_transaction_log("class name too long"); - Group::TableNameBuffer buffer; - return m_transaction.get_table(Group::class_name_to_table_name(class_name, buffer)); -} - -template -struct TemporarySwapOut { - explicit TemporarySwapOut(T& target) - : target(target) - , backup() - { - std::swap(target, backup); - } - - ~TemporarySwapOut() - { - std::swap(backup, target); - } - - T& target; - T backup; -}; - -void InstructionApplier::operator()(const Instruction::AddTable& instr) -{ - auto table_name = get_table_name(instr); - - // Temporarily swap out the last object key so it doesn't get included in error messages - TemporarySwapOut last_object_key_guard(m_last_object_key); - - auto add_table = util::overload{ - [&](const Instruction::AddTable::TopLevelTable& spec) { - auto table_type = (spec.is_asymmetric ? Table::Type::TopLevelAsymmetric : Table::Type::TopLevel); - if (spec.pk_type == Instruction::Payload::Type::GlobalKey) { - m_transaction.get_or_add_table(table_name, table_type); - } - else { - if (!is_valid_key_type(spec.pk_type)) { - bad_transaction_log("Invalid primary key type '%1' while adding table '%2'", int8_t(spec.pk_type), - table_name); - } - DataType pk_type = get_data_type(spec.pk_type); - StringData pk_field = get_string(spec.pk_field); - bool nullable = spec.pk_nullable; - - if (!m_transaction.get_or_add_table_with_primary_key(table_name, pk_type, pk_field, nullable, - table_type)) { - bad_transaction_log("AddTable: The existing table '%1' has different properties", table_name); - } - } - }, - [&](const Instruction::AddTable::EmbeddedTable&) { - if (TableRef table = m_transaction.get_table(table_name)) { - if (!table->is_embedded()) { - bad_transaction_log("AddTable: The existing table '%1' is not embedded", table_name); - } - } - else { - m_transaction.add_table(table_name, Table::Type::Embedded); - } - }, - }; - - mpark::visit(std::move(add_table), instr.type); -} - -void InstructionApplier::operator()(const Instruction::EraseTable& instr) -{ - auto table_name = get_table_name(instr); - // Temporarily swap out the last object key so it doesn't get included in error messages - TemporarySwapOut last_object_key_guard(m_last_object_key); - - if (REALM_UNLIKELY(REALM_COVER_NEVER(!m_transaction.has_table(table_name)))) { - // FIXME: Should EraseTable be considered idempotent? - bad_transaction_log("table does not exist"); - } - - m_transaction.remove_table(table_name); -} - -void InstructionApplier::operator()(const Instruction::CreateObject& instr) -{ - auto table = get_table(instr); - ColKey pk_col = table->get_primary_key_column(); - m_last_object_key = instr.object; - - mpark::visit(util::overload{ - [&](mpark::monostate) { - if (!pk_col) { - bad_transaction_log("CreateObject(NULL) on table without a primary key"); - } - if (!table->is_nullable(pk_col)) { - bad_transaction_log("CreateObject(NULL) on a table with a non-nullable primary key"); - } - m_last_object = table->create_object_with_primary_key(util::none); - }, - [&](int64_t pk) { - if (!pk_col) { - bad_transaction_log("CreateObject(Int) on table without a primary key"); - } - if (table->get_column_type(pk_col) != type_Int) { - bad_transaction_log("CreateObject(Int) on a table with primary key type %1", - table->get_column_type(pk_col)); - } - m_last_object = table->create_object_with_primary_key(pk); - }, - [&](InternString pk) { - if (!pk_col) { - bad_transaction_log("CreateObject(String) on table without a primary key"); - } - if (table->get_column_type(pk_col) != type_String) { - bad_transaction_log("CreateObject(String) on a table with primary key type %1", - table->get_column_type(pk_col)); - } - StringData str = get_string(pk); - m_last_object = table->create_object_with_primary_key(str); - }, - [&](const ObjectId& id) { - if (!pk_col) { - bad_transaction_log("CreateObject(ObjectId) on table without a primary key"); - } - if (table->get_column_type(pk_col) != type_ObjectId) { - bad_transaction_log("CreateObject(ObjectId) on a table with primary key type %1", - table->get_column_type(pk_col)); - } - m_last_object = table->create_object_with_primary_key(id); - }, - [&](const UUID& id) { - if (!pk_col) { - bad_transaction_log("CreateObject(UUID) on table without a primary key"); - } - if (table->get_column_type(pk_col) != type_UUID) { - bad_transaction_log("CreateObject(UUID) on a table with primary key type %1", - table->get_column_type(pk_col)); - } - m_last_object = table->create_object_with_primary_key(id); - }, - [&](GlobalKey key) { - if (pk_col) { - bad_transaction_log("CreateObject(GlobalKey) on table with a primary key"); - } - m_last_object = table->create_object(key); - }, - }, - instr.object); -} - -void InstructionApplier::operator()(const Instruction::EraseObject& instr) -{ - // FIXME: Log actions. - // Note: EraseObject is idempotent. - if (auto obj = get_top_object(instr, "EraseObject")) { - // This call will prevent incoming links to be nullified/deleted - obj->invalidate(); - } - m_last_object.reset(); -} - -template -void InstructionApplier::visit_payload(const Instruction::Payload& payload, F&& visitor) -{ - using Type = Instruction::Payload::Type; - - const auto& data = payload.data; - switch (payload.type) { - case Type::ObjectValue: - return visitor(Instruction::Payload::ObjectValue{}); - case Type::Set: - return visitor(Instruction::Payload::Set{}); - case Type::List: - return visitor(Instruction::Payload::List{}); - case Type::Dictionary: - return visitor(Instruction::Payload::Dictionary{}); - case Type::Erased: - return visitor(Instruction::Payload::Erased{}); - case Type::GlobalKey: - return visitor(realm::util::none); // FIXME: Not sure about this - case Type::Null: - return visitor(realm::util::none); - case Type::Int: - return visitor(data.integer); - case Type::Bool: - return visitor(data.boolean); - case Type::String: { - StringData value = get_string(data.str); - return visitor(value); - } - case Type::Binary: { - BinaryData value = get_binary(data.binary); - return visitor(value); - } - case Type::Timestamp: - return visitor(data.timestamp); - case Type::Float: - return visitor(data.fnum); - case Type::Double: - return visitor(data.dnum); - case Type::Decimal: - return visitor(data.decimal); - case Type::Link: { - StringData class_name = get_string(data.link.target_table); - Group::TableNameBuffer buffer; - StringData target_table_name = Group::class_name_to_table_name(class_name, buffer); - TableRef target_table = m_transaction.get_table(target_table_name); - if (!target_table) { - bad_transaction_log("Link with invalid target table '%1'", target_table_name); - } - if (target_table->is_embedded()) { - bad_transaction_log("Link to embedded table '%1'", target_table_name); - } - ObjKey target = get_object_key(*target_table, data.link.target); - ObjLink link = ObjLink{target_table->get_key(), target}; - return visitor(link); - } - case Type::ObjectId: - return visitor(data.object_id); - case Type::UUID: - return visitor(data.uuid); - } -} - -void InstructionApplier::operator()(const Instruction::Update& instr) -{ - struct UpdateResolver : public PathResolver { - UpdateResolver(InstructionApplier* applier, const Instruction::Update& instr) - : PathResolver(applier, instr, "Update") - , m_instr(instr) - { - } - Status on_property(Obj& obj, ColKey col) override - { - // Update of object field. - - auto table = obj.get_table(); - auto table_name = table->get_name(); - auto field_name = table->get_column_name(col); - auto data_type = DataType(col.get_type()); - - auto visitor = [&](const mpark::variant& arg) { - if (const auto link_ptr = mpark::get_if(&arg)) { - if (data_type == type_Mixed || data_type == type_TypedLink) { - obj.set_any(col, *link_ptr, m_instr.is_default); - } - else if (data_type == type_Link) { - // Validate target table. - auto target_table = table->get_link_target(col); - if (target_table->get_key() != link_ptr->get_table_key()) { - m_applier->bad_transaction_log( - "Update: Target table mismatch (expected %1, got %2)", target_table->get_name(), - m_applier->m_transaction.get_table(link_ptr->get_table_key())->get_name()); - } - obj.set(col, link_ptr->get_obj_key(), m_instr.is_default); - } - else { - m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)", - field_name, table_name, col.get_type(), type_Link); - } - } - else if (const auto mixed_ptr = mpark::get_if(&arg)) { - if (mixed_ptr->is_null()) { - if (col.is_nullable()) { - obj.set_null(col, m_instr.is_default); - } - else { - m_applier->bad_transaction_log("Update: NULL in non-nullable field '%2.%1'", field_name, - table_name); - } - } - else if (data_type == type_Mixed || mixed_ptr->get_type() == data_type) { - obj.set_any(col, *mixed_ptr, m_instr.is_default); - } - else { - m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)", - field_name, table_name, col.get_type(), mixed_ptr->get_type()); - } - } - else if (mpark::get_if(&arg)) { - if (obj.is_null(col)) { - obj.create_and_set_linked_object(col); - } - } - else if (mpark::get_if(&arg)) { - m_applier->bad_transaction_log("Update: Dictionary erase at object field"); - } - else if (mpark::get_if(&arg)) { - obj.set_collection(col, CollectionType::Dictionary); - } - else if (mpark::get_if(&arg)) { - obj.set_collection(col, CollectionType::List); - } - else if (mpark::get_if(&arg)) { - obj.set_collection(col, CollectionType::Set); - } - }; - - m_applier->visit_payload(m_instr.value, visitor); - return Status::Pending; - } - Status on_list_index(LstBase& list, uint32_t index) override - { - // Update of list element. - - auto col = list.get_col_key(); - auto data_type = DataType(col.get_type()); - auto table = list.get_table(); - auto table_name = table->get_name(); - auto field_name = table->get_column_name(col); - - auto visitor = util::overload{ - [&](const ObjLink& link) { - if (data_type == type_TypedLink) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& link_list = static_cast&>(list); - link_list.set(index, link); - } - else if (data_type == type_Mixed) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& mixed_list = static_cast&>(list); - mixed_list.set(index, link); - } - else if (data_type == type_Link) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& link_list = static_cast&>(list); - // Validate the target. - auto target_table = table->get_link_target(col); - if (target_table->get_key() != link.get_table_key()) { - m_applier->bad_transaction_log( - "Update: Target table mismatch (expected '%1', got '%2')", target_table->get_name(), - m_applier->m_transaction.get_table(link.get_table_key())->get_name()); - } - link_list.set(index, link.get_obj_key()); - } - else { - m_applier->bad_transaction_log( - "Update: Type mismatch in list at '%2.%1' (expected link type, was %3)", field_name, - table_name, data_type); - } - }, - [&](Mixed value) { - if (value.is_null()) { - if (col.is_nullable()) { - list.set_null(index); - } - else { - m_applier->bad_transaction_log("Update: NULL in non-nullable list '%2.%1'", field_name, - table_name); - } - } - else { - if (data_type == type_Mixed || value.get_type() == data_type) { - list.set_any(index, value); - } - else { - m_applier->bad_transaction_log( - "Update: Type mismatch in list at '%2.%1' (expected %3, got %4)", field_name, - table_name, data_type, value.get_type()); - } - } - }, - [&](const Instruction::Payload::ObjectValue&) { - // Embedded object creation is idempotent, and link lists cannot - // contain nulls, so this is a no-op. - }, - [&](const Instruction::Payload::Dictionary&) { - list.set_collection(size_t(index), CollectionType::Dictionary); - }, - [&](const Instruction::Payload::List&) { - list.set_collection(size_t(index), CollectionType::List); - }, - [&](const Instruction::Payload::Set&) { - list.set_collection(size_t(index), CollectionType::Set); - }, - [&](const Instruction::Payload::Erased&) { - m_applier->bad_transaction_log("Update: Dictionary erase of list element"); - }, - }; - - m_applier->visit_payload(m_instr.value, visitor); - return Status::Pending; - } - Status on_dictionary_key(Dictionary& dict, Mixed key) override - { - // Update (insert) of dictionary element. - - auto visitor = util::overload{ - [&](Mixed value) { - if (value.is_null()) { - // FIXME: Separate handling of NULL is needed because - // `Mixed::get_type()` asserts on NULL. - dict.insert(key, value); - } - else if (value.get_type() == type_Link) { - m_applier->bad_transaction_log("Update: Untyped links are not supported in dictionaries."); - } - else { - dict.insert(key, value); - } - }, - [&](const Instruction::Payload::Erased&) { - dict.try_erase(key); - }, - [&](const Instruction::Payload::ObjectValue&) { - dict.create_and_insert_linked_object(key); - }, - [&](const Instruction::Payload::Dictionary&) { - dict.insert_collection(key.get_string(), CollectionType::Dictionary); - }, - [&](const Instruction::Payload::List&) { - dict.insert_collection(key.get_string(), CollectionType::List); - }, - [&](const Instruction::Payload::Set&) { - dict.insert_collection(key.get_string(), CollectionType::Set); - }, - }; - - m_applier->visit_payload(m_instr.value, visitor); - return Status::Pending; - } - - private: - const Instruction::Update& m_instr; - }; - UpdateResolver resolver(this, instr); - resolver.resolve(); -} - -void InstructionApplier::operator()(const Instruction::AddInteger& instr) -{ - // FIXME: Implement increments of array elements, dictionary values. - struct AddIntegerResolver : public PathResolver { - AddIntegerResolver(InstructionApplier* applier, const Instruction::AddInteger& instr) - : PathResolver(applier, instr, "AddInteger") - , m_instr(instr) - { - } - Status on_property(Obj& obj, ColKey col) - { - // Increment of object field. - if (!obj.is_null(col)) { - try { - obj.add_int(col, m_instr.value); - } - catch (const LogicError&) { - auto table = obj.get_table(); - m_applier->bad_transaction_log("AddInteger: Not an integer field '%2.%1'", - table->get_column_name(col), table->get_name()); - } - } - return Status::Pending; - } - - private: - const Instruction::AddInteger& m_instr; - }; - AddIntegerResolver resolver(this, instr); - resolver.resolve(); -} - -void InstructionApplier::operator()(const Instruction::AddColumn& instr) -{ - using Type = Instruction::Payload::Type; - using CollectionType = Instruction::CollectionType; - - // Temporarily swap out the last object key so it doesn't get included in error messages - TemporarySwapOut last_object_key_guard(m_last_object_key); - - auto table = get_table(instr, "AddColumn"); - auto col_name = get_string(instr.field); - - if (ColKey existing_key = table->get_column_key(col_name)) { - DataType new_type = get_data_type(instr.type); - ColumnType existing_type = existing_key.get_type(); - if (existing_type != ColumnType(new_type)) { - bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (expected %3, got %4)", - table->get_name(), col_name, existing_type, new_type); - } - bool existing_is_list = existing_key.is_list(); - if ((instr.collection_type == CollectionType::List) != existing_is_list) { - bad_transaction_log( - "AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a list, the other is%4)", - table->get_name(), col_name, existing_is_list ? "" : " not", existing_is_list ? " not" : ""); - } - bool existing_is_set = existing_key.is_set(); - if ((instr.collection_type == CollectionType::Set) != existing_is_set) { - bad_transaction_log( - "AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a set, the other is%4)", - table->get_name(), col_name, existing_is_set ? "" : " not", existing_is_set ? " not" : ""); - } - bool existing_is_dict = existing_key.is_dictionary(); - if ((instr.collection_type == CollectionType::Dictionary) != existing_is_dict) { - bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a " - "dictionary, the other is%4)", - table->get_name(), col_name, existing_is_dict ? "" : " not", - existing_is_dict ? " not" : ""); - } - if (new_type == type_Link) { - Group::TableNameBuffer buffer; - auto target_table_name = Group::class_name_to_table_name(get_string(instr.link_target_table), buffer); - if (target_table_name != table->get_link_target(existing_key)->get_name()) { - bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (link targets differ)", - table->get_name(), col_name); - } - } - return; - } - - if (instr.collection_type == CollectionType::Dictionary && instr.key_type != Type::String) { - bad_transaction_log("AddColumn '%1.%3' adding dictionary column with non-string keys", table->get_name(), - col_name); - } - - if (instr.type != Type::Link) { - DataType type = get_data_type(instr.type); - switch (instr.collection_type) { - case CollectionType::Single: { - table->add_column(type, col_name, instr.nullable); - break; - } - case CollectionType::List: { - table->add_column_list(type, col_name, instr.nullable); - break; - } - case CollectionType::Dictionary: { - DataType key_type = get_data_type(instr.key_type); - table->add_column_dictionary(type, col_name, instr.nullable, key_type); - break; - } - case CollectionType::Set: { - table->add_column_set(type, col_name, instr.nullable); - break; - } - } - } - else { - Group::TableNameBuffer buffer; - auto target_table_name = get_string(instr.link_target_table); - if (target_table_name.size() != 0) { - TableRef target = m_transaction.get_table(Group::class_name_to_table_name(target_table_name, buffer)); - if (!target) { - bad_transaction_log("AddColumn(Link) '%1.%2' to table '%3' which doesn't exist", table->get_name(), - col_name, target_table_name); - } - if (instr.collection_type == CollectionType::List) { - table->add_column_list(*target, col_name); - } - else if (instr.collection_type == CollectionType::Set) { - table->add_column_set(*target, col_name); - } - else if (instr.collection_type == CollectionType::Dictionary) { - table->add_column_dictionary(*target, col_name); - } - else { - REALM_ASSERT(instr.collection_type == CollectionType::Single); - table->add_column(*target, col_name); - } - } - else { - if (instr.collection_type == CollectionType::List) { - table->add_column_list(type_TypedLink, col_name); - } - else { - REALM_ASSERT(instr.collection_type == CollectionType::Single); - table->add_column(type_TypedLink, col_name); - } - } - } -} - -void InstructionApplier::operator()(const Instruction::EraseColumn& instr) -{ - // Temporarily swap out the last object key so it doesn't get included in error messages - TemporarySwapOut last_object_key_guard(m_last_object_key); - - auto table = get_table(instr, "EraseColumn"); - auto col_name = get_string(instr.field); - - ColKey col = table->get_column_key(col_name); - if (!col) { - bad_transaction_log("EraseColumn '%1.%2' which doesn't exist", table->get_name(), col_name); - } - - table->remove_column(col); -} - -void InstructionApplier::operator()(const Instruction::ArrayInsert& instr) -{ - struct ArrayInsertResolver : public PathResolver { - ArrayInsertResolver(InstructionApplier* applier, const Instruction::ArrayInsert& instr) - : PathResolver(applier, instr, "ArrayInsert") - , m_instr(instr) - { - } - Status on_list_index(LstBase& list, uint32_t index) override - { - auto data_type = list.get_data_type(); - auto table = list.get_table(); - auto table_name = table->get_name(); - auto field_name = [&] { - return table->get_column_name(list.get_col_key()); - }; - - if (index > m_instr.prior_size) { - m_applier->bad_transaction_log("ArrayInsert: Invalid insertion index (index = %1, prior_size = %2)", - index, m_instr.prior_size); - } - - if (index > list.size()) { - m_applier->bad_transaction_log("ArrayInsert: Index out of bounds (%1 > %2)", index, list.size()); - } - - if (m_instr.prior_size != list.size()) { - m_applier->bad_transaction_log("ArrayInsert: Invalid prior_size (list size = %1, prior_size = %2)", - list.size(), m_instr.prior_size); - } - - auto inserter = util::overload{ - [&](const ObjLink& link) { - if (data_type == type_TypedLink) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& link_list = static_cast&>(list); - link_list.insert(index, link); - } - else if (data_type == type_Mixed) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& mixed_list = static_cast&>(list); - mixed_list.insert(index, link); - } - else if (data_type == type_Link) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& link_list = static_cast&>(list); - // Validate the target. - auto target_table = table->get_link_target(list.get_col_key()); - if (target_table->get_key() != link.get_table_key()) { - m_applier->bad_transaction_log( - "ArrayInsert: Target table mismatch (expected '%1', got '%2')", - target_table->get_name(), - m_applier->m_transaction.get_table(link.get_table_key())->get_name()); - } - link_list.insert(index, link.get_obj_key()); - } - else { - m_applier->bad_transaction_log( - "ArrayInsert: Type mismatch in list at '%2.%1' (expected link type, was %3)", - field_name(), table_name, data_type); - } - }, - [&](Mixed value) { - if (data_type == type_Mixed) { - list.insert_any(index, value); - } - else if (value.is_null()) { - if (list.get_col_key().is_nullable()) { - list.insert_null(index); - } - else { - m_applier->bad_transaction_log("ArrayInsert: NULL in non-nullable list '%2.%1'", - field_name(), table_name); - } - } - else { - if (value.get_type() == data_type) { - list.insert_any(index, value); - } - else { - m_applier->bad_transaction_log( - "ArrayInsert: Type mismatch in list at '%2.%1' (expected %3, got %4)", field_name(), - table_name, data_type, value.get_type()); - } - } - }, - [&](const Instruction::Payload::ObjectValue&) { - if (data_type == type_Link) { - auto target_table = list.get_table()->get_link_target(list.get_col_key()); - if (!target_table->is_embedded()) { - m_applier->bad_transaction_log( - "ArrayInsert: Creation of embedded object of type '%1', which is not " - "an embedded table", - target_table->get_name()); - } - - REALM_ASSERT(dynamic_cast(&list)); - auto& link_list = static_cast(list); - link_list.create_and_insert_linked_object(index); - } - else { - m_applier->bad_transaction_log( - "ArrayInsert: Creation of embedded object in non-link list field '%2.%1'", field_name(), - table_name); - } - }, - [&](const Instruction::Payload::Dictionary&) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& mixed_list = static_cast&>(list); - mixed_list.insert_collection(size_t(index), CollectionType::Dictionary); - }, - [&](const Instruction::Payload::List&) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& mixed_list = static_cast&>(list); - mixed_list.insert_collection(size_t(index), CollectionType::List); - }, - [&](const Instruction::Payload::Set&) { - REALM_ASSERT(dynamic_cast*>(&list)); - auto& mixed_list = static_cast&>(list); - mixed_list.insert_collection(size_t(index), CollectionType::Set); - }, - [&](const Instruction::Payload::Erased&) { - m_applier->bad_transaction_log("Dictionary erase payload for ArrayInsert"); - }, - }; - - m_applier->visit_payload(m_instr.value, inserter); - return Status::Pending; - } - - private: - const Instruction::ArrayInsert& m_instr; - }; - ArrayInsertResolver(this, instr).resolve(); -} - -void InstructionApplier::operator()(const Instruction::ArrayMove& instr) -{ - struct ArrayMoveResolver : public PathResolver { - ArrayMoveResolver(InstructionApplier* applier, const Instruction::ArrayMove& instr) - : PathResolver(applier, instr, "ArrayMove") - , m_instr(instr) - { - } - Status on_list_index(LstBase& list, uint32_t index) override - { - if (index >= list.size()) { - m_applier->bad_transaction_log("ArrayMove from out of bounds (%1 >= %2)", m_instr.index(), - list.size()); - } - if (m_instr.ndx_2 >= list.size()) { - m_applier->bad_transaction_log("ArrayMove to out of bounds (%1 >= %2)", m_instr.ndx_2, list.size()); - } - if (index == m_instr.ndx_2) { - // FIXME: Does this really need to be an error? - m_applier->bad_transaction_log("ArrayMove to same location (%1)", m_instr.index()); - } - if (m_instr.prior_size != list.size()) { - m_applier->bad_transaction_log("ArrayMove: Invalid prior_size (list size = %1, prior_size = %2)", - list.size(), m_instr.prior_size); - } - list.move(index, m_instr.ndx_2); - return Status::Pending; - } - - private: - const Instruction::ArrayMove& m_instr; - }; - ArrayMoveResolver(this, instr).resolve(); -} - -void InstructionApplier::operator()(const Instruction::ArrayErase& instr) -{ - struct ArrayEraseResolver : public PathResolver { - ArrayEraseResolver(InstructionApplier* applier, const Instruction::ArrayErase& instr) - : PathResolver(applier, instr, "ArrayErase") - , m_instr(instr) - { - } - Status on_list_index(LstBase& list, uint32_t index) override - { - if (index >= m_instr.prior_size) { - m_applier->bad_transaction_log("ArrayErase: Invalid index (index = %1, prior_size = %2)", index, - m_instr.prior_size); - } - if (index >= list.size()) { - m_applier->bad_transaction_log("ArrayErase: Index out of bounds (%1 >= %2)", index, list.size()); - } - if (m_instr.prior_size != list.size()) { - m_applier->bad_transaction_log("ArrayErase: Invalid prior_size (list size = %1, prior_size = %2)", - list.size(), m_instr.prior_size); - } - list.remove(index, index + 1); - return Status::Pending; - } - - private: - const Instruction::ArrayErase& m_instr; - }; - ArrayEraseResolver(this, instr).resolve(); -} - -void InstructionApplier::operator()(const Instruction::Clear& instr) -{ - // For collections and nested collections in Mixed, applying a Clear instruction - // implicitly sets the collection type (of the clear instruction) too. - struct ClearResolver : public PathResolver { - ClearResolver(InstructionApplier* applier, const Instruction::Clear& instr) - : PathResolver(applier, instr, "Clear") - { - switch (instr.collection_type) { - case Instruction::CollectionType::Single: - break; - case Instruction::CollectionType::List: - m_collection_type = CollectionType::List; - break; - case Instruction::CollectionType::Dictionary: - m_collection_type = CollectionType::Dictionary; - break; - case Instruction::CollectionType::Set: - m_collection_type = CollectionType::Set; - break; - } - } - void on_list(LstBase& list) override - { - // list property - if (m_collection_type && *m_collection_type != CollectionType::List) { - m_applier->bad_transaction_log("Clear: Not a List"); - } - list.clear(); - } - Status on_list_index(LstBase& list, uint32_t index) override - { - REALM_ASSERT(m_collection_type); - REALM_ASSERT(dynamic_cast*>(&list)); - auto& mixed_list = static_cast&>(list); - if (index >= mixed_list.size()) { - m_applier->bad_transaction_log("Clear: Index out of bounds (%1 > %2)", index, - mixed_list.size()); // Throws - } - auto val = mixed_list.get(index); - if (val.is_type(type_Dictionary)) { - Dictionary d(mixed_list, mixed_list.get_key(index)); - d.clear(); - } - else if (val.is_type(type_List)) { - Lst l(mixed_list, mixed_list.get_key(index)); - l.clear(); - } - else if (val.is_type(type_Set)) { - m_applier->bad_transaction_log("Clear: Item at index %1 is a Set", - index); // Throws - } - mixed_list.set_collection(size_t(index), *m_collection_type); - return Status::Pending; - } - void on_dictionary(Dictionary& dict) override - { - // dictionary property - if (m_collection_type && *m_collection_type != CollectionType::Dictionary) { - m_applier->bad_transaction_log("Clear: Not a Dictionary"); - } - dict.clear(); - } - Status on_dictionary_key(Dictionary& dict, Mixed key) override - { - REALM_ASSERT(m_collection_type); - if (auto val = dict.try_get(key)) { - if (val->is_type(type_Dictionary)) { - Dictionary d(dict, dict.build_index(key)); - d.clear(); - } - else if (val->is_type(type_List)) { - Lst l(dict, dict.build_index(key)); - l.clear(); - } - else if (val->is_type(type_Set)) { - m_applier->bad_transaction_log("Clear: Item at key '%1' is a Set", - key); // Throws - } - dict.insert_collection(key.get_string(), *m_collection_type); - return Status::Pending; - } - m_applier->bad_transaction_log("Clear: Key '%1' not found", key); - return Status::DidNotResolve; - } - void on_set(SetBase& set) override - { - // set property - if (m_collection_type && *m_collection_type != CollectionType::Set) { - m_applier->bad_transaction_log("Clear: Not a Set"); - } - set.clear(); - } - Status on_property(Obj& obj, ColKey col_key) override - { - if (col_key.get_type() == col_type_Mixed) { - REALM_ASSERT(m_collection_type); - auto val = obj.get(col_key); - if (val.is_type(type_Dictionary)) { - Dictionary dict(obj, col_key); - dict.clear(); - } - else if (val.is_type(type_List)) { - Lst list(obj, col_key); - list.clear(); - } - else if (val.is_type(type_Set)) { - m_applier->bad_transaction_log("Clear: Mixed property is a Set"); // Throws - } - obj.set_collection(col_key, *m_collection_type); - return Status::Pending; - } - - return PathResolver::on_property(obj, col_key); - } - - private: - // The server may not send the type for collection properties (non-Mixed) - // since the clients don't send it either before v13. - std::optional m_collection_type; - }; - ClearResolver(this, instr).resolve(); -} - -bool InstructionApplier::allows_null_links(const Instruction::PathInstruction& instr, - const std::string_view& instr_name) -{ - struct AllowsNullsResolver : public PathResolver { - AllowsNullsResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr, - const std::string_view& instr_name) - : PathResolver(applier, instr, instr_name) - , m_allows_nulls(false) - { - } - Status on_list_index(LstBase&, uint32_t) override - { - return Status::Pending; - } - void on_list(LstBase&) override {} - void on_set(SetBase&) override {} - void on_dictionary(Dictionary&) override - { - m_allows_nulls = true; - } - Status on_dictionary_key(Dictionary&, Mixed) override - { - m_allows_nulls = true; - return Status::Pending; - } - Status on_property(Obj&, ColKey) override - { - m_allows_nulls = true; - return Status::Pending; - } - bool allows_nulls() - { - resolve(); - return m_allows_nulls; - } - - private: - bool m_allows_nulls; - }; - return AllowsNullsResolver(this, instr, instr_name).allows_nulls(); -} - -std::string InstructionApplier::to_string(const Instruction::PathInstruction& instr) const -{ - REALM_ASSERT(m_log); - std::stringstream ss; - m_log->print_path(ss, instr.table, instr.object, instr.field, &instr.path); - return ss.str(); -} - -bool InstructionApplier::check_links_exist(const Instruction::Payload& payload) -{ - bool valid_payload = true; - using Type = Instruction::Payload::Type; - if (payload.type == Type::Link) { - StringData class_name = get_string(payload.data.link.target_table); - Group::TableNameBuffer buffer; - StringData target_table_name = Group::class_name_to_table_name(class_name, buffer); - TableRef target_table = m_transaction.get_table(target_table_name); - if (!target_table) { - bad_transaction_log("Link with invalid target table '%1'", target_table_name); - } - if (target_table->is_embedded()) { - bad_transaction_log("Link to embedded table '%1'", target_table_name); - } - Mixed linked_pk = - mpark::visit(util::overload{[&](mpark::monostate) { - return Mixed{}; // the link exists and the pk is null - }, - [&](int64_t pk) { - return Mixed{pk}; - }, - [&](InternString interned_pk) { - return Mixed{get_string(interned_pk)}; - }, - [&](GlobalKey) -> Mixed { - bad_transaction_log( - "Unexpected link to embedded object while validating a primary key"); - }, - [&](ObjectId pk) { - return Mixed{pk}; - }, - [&](UUID pk) { - return Mixed{pk}; - }}, - payload.data.link.target); - - if (!target_table->find_primary_key(linked_pk)) { - valid_payload = false; - } - } - return valid_payload; -} - -void InstructionApplier::operator()(const Instruction::SetInsert& instr) -{ - struct SetInsertResolver : public PathResolver { - SetInsertResolver(InstructionApplier* applier, const Instruction::SetInsert& instr) - : PathResolver(applier, instr, "SetInsert") - , m_instr(instr) - { - } - Status on_property(Obj& obj, ColKey col) override - { - // This better be a mixed column - REALM_ASSERT(col.get_type() == col_type_Mixed); - auto set = obj.get_set(col); - on_set(set); - return Status::Pending; - } - void on_set(SetBase& set) override - { - auto col = set.get_col_key(); - auto data_type = DataType(col.get_type()); - auto table = set.get_table(); - auto table_name = table->get_name(); - auto field_name = table->get_column_name(col); - - auto inserter = util::overload{ - [&](const ObjLink& link) { - if (data_type == type_TypedLink) { - REALM_ASSERT(dynamic_cast*>(&set)); - auto& link_set = static_cast&>(set); - link_set.insert(link); - } - else if (data_type == type_Mixed) { - REALM_ASSERT(dynamic_cast*>(&set)); - auto& mixed_set = static_cast&>(set); - mixed_set.insert(link); - } - else if (data_type == type_Link) { - REALM_ASSERT(dynamic_cast*>(&set)); - auto& link_set = static_cast&>(set); - // Validate the target. - auto target_table = table->get_link_target(col); - if (target_table->get_key() != link.get_table_key()) { - m_applier->bad_transaction_log( - "SetInsert: Target table mismatch (expected '%1', got '%2')", - target_table->get_name(), table_name); - } - link_set.insert(link.get_obj_key()); - } - else { - m_applier->bad_transaction_log( - "SetInsert: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name, - table_name, data_type); - } - }, - [&](Mixed value) { - if (value.is_null() && !col.is_nullable()) { - m_applier->bad_transaction_log("SetInsert: NULL in non-nullable set '%2.%1'", field_name, - table_name); - } - - if (data_type == type_Mixed || value.is_null() || value.get_type() == data_type) { - set.insert_any(value); - } - else { - m_applier->bad_transaction_log( - "SetInsert: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name, - table_name, data_type, value.get_type()); - } - }, - [&](const Instruction::Payload::ObjectValue&) { - m_applier->bad_transaction_log("SetInsert: Sets of embedded objects are not supported."); - }, - [&](const Instruction::Payload::Dictionary&) { - m_applier->bad_transaction_log("SetInsert: Sets of dictionaries are not supported."); - }, - [&](const Instruction::Payload::List&) { - m_applier->bad_transaction_log("SetInsert: Sets of lists are not supported."); - }, - [&](const Instruction::Payload::Set&) { - m_applier->bad_transaction_log("SetInsert: Sets of sets are not supported."); - }, - [&](const Instruction::Payload::Erased&) { - m_applier->bad_transaction_log("SetInsert: Dictionary erase payload in SetInsert"); - }, - }; - - m_applier->visit_payload(m_instr.value, inserter); - } - - private: - const Instruction::SetInsert& m_instr; - }; - SetInsertResolver(this, instr).resolve(); -} - -void InstructionApplier::operator()(const Instruction::SetErase& instr) -{ - struct SetEraseResolver : public PathResolver { - SetEraseResolver(InstructionApplier* applier, const Instruction::SetErase& instr) - : PathResolver(applier, instr, "SetErase") - , m_instr(instr) - { - } - Status on_property(Obj& obj, ColKey col) override - { - // This better be a mixed column - REALM_ASSERT(col.get_type() == col_type_Mixed); - auto set = obj.get_set(col); - on_set(set); - return Status::Pending; - } - void on_set(SetBase& set) override - { - auto col = set.get_col_key(); - auto data_type = DataType(col.get_type()); - auto table = set.get_table(); - auto table_name = table->get_name(); - auto field_name = table->get_column_name(col); - - auto inserter = util::overload{ - [&](const ObjLink& link) { - if (data_type == type_TypedLink) { - REALM_ASSERT(dynamic_cast*>(&set)); - auto& link_set = static_cast&>(set); - link_set.erase(link); - } - else if (data_type == type_Mixed) { - REALM_ASSERT(dynamic_cast*>(&set)); - auto& mixed_set = static_cast&>(set); - mixed_set.erase(link); - } - else if (data_type == type_Link) { - REALM_ASSERT(dynamic_cast*>(&set)); - auto& link_set = static_cast&>(set); - // Validate the target. - auto target_table = table->get_link_target(col); - if (target_table->get_key() != link.get_table_key()) { - m_applier->bad_transaction_log( - "SetErase: Target table mismatch (expected '%1', got '%2')", target_table->get_name(), - table_name); - } - link_set.erase(link.get_obj_key()); - } - else { - m_applier->bad_transaction_log( - "SetErase: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name, - table_name, data_type); - } - }, - [&](Mixed value) { - if (value.is_null() && !col.is_nullable()) { - m_applier->bad_transaction_log("SetErase: NULL in non-nullable set '%2.%1'", field_name, - table_name); - } - - if (data_type == type_Mixed || value.get_type() == data_type) { - set.erase_any(value); - } - else { - m_applier->bad_transaction_log( - "SetErase: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name, table_name, - data_type, value.get_type()); - } - }, - [&](const Instruction::Payload::ObjectValue&) { - m_applier->bad_transaction_log("SetErase: Sets of embedded objects are not supported."); - }, - [&](const Instruction::Payload::List&) { - m_applier->bad_transaction_log("SetErase: Sets of lists are not supported."); - }, - [&](const Instruction::Payload::Set&) { - m_applier->bad_transaction_log("SetErase: Sets of sets are not supported."); - }, - [&](const Instruction::Payload::Dictionary&) { - m_applier->bad_transaction_log("SetErase: Sets of dictionaries are not supported."); - }, - [&](const Instruction::Payload::Erased&) { - m_applier->bad_transaction_log("SetErase: Dictionary erase payload in SetErase"); - }, - }; - - m_applier->visit_payload(m_instr.value, inserter); - } - - private: - const Instruction::SetErase& m_instr; - }; - SetEraseResolver(this, instr).resolve(); -} - -StringData InstructionApplier::get_table_name(const Instruction::TableInstruction& instr, - const std::string_view& name) -{ - if (auto class_name = m_log->try_get_string(instr.table)) { - return Group::class_name_to_table_name(*class_name, m_table_name_buffer); - } - else { - bad_transaction_log("Corrupt table name in %1 instruction", name); - } -} - -TableRef InstructionApplier::get_table(const Instruction::TableInstruction& instr, const std::string_view& name) -{ - if (instr.table == m_last_table_name) { - return m_last_table; - } - else { - auto table_name = get_table_name(instr, name); - TableRef table = m_transaction.get_table(table_name); - if (!table) { - bad_transaction_log("%1: Table '%2' does not exist", name, table_name); - } - m_last_table = table; - m_last_table_name = instr.table; - m_last_object_key.reset(); - m_last_object.reset(); - m_last_field_name = InternString{}; - m_last_field = ColKey{}; - return table; - } -} - -util::Optional InstructionApplier::get_top_object(const Instruction::ObjectInstruction& instr, - const std::string_view& name) -{ - if (m_last_table_name == instr.table && m_last_object_key && m_last_object && - *m_last_object_key == instr.object) { - // We have already found the object, reuse it. - return *m_last_object; - } - else { - TableRef table = get_table(instr, name); - ObjKey key = get_object_key(*table, instr.object, name); - if (!key) { - return util::none; - } - if (!table->is_valid(key)) { - // Check if the object is deleted or is a tombstone. - return util::none; - } - - Obj obj = table->get_object(key); - m_last_object_key = instr.object; - m_last_object = obj; - return obj; - } -} - -LstBasePtr InstructionApplier::get_list_from_path(Obj& obj, ColKey col) -{ - // For link columns, `Obj::get_listbase_ptr()` always returns an instance whose concrete type is - // `LnkLst`, which uses condensed indexes. However, we are interested in using non-condensed - // indexes, so we need to manually construct a `Lst` instead for lists of non-embedded - // links. - REALM_ASSERT(col.is_list()); - LstBasePtr list; - if (col.get_type() == col_type_Link) { - auto table = obj.get_table(); - if (!table->get_link_target(col)->is_embedded()) { - list = obj.get_list_ptr(col); - } - else { - list = obj.get_listbase_ptr(col); - } - } - else { - list = obj.get_listbase_ptr(col); - } - return list; -} - -InstructionApplier::PathResolver::PathResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr, - const std::string_view& instr_name) - : m_applier(applier) - , m_path_instr(instr) - , m_instr_name(instr_name) -{ -} - -InstructionApplier::PathResolver::~PathResolver() -{ - on_finish(); -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_property(Obj&, ColKey) -{ - m_applier->bad_transaction_log(util::format("Invalid path for %1 (object, column)", m_instr_name)); - return Status::DidNotResolve; -} - -void InstructionApplier::PathResolver::on_list(LstBase&) -{ - m_applier->bad_transaction_log(util::format("Invalid path for %1 (list)", m_instr_name)); -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index(LstBase&, uint32_t) -{ - m_applier->bad_transaction_log(util::format("Invalid path for %1 (list, index)", m_instr_name)); - return Status::DidNotResolve; -} - -void InstructionApplier::PathResolver::on_dictionary(Dictionary&) -{ - m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name)); -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_dictionary_key(Dictionary&, Mixed) -{ - m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name)); - return Status::DidNotResolve; -} - -void InstructionApplier::PathResolver::on_set(SetBase&) -{ - m_applier->bad_transaction_log(util::format("Invalid path for %1 (set)", m_instr_name)); -} - -void InstructionApplier::PathResolver::on_error(const std::string& err_msg) -{ - m_applier->bad_transaction_log(err_msg); -} - -InstructionApplier::PathResolver::Status -InstructionApplier::PathResolver::on_mixed_type_changed(const std::string& err_msg) -{ - m_applier->bad_transaction_log(err_msg); - return Status::DidNotResolve; -} - -void InstructionApplier::PathResolver::on_column_advance(ColKey col) -{ - m_applier->m_last_field = col; -} - -void InstructionApplier::PathResolver::on_dict_key_advance(StringData) {} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index_advance(uint32_t) -{ - return Status::Pending; -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_null_link_advance(StringData, - StringData) -{ - return Status::Pending; -} - -InstructionApplier::PathResolver::Status -InstructionApplier::PathResolver::on_dict_key_not_found(StringData, StringData, StringData) -{ - return Status::Pending; -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_begin(const util::Optional&) -{ - m_applier->m_current_path = m_path_instr.path; - m_applier->m_last_field_name = m_path_instr.field; - return Status::Pending; -} - -void InstructionApplier::PathResolver::on_finish() -{ - m_applier->m_current_path.reset(); - m_applier->m_last_field_name = InternString{}; - m_applier->m_last_field = ColKey{}; -} - -StringData InstructionApplier::PathResolver::get_string(InternString interned) -{ - return m_applier->get_string(interned); -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve() -{ - util::Optional obj = m_applier->get_top_object(m_path_instr, m_instr_name); - Status begin_status = on_begin(obj); - if (begin_status != Status::Pending) { - return begin_status; - } - if (!obj) { - m_applier->bad_transaction_log("%1: No such object: '%2' in class '%3'", m_instr_name, - format_pk(m_applier->m_log->get_key(m_path_instr.object)), - get_string(m_path_instr.table)); - } - - m_it_begin = m_path_instr.path.begin(); - m_it_end = m_path_instr.path.end(); - Status status = resolve_field(*obj, m_path_instr.field); - return status == Status::Pending ? Status::Success : status; -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_field(Obj& obj, InternString field) -{ - auto field_name = get_string(field); - ColKey col = obj.get_table()->get_column_key(field_name); - if (!col) { - on_error(util::format("%1: No such field: '%2' in class '%3'", m_instr_name, field_name, - obj.get_table()->get_name())); - return Status::DidNotResolve; - } - on_column_advance(col); - - if (m_it_begin == m_it_end) { - if (col.is_list()) { - auto list = obj.get_listbase_ptr(col); - on_list(*list); - } - else if (col.is_dictionary()) { - auto dict = obj.get_dictionary(col); - on_dictionary(dict); - } - else if (col.is_set()) { - SetBasePtr set; - if (col.get_type() == col_type_Link) { - // We are interested in using non-condensed indexes - as for Lists below - set = obj.get_set_ptr(col); - } - else { - set = obj.get_setbase_ptr(col); - } - on_set(*set); - } - else { - return on_property(obj, col); - } - return Status::Pending; - } - - if (col.is_list()) { - if (auto pindex = mpark::get_if(&*m_it_begin)) { - auto list = InstructionApplier::get_list_from_path(obj, col); - ++m_it_begin; - return resolve_list_element(*list, *pindex); - } - on_error( - util::format(s_list_index_wrong_type_err.data(), m_instr_name, field_name, obj.get_table()->get_name())); - } - else if (col.is_dictionary()) { - if (auto pkey = mpark::get_if(&*m_it_begin)) { - auto dict = obj.get_dictionary(col); - ++m_it_begin; - return resolve_dictionary_element(dict, *pkey); - } - on_error( - util::format(s_dict_key_wrong_type_err.data(), m_instr_name, field_name, obj.get_table()->get_name())); - } - else if (col.get_type() == col_type_Mixed) { - auto val = obj.get(col); - std::string_view error_msg; - if (val.is_type(type_Dictionary)) { - if (auto pkey = mpark::get_if(&*m_it_begin)) { - Dictionary dict(obj, col); - ++m_it_begin; - return resolve_dictionary_element(dict, *pkey); - } - error_msg = s_dict_key_wrong_type_err; - } - else if (val.is_type(type_List)) { - if (auto pindex = mpark::get_if(&*m_it_begin)) { - Lst list(obj, col); - ++m_it_begin; - return resolve_list_element(list, *pindex); - } - error_msg = s_list_index_wrong_type_err; - } - else { - error_msg = s_wrong_collection_type_err; - } - return on_mixed_type_changed( - util::format(error_msg.data(), m_instr_name, field_name, obj.get_table()->get_name())); - } - else if (col.get_type() == col_type_Link) { - auto target = obj.get_table()->get_link_target(col); - if (!target->is_embedded()) { - on_error(util::format("%1: Reference through non-embedded link in field '%2' in class '%3'", m_instr_name, - field_name, obj.get_table()->get_name())); - } - else if (obj.is_null(col)) { - Status null_status = - on_null_link_advance(obj.get_table()->get_name(), obj.get_table()->get_column_name(col)); - if (null_status != Status::Pending) { - return null_status; - } - on_error(util::format("%1: Reference through NULL embedded link in field '%2' in class '%3'", - m_instr_name, field_name, obj.get_table()->get_name())); - } - else if (auto pfield = mpark::get_if(&*m_it_begin)) { - auto embedded_object = obj.get_linked_object(col); - ++m_it_begin; - return resolve_field(embedded_object, *pfield); - } - else { - on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name)); - } - } - else { - on_error(util::format("%1: Resolving path through unstructured field '%3.%2' of type %4", m_instr_name, - field_name, obj.get_table()->get_name(), col.get_type())); - } - return Status::DidNotResolve; -} - -InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_list_element(LstBase& list, - uint32_t index) -{ - if (m_it_begin == m_it_end) { - return on_list_index(list, index); - } - - auto col = list.get_col_key(); - auto table = list.get_table(); - auto field_name = table->get_column_name(col); - - if (col.get_type() == col_type_Link) { - auto target = table->get_link_target(col); - if (!target->is_embedded()) { - on_error(util::format("%1: Reference through non-embedded link at '%2.%3[%4]'", m_instr_name, - table->get_name(), field_name, index)); - return Status::DidNotResolve; - } - - Status list_status = on_list_index_advance(index); - if (list_status != Status::Pending) { - return list_status; - } - - REALM_ASSERT(dynamic_cast(&list)); - auto& link_list = static_cast(list); - if (index >= link_list.size()) { - on_error(util::format("%1: Out-of-bounds index through list at '%2.%3[%4]'", m_instr_name, - table->get_name(), field_name, index)); - } - else if (auto pfield = mpark::get_if(&*m_it_begin)) { - auto embedded_object = link_list.get_object(index); - ++m_it_begin; - return resolve_field(embedded_object, *pfield); - } - on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name)); - } - else { - if (list.get_data_type() == type_Mixed) { - Status list_status = on_list_index_advance(index); - if (list_status != Status::Pending) { - return list_status; - } - auto& mixed_list = static_cast&>(list); - if (index >= mixed_list.size()) { - on_error(util::format("%1: Out-of-bounds index '%2' through list along path '%2.%3'", m_instr_name, - index, table->get_name(), field_name)); - } - else { - auto val = mixed_list.get(index); - std::string_view error_msg; - - if (val.is_type(type_Dictionary)) { - if (auto pfield = mpark::get_if(&*m_it_begin)) { - Dictionary d(mixed_list, mixed_list.get_key(index)); - ++m_it_begin; - return resolve_dictionary_element(d, *pfield); - } - error_msg = s_dict_key_wrong_type_err; - } - else if (val.is_type(type_List)) { - if (auto pindex = mpark::get_if(&*m_it_begin)) { - Lst l(mixed_list, mixed_list.get_key(index)); - ++m_it_begin; - return resolve_list_element(l, *pindex); - } - error_msg = s_list_index_wrong_type_err; - } - else { - error_msg = s_wrong_collection_type_err; - } - return on_mixed_type_changed( - util::format(error_msg.data(), m_instr_name, field_name, table->get_name())); - } - } - on_error(util::format( - "%1: Resolving path through unstructured list element on '%2.%3', which is a list of type '%4'", - m_instr_name, table->get_name(), field_name, col.get_type())); - } - return Status::DidNotResolve; -} - -InstructionApplier::PathResolver::Status -InstructionApplier::PathResolver::resolve_dictionary_element(Dictionary& dict, InternString key) -{ - StringData string_key = get_string(key); - if (m_it_begin == m_it_end) { - return on_dictionary_key(dict, Mixed{string_key}); - } - - on_dict_key_advance(string_key); - - auto col = dict.get_col_key(); - auto table = dict.get_table(); - auto field_name = table->get_column_name(col); - - if (col.get_type() == col_type_Link) { - auto target = dict.get_target_table(); - if (!target->is_embedded()) { - on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name, - table->get_name(), string_key)); - return Status::DidNotResolve; - } - - auto embedded_object = dict.get_object(string_key); - if (!embedded_object) { - Status null_link_status = on_null_link_advance(table->get_name(), string_key); - if (null_link_status != Status::Pending) { - return null_link_status; - } - on_error(util::format("%1: Unmatched key through dictionary at '%3.%2[%4]'", m_instr_name, field_name, - table->get_name(), string_key)); - } - else if (auto pfield = mpark::get_if(&*m_it_begin)) { - ++m_it_begin; - return resolve_field(embedded_object, *pfield); - } - else { - on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name)); - } - } - else { - if (auto val = dict.try_get(string_key)) { - std::string_view error_msg; - if (val->is_type(type_Dictionary)) { - if (auto pfield = mpark::get_if(&*m_it_begin)) { - Dictionary d(dict, dict.build_index(string_key)); - ++m_it_begin; - return resolve_dictionary_element(d, *pfield); - } - error_msg = s_dict_key_wrong_type_err; - } - else if (val->is_type(type_List)) { - if (auto pindex = mpark::get_if(&*m_it_begin)) { - Lst l(dict, dict.build_index(string_key)); - ++m_it_begin; - return resolve_list_element(l, *pindex); - } - error_msg = s_list_index_wrong_type_err; - } - else { - error_msg = s_wrong_collection_type_err; - } - return on_mixed_type_changed(util::format(error_msg.data(), m_instr_name, field_name, table->get_name())); - } - Status key_not_found_status = on_dict_key_not_found(table->get_name(), field_name, string_key); - if (key_not_found_status != Status::Pending) { - return key_not_found_status; - } - on_error(util::format("%1: Unmatched key '%2' through dictionary along path '%3.%4'", m_instr_name, - string_key, table->get_name(), field_name)); - } - return Status::DidNotResolve; -} - - -ObjKey InstructionApplier::get_object_key(Table& table, const Instruction::PrimaryKey& primary_key, - const std::string_view& name) const -{ - StringData table_name = table.get_name(); - ColKey pk_col = table.get_primary_key_column(); - StringData pk_name = ""; - DataType pk_type; - if (pk_col) { - pk_name = table.get_column_name(pk_col); - pk_type = table.get_column_type(pk_col); - } - return mpark::visit( - util::overload{ - [&](mpark::monostate) { - if (!pk_col) { - bad_transaction_log( - "%1 instruction with NULL primary key, but table '%2' does not have a primary key column", - name, table_name); - } - if (!table.is_nullable(pk_col)) { - bad_transaction_log("%1 instruction with NULL primary key, but column '%2.%3' is not nullable", - name, table_name, pk_name); - } - - ObjKey key = table.get_objkey_from_primary_key(realm::util::none); - return key; - }, - [&](int64_t pk) { - if (!pk_col) { - bad_transaction_log("%1 instruction with integer primary key (%2), but table '%3' does not have " - "a primary key column", - name, pk, table_name); - } - if (pk_type != type_Int) { - bad_transaction_log( - "%1 instruction with integer primary key (%2), but '%3.%4' has primary keys of type '%5'", - name, pk, table_name, pk_name, pk_type); - } - ObjKey key = table.get_objkey_from_primary_key(pk); - return key; - }, - [&](InternString interned_pk) { - auto pk = get_string(interned_pk); - if (!pk_col) { - bad_transaction_log("%1 instruction with string primary key (\"%2\"), but table '%3' does not " - "have a primary key column", - name, pk, table_name); - } - if (pk_type != type_String) { - bad_transaction_log( - "%1 instruction with string primary key (\"%2\"), but '%3.%4' has primary keys of type '%5'", - name, pk, table_name, pk_name, pk_type); - } - ObjKey key = table.get_objkey_from_primary_key(pk); - return key; - }, - [&](GlobalKey id) { - if (pk_col) { - bad_transaction_log( - "%1 instruction without primary key, but table '%2' has a primary key column of type %3", - name, table_name, pk_type); - } - ObjKey key = table.get_objkey_from_global_key(id); - return key; - }, - [&](ObjectId pk) { - if (!pk_col) { - bad_transaction_log("%1 instruction with ObjectId primary key (\"%2\"), but table '%3' does not " - "have a primary key column", - name, pk, table_name); - } - if (pk_type != type_ObjectId) { - bad_transaction_log( - "%1 instruction with ObjectId primary key (%2), but '%3.%4' has primary keys of type '%5'", - name, pk, table_name, pk_name, pk_type); - } - ObjKey key = table.get_objkey_from_primary_key(pk); - return key; - }, - [&](UUID pk) { - if (!pk_col) { - bad_transaction_log("%1 instruction with UUID primary key (\"%2\"), but table '%3' does not " - "have a primary key column", - name, pk, table_name); - } - if (pk_type != type_UUID) { - bad_transaction_log( - "%1 instruction with UUID primary key (%2), but '%3.%4' has primary keys of type '%5'", name, - pk, table_name, pk_name, pk_type); - } - ObjKey key = table.get_objkey_from_primary_key(pk); - return key; - }}, - primary_key); -} - - -} // namespace realm::sync diff --git a/src/realm/sync/instruction_applier.hpp b/src/realm/sync/instruction_applier.hpp deleted file mode 100644 index c51834e6925..00000000000 --- a/src/realm/sync/instruction_applier.hpp +++ /dev/null @@ -1,200 +0,0 @@ -/************************************************************************* - * - * Copyright 2017 Realm Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef REALM_SYNC_IMPL_INSTRUCTION_APPLIER_HPP -#define REALM_SYNC_IMPL_INSTRUCTION_APPLIER_HPP - -#include -#include -#include -#include -#include - -#include - -namespace realm { -namespace sync { - -struct Changeset; - -struct InstructionApplier { - explicit InstructionApplier(Transaction&) noexcept; - - /// Throws BadChangesetError if application fails due to a problem with the - /// changeset. - /// - /// FIXME: Consider using std::error_code instead of throwing - /// BadChangesetError. - void apply(const Changeset&); - - void begin_apply(const Changeset&) noexcept; - void end_apply() noexcept; - -protected: - util::Optional get_top_object(const Instruction::ObjectInstruction&, - const std::string_view& instr = "(unspecified)"); - static LstBasePtr get_list_from_path(Obj& obj, ColKey col); - StringData get_string(InternString) const; - StringData get_string(StringBufferRange) const; - BinaryData get_binary(StringBufferRange) const; - TableRef get_table(const Instruction::TableInstruction&, const std::string_view& instr = "(unspecified)"); -#define REALM_DECLARE_INSTRUCTION_HANDLER(X) virtual void operator()(const Instruction::X&); - REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DECLARE_INSTRUCTION_HANDLER) -#undef REALM_DECLARE_INSTRUCTION_HANDLER - friend struct Instruction; // to allow visitor - - template - static void apply(A& applier, const Changeset&); - - // Allows for in-place modification of changeset while applying it - template - static void apply(A& applier, Changeset&); - - TableRef table_for_class_name(StringData) const; // Throws - - Transaction& m_transaction; - - bool check_links_exist(const Instruction::Payload& payload); - bool allows_null_links(const Instruction::PathInstruction& instr, const std::string_view& instr_name); - std::string to_string(const Instruction::PathInstruction& instr) const; - - struct PathResolver { - enum class Status { Pending, Success, DidNotResolve }; - PathResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr, - const std::string_view& instr_name); - virtual ~PathResolver(); - virtual Status resolve(); - - [[nodiscard]] virtual Status on_property(Obj&, ColKey); - virtual void on_list(LstBase&); - [[nodiscard]] virtual Status on_list_index(LstBase&, uint32_t); - virtual void on_dictionary(Dictionary&); - [[nodiscard]] virtual Status on_dictionary_key(Dictionary&, Mixed); - virtual void on_set(SetBase&); - virtual void on_error(const std::string&); - [[nodiscard]] virtual Status on_mixed_type_changed(const std::string&); - virtual void on_column_advance(ColKey); - virtual void on_dict_key_advance(StringData); - [[nodiscard]] virtual Status on_list_index_advance(uint32_t); - [[nodiscard]] virtual Status on_null_link_advance(StringData, StringData); - [[nodiscard]] virtual Status on_dict_key_not_found(StringData, StringData, StringData); - [[nodiscard]] virtual Status on_begin(const util::Optional& obj); - virtual void on_finish(); - virtual StringData get_string(InternString); - - protected: - [[nodiscard]] Status resolve_field(Obj& obj, InternString field); - [[nodiscard]] Status resolve_list_element(LstBase& list, uint32_t index); - [[nodiscard]] Status resolve_dictionary_element(Dictionary& dict, InternString key); - - InstructionApplier* m_applier; - const Instruction::PathInstruction& m_path_instr; - std::string_view m_instr_name; - Instruction::Path::const_iterator m_it_begin; - Instruction::Path::const_iterator m_it_end; - }; - friend struct PathResolver; - -private: - const Changeset* m_log = nullptr; - - Group::TableNameBuffer m_table_name_buffer; - InternString m_last_table_name; - InternString m_last_field_name; - TableRef m_last_table; - ColKey m_last_field; - util::Optional m_last_object_key; - util::Optional m_current_path; - util::Optional m_last_object; - std::unique_ptr m_last_list; - - StringData get_table_name(const Instruction::TableInstruction&, const std::string_view& instr = "(unspecified)"); - - // Note: This may return a non-invalid ObjKey if the key is dangling. - ObjKey get_object_key(Table& table, const Instruction::PrimaryKey&, - const std::string_view& instr = "(unspecified)") const; - - template - void visit_payload(const Instruction::Payload&, F&& visitor); - - REALM_NORETURN void bad_transaction_log(const std::string& msg) const; - template - REALM_NORETURN void bad_transaction_log(const char* msg, Params&&... params) const; -}; - - -// Implementation - -inline InstructionApplier::InstructionApplier(Transaction& group) noexcept - : m_transaction(group) -{ -} - -inline void InstructionApplier::begin_apply(const Changeset& log) noexcept -{ - m_log = &log; -} - -inline void InstructionApplier::end_apply() noexcept -{ - m_log = nullptr; - m_last_table_name = InternString{}; - m_last_field_name = InternString{}; - m_last_table = TableRef{}; - m_last_field = ColKey{}; - m_last_object.reset(); - m_last_object_key.reset(); - m_last_list.reset(); -} - -template -inline void InstructionApplier::apply(A& applier, const Changeset& changeset) -{ - applier.begin_apply(changeset); - for (auto instr : changeset) { - if (!instr) - continue; - instr->visit(applier); // Throws - } - applier.end_apply(); -} - -template -inline void InstructionApplier::apply(A& applier, Changeset& changeset) -{ - applier.begin_apply(changeset); - for (auto instr : changeset) { - if (!instr) - continue; - instr->visit(applier); // Throws -#if REALM_DEBUG - applier.m_table_info_cache.verify(); -#endif - } - applier.end_apply(); -} - -inline void InstructionApplier::apply(const Changeset& log) -{ - apply(*this, log); // Throws -} - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_IMPL_INSTRUCTION_APPLIER_HPP diff --git a/src/realm/sync/instruction_replication.cpp b/src/realm/sync/instruction_replication.cpp deleted file mode 100644 index c845bd9c0bf..00000000000 --- a/src/realm/sync/instruction_replication.cpp +++ /dev/null @@ -1,825 +0,0 @@ -#include -#include -#include // TransformError -#include - -namespace realm { -namespace sync { - -void SyncReplication::reset() -{ - m_encoder.reset(); - - m_last_table = nullptr; - m_last_object = ObjKey(); - m_last_field_name = StringData(); - m_last_class_name = InternString::npos; - m_last_primary_key = Instruction::PrimaryKey(); - m_last_interned_field_name = InternString::npos; -} - -void SyncReplication::do_initiate_transact(Group& group, version_type current_version, bool history_updated) -{ - Replication::do_initiate_transact(group, current_version, history_updated); - m_transaction = dynamic_cast(&group); // FIXME: Is this safe? - m_write_validator = make_write_validator(*m_transaction); - reset(); -} - -Instruction::Payload SyncReplication::as_payload(Mixed value) -{ - if (value.is_null()) { - return Instruction::Payload{}; - } - - auto type = value.get_type(); - switch (type) { - case type_Int: { - return Instruction::Payload{value.get()}; - } - case type_Bool: { - return Instruction::Payload{value.get()}; - } - case type_Float: { - return Instruction::Payload{value.get()}; - } - case type_Double: { - return Instruction::Payload{value.get()}; - } - case type_String: { - auto str = value.get(); - auto range = m_encoder.add_string_range(str); - return Instruction::Payload{range}; - } - case type_Binary: { - auto binary = value.get(); - auto range = m_encoder.add_string_range(StringData{binary.data(), binary.size()}); - const bool is_binary = true; - return Instruction::Payload{range, is_binary}; - } - case type_Timestamp: { - return Instruction::Payload{value.get()}; - } - case type_Decimal: { - return Instruction::Payload{value.get()}; - } - case type_ObjectId: { - return Instruction::Payload{value.get()}; - } - case type_UUID: { - return Instruction::Payload{value.get()}; - } - case type_TypedLink: - [[fallthrough]]; - case type_Link: { - REALM_TERMINATE("as_payload() needs table/collection for links"); - break; - } - case type_Mixed: { - REALM_TERMINATE("Invalid payload type"); - break; - } - } - if (type == type_Dictionary) { - return Instruction::Payload(Instruction::Payload::Dictionary()); - } - else if (type == type_List) { - return Instruction::Payload(Instruction::Payload::List()); - } - else if (type == type_Set) { - return Instruction::Payload(Instruction::Payload::Set()); - } - return Instruction::Payload{}; -} - -Instruction::Payload SyncReplication::as_payload(const CollectionBase& collection, Mixed value) -{ - return as_payload(*collection.get_table(), collection.get_col_key(), value); -} - -Instruction::Payload SyncReplication::as_payload(const Table& table, ColKey col_key, Mixed value) -{ - if (value.is_null()) { - // FIXME: `Mixed::get_type()` asserts on null. - return Instruction::Payload{}; - } - - if (value.is_type(type_Link)) { - ConstTableRef target_table = table.get_link_target(col_key); - if (target_table->is_embedded()) { - // FIXME: Include target table name to support Mixed of Embedded Objects. - return Instruction::Payload::ObjectValue{}; - } - - Instruction::Payload::Link link; - link.target_table = emit_class_name(*target_table); - link.target = primary_key_for_object(*target_table, value.get()); - return Instruction::Payload{link}; - } - else if (value.is_type(type_TypedLink)) { - auto obj_link = value.get(); - ConstTableRef target_table = m_transaction->get_table(obj_link.get_table_key()); - REALM_ASSERT(target_table); - - if (target_table->is_embedded()) { - ConstTableRef static_target_table = table.get_link_target(col_key); - - if (static_target_table != target_table) - REALM_TERMINATE("Dynamically typed embedded objects not supported yet."); - return Instruction::Payload::ObjectValue{}; - } - - Instruction::Payload::Link link; - link.target_table = emit_class_name(*target_table); - link.target = primary_key_for_object(*target_table, obj_link.get_obj_key()); - return Instruction::Payload{link}; - } - else { - return as_payload(value); - } -} - -InternString SyncReplication::emit_class_name(StringData table_name) -{ - return m_encoder.intern_string(Group::table_name_to_class_name(table_name)); -} - -InternString SyncReplication::emit_class_name(const Table& table) -{ - return emit_class_name(table.get_name()); -} - -Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const -{ - using Type = Instruction::Payload::Type; - switch (type) { - case type_Int: - return Type::Int; - case type_Bool: - return Type::Bool; - case type_String: - return Type::String; - case type_Binary: - return Type::Binary; - case type_Timestamp: - return Type::Timestamp; - case type_Float: - return Type::Float; - case type_Double: - return Type::Double; - case type_Decimal: - return Type::Decimal; - case type_Link: - return Type::Link; - case type_TypedLink: - return Type::Link; - case type_ObjectId: - return Type::ObjectId; - case type_UUID: - return Type::UUID; - case type_Mixed: - return Type::Null; - } - unsupported_instruction(); - return Type::Int; // Make compiler happy -} - -void SyncReplication::add_class(TableKey tk, StringData name, Table::Type table_type) -{ - Replication::add_class(tk, name, table_type); - - bool is_class = m_transaction->table_is_public(tk); - - if (is_class && !m_short_circuit) { - Instruction::AddTable instr; - instr.table = emit_class_name(name); - if (table_type == Table::Type::Embedded) { - instr.type = Instruction::AddTable::EmbeddedTable{}; - } - else { - auto field = m_encoder.intern_string(""); // FIXME: Should this be "_id"? - const bool is_nullable = false; - bool is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric); - instr.type = Instruction::AddTable::TopLevelTable{ - field, - Instruction::Payload::Type::GlobalKey, - is_nullable, - is_asymmetric, - }; - } - emit(instr); - } -} - -void SyncReplication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_field, - bool nullable, Table::Type table_type) -{ - Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type); - - bool is_class = m_transaction->table_is_public(tk); - - if (is_class && !m_short_circuit) { - Instruction::AddTable instr; - instr.table = emit_class_name(name); - auto field = m_encoder.intern_string(pk_field); - auto is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric); - auto spec = Instruction::AddTable::TopLevelTable{field, get_payload_type(pk_type), nullable, is_asymmetric}; - if (!is_valid_key_type(spec.pk_type)) { - unsupported_instruction(); - } - instr.type = std::move(spec); - emit(instr); - } -} - -void SyncReplication::create_object(const Table* table, GlobalKey oid) -{ - if (table->is_embedded()) { - unsupported_instruction(); // FIXME: TODO - } - - Replication::create_object(table, oid); - if (select_table(*table)) { - if (table->get_primary_key_column()) { - // Trying to create object without a primary key in a table that - // has a primary key column. - unsupported_instruction(); - } - Instruction::CreateObject instr; - instr.table = m_last_class_name; - instr.object = oid; - emit(instr); - } -} - -Instruction::PrimaryKey SyncReplication::as_primary_key(Mixed value) -{ - if (value.is_null()) { - return mpark::monostate{}; - } - else if (value.get_type() == type_Int) { - return value.get(); - } - else if (value.get_type() == type_String) { - return m_encoder.intern_string(value.get()); - } - else if (value.get_type() == type_ObjectId) { - return value.get(); - } - else if (value.get_type() == type_UUID) { - return value.get(); - } - else { - // Unsupported primary key type. - unsupported_instruction(); - } -} - -void SyncReplication::create_object_with_primary_key(const Table* table, ObjKey oid, Mixed value) -{ - if (table->is_embedded()) { - // Trying to create an object with a primary key in an embedded table. - unsupported_instruction(); - } - - Replication::create_object_with_primary_key(table, oid, value); - if (select_table(*table)) { - if (m_write_validator) { - m_write_validator(*table); - } - - auto col = table->get_primary_key_column(); - if (col && ((value.is_null() && col.is_nullable()) || DataType(col.get_type()) == value.get_type())) { - Instruction::CreateObject instr; - instr.table = m_last_class_name; - instr.object = as_primary_key(value); - emit(instr); - } - else { - // Trying to create object with primary key in table without a - // primary key column, or with wrong primary key type. - unsupported_instruction(); - } - } -} - - -void SyncReplication::erase_class(TableKey table_key, StringData table_name, size_t num_tables) -{ - Replication::erase_class(table_key, table_name, num_tables); - - bool is_class = m_transaction->table_is_public(table_key); - - if (is_class && !m_short_circuit) { - Instruction::EraseTable instr; - instr.table = emit_class_name(table_name); - emit(instr); - } - - m_last_table = nullptr; -} - -void SyncReplication::rename_class(TableKey, StringData) -{ - unsupported_instruction(); -} - -void SyncReplication::insert_column(const Table* table, ColKey col_key, DataType type, StringData name, - Table* target_table) -{ - Replication::insert_column(table, col_key, type, name, target_table); - using CollectionType = Instruction::CollectionType; - - if (select_table(*table)) { - Instruction::AddColumn instr; - instr.table = m_last_class_name; - instr.field = m_encoder.intern_string(name); - instr.nullable = col_key.is_nullable(); - instr.type = get_payload_type(type); - instr.key_type = Instruction::Payload::Type::Null; - - if (col_key.is_list()) { - instr.collection_type = CollectionType::List; - } - else if (col_key.is_dictionary()) { - instr.collection_type = CollectionType::Dictionary; - auto key_type = table->get_dictionary_key_type(col_key); - REALM_ASSERT(key_type == type_String); - instr.key_type = get_payload_type(key_type); - } - else if (col_key.is_set()) { - instr.collection_type = CollectionType::Set; - } - else { - REALM_ASSERT(!col_key.is_collection()); - instr.collection_type = CollectionType::Single; - } - - // Mixed columns are always nullable. - REALM_ASSERT(instr.type != Instruction::Payload::Type::Null || instr.nullable || - instr.collection_type == CollectionType::Dictionary); - - if (instr.type == Instruction::Payload::Type::Link && target_table) { - instr.link_target_table = emit_class_name(*target_table); - } - else { - instr.link_target_table = m_encoder.intern_string(""); - } - emit(instr); - } -} - -void SyncReplication::erase_column(const Table* table, ColKey col_ndx) -{ - Replication::erase_column(table, col_ndx); - - if (select_table(*table)) { - // Not allowed to remove PK/OID columns! - REALM_ASSERT(col_ndx != table->get_primary_key_column()); - Instruction::EraseColumn instr; - instr.table = m_last_class_name; - instr.field = m_encoder.intern_string(table->get_column_name(col_ndx)); - emit(instr); - } -} - -void SyncReplication::rename_column(const Table*, ColKey, StringData) -{ - unsupported_instruction(); -} - -void SyncReplication::list_set(const CollectionBase& list, size_t ndx, Mixed value) -{ - bool prior_is_unresolved = list.get_any(ndx).is_unresolved_link(); - - // If link is unresolved, it should not be communicated. - if (value.is_unresolved_link()) { - // ... but reported internally as a deletion if prior value was not unresolved - if (!prior_is_unresolved) - Replication::list_erase(list, ndx); - } - else { - if (prior_is_unresolved) { - Replication::list_insert(list, ndx, value, 0 /* prior size not used */); - } - else { - Replication::list_set(list, ndx, value); - } - } - - if (select_collection(list)) { - // If this is an embedded object then we need to emit and erase/insert instruction so that the old - // object gets cleared, otherwise you'll only see the Update ObjectValue instruction, which is idempotent, - // and that will lead to corrupted prior size for array operations inside the embedded object during - // changeset application. - auto needs_insert_erase_sequence = [&] { - if (value.is_type(type_Link)) { - return list.get_target_table()->is_embedded(); - } - else if (value.is_type(type_TypedLink)) { - return m_transaction->get_table(value.get_link().get_table_key())->is_embedded(); - } - return false; - }; - if (needs_insert_erase_sequence()) { - REALM_ASSERT(!list.is_null(ndx)); - Instruction::ArrayErase erase_instr; - populate_path_instr(erase_instr, list, static_cast(ndx)); - erase_instr.prior_size = uint32_t(list.size()); - emit(erase_instr); - - Instruction::ArrayInsert insert_instr; - populate_path_instr(insert_instr, list, static_cast(ndx)); - insert_instr.prior_size = erase_instr.prior_size - 1; - insert_instr.value = as_payload(list, value); - emit(insert_instr); - } - else { - Instruction::Update instr; - populate_path_instr(instr, list, uint32_t(ndx)); - REALM_ASSERT(instr.is_array_update()); - instr.value = as_payload(list, value); - instr.prior_size = uint32_t(list.size()); - emit(instr); - } - } -} - -void SyncReplication::list_insert(const CollectionBase& list, size_t ndx, Mixed value, size_t prior_size) -{ - // If link is unresolved, it should not be communicated. - if (!value.is_unresolved_link()) { - Replication::list_insert(list, ndx, value, prior_size); - } - - if (select_collection(list)) { - Instruction::ArrayInsert instr; - populate_path_instr(instr, list, uint32_t(ndx)); - instr.value = as_payload(list, value); - instr.prior_size = uint32_t(prior_size); - emit(instr); - } -} - -void SyncReplication::add_int(const Table* table, ColKey col, ObjKey ndx, int_fast64_t value) -{ - Replication::add_int(table, col, ndx, value); - - if (select_table(*table)) { - REALM_ASSERT(col != table->get_primary_key_column()); - - Instruction::AddInteger instr; - populate_path_instr(instr, *table, ndx, {col}); - instr.value = value; - emit(instr); - } -} - -void SyncReplication::set(const Table* table, ColKey col, ObjKey key, Mixed value, _impl::Instruction variant) -{ - Replication::set(table, col, key, value, variant); - - if (key.is_unresolved()) { - return; - } - - if (col == table->get_primary_key_column()) { - return; - } - - // If link is unresolved, it should not be communicated. - if (value.is_unresolved_link()) { - return; - } - - if (select_table(*table)) { - // Omit of Update(NULL, default=true) for embedded object / dictionary - // columns if the value is already NULL. This is a workaround for the - // fact that erase always wins for nested structures, but we don't want - // default values to win over later embedded object creation. - if (variant == _impl::instr_SetDefault && value.is_null()) { - if (col.get_type() == col_type_Link && table->get_object(key).is_null(col)) { - return; - } - if (col.is_dictionary() && table->get_object(key).is_null(col)) { - // Dictionary columns cannot currently be NULL, but this is - // likely to change. - return; - } - } - - Instruction::Update instr; - populate_path_instr(instr, *table, key, {col}); - instr.value = as_payload(*table, col, value); - instr.is_default = (variant == _impl::instr_SetDefault); - emit(instr); - } -} - - -void SyncReplication::remove_object(const Table* table, ObjKey row_ndx) -{ - Replication::remove_object(table, row_ndx); - if (table->is_embedded()) - return; - if (table->is_asymmetric()) - return; - REALM_ASSERT(!row_ndx.is_unresolved()); - - if (select_table(*table)) { - Instruction::EraseObject instr; - instr.table = m_last_class_name; - instr.object = primary_key_for_object(*table, row_ndx); - emit(instr); - } -} - - -void SyncReplication::list_move(const CollectionBase& view, size_t from_ndx, size_t to_ndx) -{ - Replication::list_move(view, from_ndx, to_ndx); - if (select_collection(view)) { - Instruction::ArrayMove instr; - populate_path_instr(instr, view, uint32_t(from_ndx)); - instr.ndx_2 = uint32_t(to_ndx); - instr.prior_size = uint32_t(view.size()); - emit(instr); - } -} - -void SyncReplication::list_erase(const CollectionBase& list, size_t ndx) -{ - Mixed prior_value = list.get_any(ndx); - // If link is unresolved, it should not be communicated. - if (!prior_value.is_unresolved_link()) { - Replication::list_erase(list, ndx); - } - - size_t prior_size = list.size(); - if (select_collection(list)) { - Instruction::ArrayErase instr; - populate_path_instr(instr, list, uint32_t(ndx)); - instr.prior_size = uint32_t(prior_size); - emit(instr); - } -} - -void SyncReplication::list_clear(const CollectionBase& view) -{ - Replication::list_clear(view); - if (select_collection(view)) { - Instruction::Clear instr; - populate_path_instr(instr, view); - instr.collection_type = Instruction::CollectionType::List; - emit(instr); - } -} - -void SyncReplication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value) -{ - Replication::set_insert(set, set_ndx, value); - - if (select_collection(set)) { - Instruction::SetInsert instr; - populate_path_instr(instr, set); - instr.value = as_payload(set, value); - emit(instr); - } -} - -void SyncReplication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value) -{ - Replication::set_erase(set, set_ndx, value); - - if (select_collection(set)) { - Instruction::SetErase instr; - populate_path_instr(instr, set); - instr.value = as_payload(set, value); - emit(instr); - } -} - -void SyncReplication::set_clear(const CollectionBase& set) -{ - Replication::set_clear(set); - - if (select_collection(set)) { - Instruction::Clear instr; - populate_path_instr(instr, set); - instr.collection_type = Instruction::CollectionType::Set; - emit(instr); - } -} - -void SyncReplication::dictionary_update(const CollectionBase& dict, const Mixed& key, const Mixed& value) -{ - // If link is unresolved, it should not be communicated. - if (value.is_unresolved_link()) { - return; - } - - if (select_collection(dict)) { - Instruction::Update instr; - REALM_ASSERT(key.get_type() == type_String); - populate_path_instr(instr, dict); - StringData key_value = key.get_string(); - instr.path.push_back(m_encoder.intern_string(key_value)); - instr.value = as_payload(dict, value); - instr.is_default = false; - emit(instr); - } -} - -void SyncReplication::dictionary_insert(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value) -{ - Replication::dictionary_insert(dict, ndx, key, value); - dictionary_update(dict, key, value); -} - -void SyncReplication::dictionary_set(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value) -{ - Replication::dictionary_set(dict, ndx, key, value); - dictionary_update(dict, key, value); -} - -void SyncReplication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key) -{ - Replication::dictionary_erase(dict, ndx, key); - - if (select_collection(dict)) { - Instruction::Update instr; - REALM_ASSERT(key.get_type() == type_String); - populate_path_instr(instr, dict); - StringData key_value = key.get_string(); - instr.path.push_back(m_encoder.intern_string(key_value)); - instr.value = Instruction::Payload::Erased{}; - instr.is_default = false; - emit(instr); - } -} - -void SyncReplication::dictionary_clear(const CollectionBase& dict) -{ - Replication::dictionary_clear(dict); - - if (select_collection(dict)) { - Instruction::Clear instr; - populate_path_instr(instr, dict); - instr.collection_type = Instruction::CollectionType::Dictionary; - emit(instr); - } -} - -void SyncReplication::nullify_link(const Table* table, ColKey col_key, ObjKey ndx) -{ - Replication::nullify_link(table, col_key, ndx); - - if (select_table(*table)) { - Instruction::Update instr; - populate_path_instr(instr, *table, ndx, {col_key}); - REALM_ASSERT(!instr.is_array_update()); - instr.value = Instruction::Payload{realm::util::none}; - instr.is_default = false; - emit(instr); - } -} - -void SyncReplication::link_list_nullify(const Lst& view, size_t ndx) -{ - size_t prior_size = view.size(); - Replication::link_list_nullify(view, ndx); - if (select_collection(view)) { - Instruction::ArrayErase instr; - populate_path_instr(instr, view, uint32_t(ndx)); - instr.prior_size = uint32_t(prior_size); - emit(instr); - } -} - -void SyncReplication::unsupported_instruction() const -{ - throw realm::sync::TransformError{"Unsupported instruction"}; -} - -bool SyncReplication::select_table(const Table& table) -{ - if (is_short_circuited()) { - return false; - } - - if (&table == m_last_table) { - return true; - } - - if (!m_transaction->table_is_public(table.get_key())) { - return false; - } - - m_last_class_name = emit_class_name(table); - m_last_table = &table; - m_last_field_name = StringData{}; - m_last_object = ObjKey{}; - m_last_primary_key.reset(); - return true; -} - -bool SyncReplication::select_collection(const CollectionBase& view) -{ - if (view.get_owner_key().is_unresolved()) { - return false; - } - - return select_table(*view.get_table()); -} - -Instruction::PrimaryKey SyncReplication::primary_key_for_object(const Table& table, ObjKey key) -{ - bool should_emit = select_table(table); - REALM_ASSERT(should_emit); - - if (table.get_primary_key_column()) { - return as_primary_key(table.get_primary_key(key)); - } - - GlobalKey global_key = table.get_object_id(key); - return global_key; -} - -void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const Table& table, ObjKey key, - Path path) -{ - REALM_ASSERT(key); - // The first path entry will be the column key - REALM_ASSERT(path[0].is_col_key()); - - if (table.is_embedded()) { - // For embedded objects, Obj::traverse_path() yields the top object - // first, then objects in the path in order. - auto obj = table.get_object(key); - auto full_path = obj.get_path(); - // Populate top object in the normal way. - auto top_table = table.get_parent_group()->get_table(full_path.top_table); - - full_path.path_from_top.emplace_back(table.get_column_name(path[0].get_col_key())); - - for (auto it = path.begin() + 1; it != path.end(); ++it) { - full_path.path_from_top.emplace_back(std::move(*it)); - } - populate_path_instr(instr, *top_table, full_path.top_objkey, full_path.path_from_top); - return; - } - - bool should_emit = select_table(table); - REALM_ASSERT(should_emit); - - instr.table = m_last_class_name; - if (m_last_object == key) { - instr.object = *m_last_primary_key; - } - else { - instr.object = primary_key_for_object(table, key); - m_last_object = key; - m_last_primary_key = instr.object; - } - - StringData field_name = table.get_column_name(path[0].get_col_key()); - - if (m_last_field_name == field_name) { - instr.field = m_last_interned_field_name; - } - else { - instr.field = m_encoder.intern_string(field_name); - m_last_field_name = field_name; - m_last_interned_field_name = instr.field; - } - size_t sz = path.size(); - instr.path.reserve(sz - 1); - for (size_t i = 1; i < sz; i++) { - auto& path_elem = path[i]; - if (path_elem.is_ndx()) { - instr.path.push_back(uint32_t(path_elem.get_ndx())); - } - else { - REALM_ASSERT(path_elem.is_key()); - InternString interned_field_name = m_encoder.intern_string(path_elem.get_key().c_str()); - instr.path.push_back(interned_field_name); - } - } -} - -void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& collection) -{ - ConstTableRef source_table = collection.get_table(); - ObjKey source_obj = collection.get_owner_key(); - populate_path_instr(instr, *source_table, source_obj, collection.get_short_path()); -} - -void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list, - uint32_t ndx) -{ - populate_path_instr(instr, list); - instr.path.push_back(ndx); -} - -} // namespace sync -} // namespace realm diff --git a/src/realm/sync/instruction_replication.hpp b/src/realm/sync/instruction_replication.hpp deleted file mode 100644 index ff7fbd62de4..00000000000 --- a/src/realm/sync/instruction_replication.hpp +++ /dev/null @@ -1,204 +0,0 @@ -/************************************************************************* - * - * Copyright 2017 Realm Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef REALM_SYNC_IMPL_INSTRUCTION_REPLICATION_HPP -#define REALM_SYNC_IMPL_INSTRUCTION_REPLICATION_HPP - -#include -#include -#include - -namespace realm { -namespace sync { - - -class SyncReplication : public Replication { -public: - // This will be called for any instruction that mutates an object (instead of instructions that mutates - // schema) with the class name (without the "class_" prefix) of the object being modified. If The - // validator needs to reject the write, it should throw an exception. - using WriteValidator = void(const Table&); - - void set_short_circuit(bool) noexcept; - bool is_short_circuited() const noexcept; - - // reset() resets the encoder, the selected tables and the cache. It is - // called by do_initiate_transact(), but can be called at the other times - // as well. - void reset(); - - ChangesetEncoder& get_instruction_encoder() noexcept; - const ChangesetEncoder& get_instruction_encoder() const noexcept; - - void add_class(TableKey tk, StringData table_name, Table::Type table_type = Table::Type::TopLevel) final; - void add_class_with_primary_key(TableKey tk, StringData table_name, DataType pk_type, StringData pk_field, - bool nullable, Table::Type table_type) final; - void create_object(const Table*, GlobalKey) final; - void create_object_with_primary_key(const Table*, ObjKey, Mixed) final; - - void erase_class(TableKey table_key, StringData table_name, size_t num_tables) final; - void rename_class(TableKey table_key, StringData new_name) final; - void insert_column(const Table*, ColKey col_key, DataType type, StringData name, Table* target_table) final; - void erase_column(const Table*, ColKey col_key) final; - void rename_column(const Table*, ColKey col_key, StringData name) final; - - void add_int(const Table*, ColKey col_key, ObjKey key, int_fast64_t value) final; - void set(const Table*, ColKey col_key, ObjKey key, Mixed value, _impl::Instruction variant) final; - - void list_set(const CollectionBase& list, size_t list_ndx, Mixed value) final; - void list_insert(const CollectionBase& list, size_t list_ndx, Mixed value, size_t prior_size) final; - void list_move(const CollectionBase&, size_t from_link_ndx, size_t to_link_ndx) final; - void list_erase(const CollectionBase&, size_t link_ndx) final; - void list_clear(const CollectionBase&) final; - - void set_insert(const CollectionBase& list, size_t list_ndx, Mixed value) final; - void set_erase(const CollectionBase& list, size_t list_ndx, Mixed value) final; - void set_clear(const CollectionBase& list) final; - - void dictionary_insert(const CollectionBase&, size_t ndx, Mixed key, Mixed val) final; - void dictionary_set(const CollectionBase&, size_t ndx, Mixed key, Mixed val) final; - void dictionary_erase(const CollectionBase&, size_t ndx, Mixed key) final; - void dictionary_clear(const CollectionBase& dict) final; - - void remove_object(const Table*, ObjKey) final; - - //@{ - - /// Implicit nullifications due to removal of target row. This is redundant - /// information from the point of view of replication, as the removal of the - /// target row will reproduce the implicit nullifications in the target - /// Realm anyway. The purpose of this instruction is to allow observers - /// (reactor pattern) to be explicitly notified about the implicit - /// nullifications. - - void nullify_link(const Table*, ColKey col_key, ObjKey key) final; - void link_list_nullify(const Lst&, size_t link_ndx) final; - //@} - -protected: - // Replication interface: - void do_initiate_transact(Group& group, version_type current_version, bool history_updated) override; - - virtual util::UniqueFunction make_write_validator(Transaction&) - { - return {}; - } - -private: - bool m_short_circuit = false; - - ChangesetEncoder m_encoder; - Transaction* m_transaction; - - template - void emit(T instruction); - - // Returns true and populates m_last_table_name if instructions for the - // table should be emitted. - bool select_table(const Table&); - - REALM_NORETURN void unsupported_instruction() const; // Throws TransformError - - // Returns true and populates m_last_class_name if instructions for the - // owning table should be emitted. - bool select_collection(const CollectionBase&); // returns true if table behavior != ignored - - InternString emit_class_name(StringData table_name); - InternString emit_class_name(const Table& table); - Instruction::Payload::Type get_payload_type(DataType) const; - - Instruction::Payload as_payload(Mixed value); - Instruction::Payload as_payload(const CollectionBase& collection, Mixed value); - Instruction::Payload as_payload(const Table& table, ColKey col_key, Mixed value); - - Instruction::PrimaryKey as_primary_key(Mixed); - Instruction::PrimaryKey primary_key_for_object(const Table&, ObjKey key); - void populate_path_instr(Instruction::PathInstruction&, const Table&, ObjKey key, Path path); - void populate_path_instr(Instruction::PathInstruction&, const CollectionBase&); - void populate_path_instr(Instruction::PathInstruction&, const CollectionBase&, uint32_t ndx); - - void dictionary_update(const CollectionBase&, const Mixed& key, const Mixed& val); - - // Cache information for the purpose of avoiding excessive string comparisons / interning - // lookups. - const Table* m_last_table = nullptr; - ObjKey m_last_object; - StringData m_last_field_name; - InternString m_last_class_name; - util::Optional m_last_primary_key; - InternString m_last_interned_field_name; - util::UniqueFunction m_write_validator; -}; - -inline void SyncReplication::set_short_circuit(bool b) noexcept -{ - m_short_circuit = b; -} - -inline bool SyncReplication::is_short_circuited() const noexcept -{ - return m_short_circuit; -} - -inline ChangesetEncoder& SyncReplication::get_instruction_encoder() noexcept -{ - return m_encoder; -} - -inline const ChangesetEncoder& SyncReplication::get_instruction_encoder() const noexcept -{ - return m_encoder; -} - -template -inline void SyncReplication::emit(T instruction) -{ - REALM_ASSERT(!m_short_circuit); - m_encoder(instruction); -} - - -// Temporarily short-circuit replication -class TempShortCircuitReplication { -public: - TempShortCircuitReplication(SyncReplication& bridge) - : m_bridge(bridge) - { - m_was_short_circuited = bridge.is_short_circuited(); - bridge.set_short_circuit(true); - } - - ~TempShortCircuitReplication() - { - m_bridge.set_short_circuit(m_was_short_circuited); - } - - bool was_short_circuited() const noexcept - { - return m_was_short_circuited; - } - -private: - SyncReplication& m_bridge; - bool m_was_short_circuited; -}; - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_IMPL_INSTRUCTION_REPLICATION_HPP diff --git a/src/realm/sync/instructions.cpp b/src/realm/sync/instructions.cpp deleted file mode 100644 index f79ce6c847a..00000000000 --- a/src/realm/sync/instructions.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - -using namespace realm; -using namespace realm::_impl; - -namespace realm { -namespace sync { - -const InternString InternString::npos = InternString{uint32_t(-1)}; - - -} // namespace sync -} // namespace realm diff --git a/src/realm/sync/instructions.hpp b/src/realm/sync/instructions.hpp deleted file mode 100644 index cc88815cee3..00000000000 --- a/src/realm/sync/instructions.hpp +++ /dev/null @@ -1,1177 +0,0 @@ - -#ifndef REALM_IMPL_INSTRUCTIONS_HPP -#define REALM_IMPL_INSTRUCTIONS_HPP - -#include // string conversion, debug prints -#include // shared_ptr -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace realm { - -namespace sync { - -#define REALM_FOR_EACH_INSTRUCTION_TYPE(X) \ - X(AddTable) \ - X(EraseTable) \ - X(AddColumn) \ - X(EraseColumn) \ - X(CreateObject) \ - X(EraseObject) \ - X(Update) \ - X(AddInteger) \ - X(ArrayInsert) \ - X(ArrayMove) \ - X(ArrayErase) \ - X(Clear) \ - X(SetInsert) \ - X(SetErase) - -struct StringBufferRange { - uint32_t offset, size; - - friend bool operator==(const StringBufferRange& lhs, const StringBufferRange& rhs) noexcept - { - return lhs.offset == rhs.offset && lhs.size == rhs.size; - } -}; - -struct InternString { - static const InternString npos; - explicit constexpr InternString(uint32_t v = uint32_t(-1)) noexcept - : value(v) - { - } - - uint32_t value; - - constexpr bool operator==(const InternString& other) const noexcept - { - return value == other.value; - } - constexpr bool operator!=(const InternString& other) const noexcept - { - return value != other.value; - } - constexpr bool operator<(const InternString& other) const noexcept - { - return value < other.value; - } - - explicit operator bool() const noexcept - { - return (value != npos.value); - } -}; - -struct Instruction; - -namespace instr { - -using PrimaryKey = mpark::variant; - -struct Path { - using Element = mpark::variant; - - size_t size() const noexcept - { - return m_path.size(); - } - - void reserve(size_t sz) - { - m_path.reserve(sz); - } - - // If this path is referring to an element of an array (the last path - // element is an integer index), return true. - bool is_array_index() const noexcept - { - return !m_path.empty() && mpark::holds_alternative(m_path.back()); - } - - uint32_t& index() noexcept - { - REALM_ASSERT(is_array_index()); - return mpark::get(m_path.back()); - } - - uint32_t index() const noexcept - { - REALM_ASSERT(is_array_index()); - return mpark::get(m_path.back()); - } - - Element& back() noexcept - { - REALM_ASSERT(!m_path.empty()); - return m_path.back(); - } - - const Element& back() const noexcept - { - REALM_ASSERT(!m_path.empty()); - return m_path.back(); - } - - Element& operator[](size_t idx) noexcept - { - REALM_ASSERT(idx < m_path.size()); - return m_path[idx]; - } - - const Element& operator[](size_t idx) const noexcept - { - REALM_ASSERT(idx < m_path.size()); - return m_path[idx]; - } - - void push_back(Element element) - { - m_path.push_back(element); - } - - void clear() - { - m_path.clear(); - } - - friend bool operator==(const Path& lhs, const Path& rhs) noexcept - { - return lhs.m_path == rhs.m_path; - } - - using const_iterator = typename std::vector::const_iterator; - const_iterator begin() const noexcept - { - return m_path.begin(); - } - const_iterator end() const noexcept - { - return m_path.end(); - } - -private: - // FIXME: Use a "small_vector" type for this -- most paths are very short. - // Alternatively, we could use some kind of interning with copy-on-write, - // but that seems complicated. - std::vector m_path; -}; - -struct Payload { - /// Create a new object in-place (embedded object). - struct ObjectValue {}; - /// Create an empty list in-place (does not clear an existing list). - struct List {}; - /// Create an empty dictionary in-place (does not clear an existing dictionary). - struct Dictionary {}; - /// Create an empty set in-place (does not clear an existing set). - struct Set {}; - /// Sentinel value for an erased dictionary element. - struct Erased {}; - - /// Payload data types, corresponding loosely to the `DataType` enum in - /// Core, but with some special values: - /// - /// - Null (0) indicates a NULL value of any type. - /// - GlobalKey (-1) indicates an internally generated object ID. - /// - ObjectValue (-2) indicates the creation of an embedded object. - /// - Dictionary (-3) indicates the creation of a dictionary. - /// - Erased (-4) indicates that a dictionary element should be erased. - /// - List (-5) indicates the creation of a list - /// - /// Furthermore, link values for both Link and LinkList columns are - /// represented by a single Link type. - /// - /// Note: For Mixed columns (including typed links), no separate value is required, because the - /// instruction set encodes the type of each value in the instruction. - enum class Type : int8_t { - // Special value indicating that a set should be created at the position. - Set = -6, - - // Special value indicating that a list should be created at the position. - List = -5, - - // Special value indicating that a dictionary element should be erased. - Erased = -4, - - // Special value indicating that a dictionary should be created at the position. - Dictionary = -3, - - // Special value indicating that an embedded object should be created at - // the position. - ObjectValue = -2, - GlobalKey = -1, - Null = 0, - Int = 1, - Bool = 2, - String = 3, - Binary = 4, - Timestamp = 5, - Float = 6, - Double = 7, - Decimal = 8, - Link = 9, - ObjectId = 10, - UUID = 11, - }; - - struct Link { - InternString target_table; - PrimaryKey target; - - friend bool operator==(const Link& lhs, const Link& rhs) noexcept - { - return lhs.target_table == rhs.target_table && lhs.target == rhs.target; - } - }; - - union Data { - GlobalKey key; - int64_t integer; - bool boolean; - StringBufferRange str; - StringBufferRange binary; - Timestamp timestamp; - float fnum; - double dnum; - Decimal128 decimal; - ObjectId object_id; - UUID uuid; - Link link; - ObjLink typed_link; - - Data() {} - }; - - Data data; - Type type; - - Payload() - : Payload(realm::util::none) - { - } - explicit Payload(bool value) noexcept - : type(Type::Bool) - { - data.boolean = value; - } - explicit Payload(int64_t value) noexcept - : type(Type::Int) - { - data.integer = value; - } - explicit Payload(float value) noexcept - : type(Type::Float) - { - data.fnum = value; - } - explicit Payload(double value) noexcept - : type(Type::Double) - { - data.dnum = value; - } - explicit Payload(Link value) noexcept - : type(Type::Link) - { - data.link = value; - } - explicit Payload(StringBufferRange value, bool is_binary = false) noexcept - : type(is_binary ? Type::Binary : Type::String) - { - if (is_binary) { - data.binary = value; - } - else { - data.str = value; - } - } - explicit Payload(realm::util::None) noexcept - : type(Type::Null) - { - } - - // Note: Intentionally implicit. - Payload(const ObjectValue&) noexcept - : type(Type::ObjectValue) - { - } - - // Note: Intentionally implicit. - Payload(const Erased&) noexcept - : type(Type::Erased) - { - } - Payload(const Dictionary&) noexcept - : type(Type::Dictionary) - { - } - Payload(const List&) noexcept - : type(Type::List) - { - } - Payload(const Set&) noexcept - : type(Type::Set) - { - } - - explicit Payload(Timestamp value) noexcept - : type(value.is_null() ? Type::Null : Type::Timestamp) - { - if (value.is_null()) { - type = Type::Null; - } - else { - type = Type::Timestamp; - data.timestamp = value; - } - } - - explicit Payload(ObjectId value) noexcept - : type(Type::ObjectId) - { - data.object_id = value; - } - - explicit Payload(Decimal128 value) noexcept - { - if (value.is_null()) { - type = Type::Null; - } - else { - type = Type::Decimal; - data.decimal = value; - } - } - - explicit Payload(UUID value) noexcept - : type(Type::UUID) - { - data.uuid = value; - } - - Payload(const Payload&) noexcept = default; - Payload& operator=(const Payload&) noexcept = default; - - bool is_null() const noexcept - { - return type == Type::Null; - } - - friend bool operator==(const Payload& lhs, const Payload& rhs) noexcept - { - if (lhs.type == rhs.type) { - switch (lhs.type) { - case Type::Null: - case Type::Erased: - case Type::List: - case Type::Set: - case Type::Dictionary: - case Type::ObjectValue: - return true; - case Type::GlobalKey: - return lhs.data.key == rhs.data.key; - case Type::Int: - return lhs.data.integer == rhs.data.integer; - case Type::Bool: - return lhs.data.boolean == rhs.data.boolean; - case Type::String: - return lhs.data.str == rhs.data.str; - case Type::Binary: - return lhs.data.binary == rhs.data.binary; - case Type::Timestamp: - return lhs.data.timestamp == rhs.data.timestamp; - case Type::Float: - return lhs.data.fnum == rhs.data.fnum; - case Type::Double: - return lhs.data.dnum == rhs.data.dnum; - case Type::Decimal: - return lhs.data.decimal == rhs.data.decimal; - case Type::Link: - return lhs.data.link == rhs.data.link; - case Type::ObjectId: - return lhs.data.object_id == rhs.data.object_id; - case Type::UUID: - return lhs.data.uuid == rhs.data.uuid; - } - } - return false; - } - - friend bool operator!=(const Payload& lhs, const Payload& rhs) noexcept - { - return !(lhs == rhs); - } -}; - -// This is backwards compatible with previous boolean type where 0 -// indicated simple type and 1 indicated list. -enum class CollectionType : uint8_t { Single, List, Dictionary, Set }; - -/// All instructions are TableInstructions. -struct TableInstruction { - InternString table; - -protected: - bool operator==(const TableInstruction& rhs) const noexcept - { - return table == rhs.table; - } -}; - -/// All instructions except schema instructions are ObjectInstructions. -struct ObjectInstruction : TableInstruction { - PrimaryKey object; - -protected: - bool operator==(const ObjectInstruction& rhs) const noexcept - { - return TableInstruction::operator==(rhs) && object == rhs.object; - } -}; - -/// All instructions except schema instructions and CreateObject/EraseObject are PathInstructions. -struct PathInstruction : ObjectInstruction { - InternString field; - Path path; - - uint32_t& index() noexcept - { - return path.index(); - } - - uint32_t index() const noexcept - { - return path.index(); - } - -protected: - bool operator==(const PathInstruction& rhs) const noexcept - { - return ObjectInstruction::operator==(rhs) && field == rhs.field && path == rhs.path; - } -}; - -struct AddTable : TableInstruction { - // Note: Tables "without" a primary key have a secret primary key of type - // ObjKey. The field name of such primary keys is assumed to be "_id". - struct TopLevelTable { - InternString pk_field; - Payload::Type pk_type; - bool pk_nullable; - bool is_asymmetric; - - bool operator==(const TopLevelTable& rhs) const noexcept - { - return pk_field == rhs.pk_field && pk_type == rhs.pk_type && pk_nullable == rhs.pk_nullable && - is_asymmetric == rhs.is_asymmetric; - } - }; - - struct EmbeddedTable { - bool operator==(const EmbeddedTable&) const noexcept - { - return true; - } - }; - - mpark::variant type; - - bool operator==(const AddTable& rhs) const noexcept - { - return TableInstruction::operator==(rhs) && type == rhs.type; - } -}; - -struct EraseTable : TableInstruction { - using TableInstruction::TableInstruction; - - bool operator==(const EraseTable& rhs) const noexcept - { - return TableInstruction::operator==(rhs); - } -}; - -struct AddColumn : TableInstruction { - using TableInstruction::TableInstruction; - - InternString field; - - // `Type::Null` for Mixed columns. Mixed columns are always nullable. - Payload::Type type; - // `Type::Null` for other than dictionary columns - Payload::Type key_type; - - bool nullable; - - // For Mixed columns, this is `none`. Mixed columns are always nullable. - // - // For dictionaries, this must always be `Type::String`. - CollectionType collection_type; - - InternString link_target_table; - - bool operator==(const AddColumn& rhs) const noexcept - { - return TableInstruction::operator==(rhs) && field == rhs.field && type == rhs.type && - key_type == rhs.key_type && nullable == rhs.nullable && collection_type == rhs.collection_type && - link_target_table == rhs.link_target_table; - } -}; - -struct EraseColumn : TableInstruction { - using TableInstruction::TableInstruction; - InternString field; - - bool operator==(const EraseColumn& rhs) const noexcept - { - return TableInstruction::operator==(rhs) && field == rhs.field; - } -}; - -struct CreateObject : ObjectInstruction { - using ObjectInstruction::ObjectInstruction; - - bool operator==(const CreateObject& rhs) const noexcept - { - return ObjectInstruction::operator==(rhs); - } -}; - -struct EraseObject : ObjectInstruction { - using ObjectInstruction::ObjectInstruction; - - bool operator==(const EraseObject& rhs) const noexcept - { - return ObjectInstruction::operator==(rhs); - } -}; - -struct Update : PathInstruction { - using PathInstruction::PathInstruction; - - // Note: For "ArrayUpdate", the path ends with an integer. - Payload value; - union { - bool is_default; // For fields - uint32_t prior_size; // For "ArrayUpdate" - }; - - Update() - : prior_size(0) - { - } - - bool is_array_update() const noexcept - { - return path.is_array_index(); - } - - bool operator==(const Update& rhs) const noexcept - { - return PathInstruction::operator==(rhs) && value == rhs.value && - (is_array_update() ? prior_size == rhs.prior_size : is_default == rhs.is_default); - } -}; - -struct AddInteger : PathInstruction { - using PathInstruction::PathInstruction; - int64_t value; - - bool operator==(const AddInteger& rhs) const noexcept - { - return PathInstruction::operator==(rhs) && value == rhs.value; - } -}; - -struct ArrayInsert : PathInstruction { - // Note: The insertion index is the last path component. - using PathInstruction::PathInstruction; - Payload value; - uint32_t prior_size; - - bool operator==(const ArrayInsert& rhs) const noexcept - { - return PathInstruction::operator==(rhs) && value == rhs.value && prior_size == rhs.prior_size; - } -}; - -struct ArrayMove : PathInstruction { - // Note: The move-from index is the last path component. - using PathInstruction::PathInstruction; - uint32_t ndx_2; - uint32_t prior_size; - - bool operator==(const ArrayMove& rhs) const noexcept - { - return PathInstruction::operator==(rhs) && ndx_2 == rhs.ndx_2 && prior_size == rhs.prior_size; - } -}; - -struct ArrayErase : PathInstruction { - // Note: The erased index is the last path component. - using PathInstruction::PathInstruction; - uint32_t prior_size; - - bool operator==(const ArrayErase& rhs) const noexcept - { - return PathInstruction::operator==(rhs) && prior_size == rhs.prior_size; - } -}; - -struct Clear : PathInstruction { - using PathInstruction::PathInstruction; - CollectionType collection_type; - - bool operator==(const Clear& rhs) const noexcept - { - return PathInstruction::operator==(rhs); - } -}; - -struct SetInsert : PathInstruction { - using PathInstruction::PathInstruction; - Payload value; - - bool operator==(const SetInsert& rhs) const noexcept - { - return PathInstruction::operator==(rhs) && value == rhs.value; - } -}; - -struct SetErase : PathInstruction { - using PathInstruction::PathInstruction; - Payload value; - - bool operator==(const SetErase& rhs) const noexcept - { - return PathInstruction::operator==(rhs) && value == rhs.value; - } -}; - - -} // namespace instr - -struct Instruction { -#define REALM_DECLARE_INSTRUCTION_STRUCT(X) using X = instr::X; - REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DECLARE_INSTRUCTION_STRUCT) -#undef REALM_DECLARE_INSTRUCTION_STRUCT - - using TableInstruction = instr::TableInstruction; - using ObjectInstruction = instr::ObjectInstruction; - using PathInstruction = instr::PathInstruction; - using PrimaryKey = instr::PrimaryKey; - using Payload = instr::Payload; - using Path = instr::Path; - using Vector = std::vector; - using CollectionType = instr::CollectionType; - - // CAUTION: Any change to the enum values for the instruction types is a protocol-breaking - // change! - enum class Type : uint8_t { - AddTable = 0, - EraseTable = 1, - CreateObject = 2, - EraseObject = 3, - Update = 4, // Note: Also covers ArrayUpdate - AddInteger = 5, - AddColumn = 6, - EraseColumn = 7, - ArrayInsert = 8, - ArrayMove = 9, - ArrayErase = 10, - Clear = 11, - SetInsert = 12, - SetErase = 13, - }; - - template - struct GetType; - template - struct GetInstructionType; - - template - Instruction(T instr); - - mpark::variant - m_instr; - - Type type() const noexcept; - - template - decltype(auto) visit(F&& lambda); - template - decltype(auto) visit(F&& lambda) const; - - template - T* get_if() noexcept; - - template - const T* get_if() const noexcept - { - return const_cast(*this).get_if(); - } - - template - T& get_as() - { - auto ptr = get_if(); - REALM_ASSERT(ptr); - return *ptr; - } - - template - const T& get_as() const - { - auto ptr = get_if(); - REALM_ASSERT(ptr); - return *ptr; - } - - bool operator==(const Instruction& other) const noexcept; - bool operator!=(const Instruction& other) const noexcept - { - return !(*this == other); - } - - bool is_vector() const noexcept - { - return mpark::holds_alternative(m_instr); - } - - size_t path_length() const noexcept; - - Vector& convert_to_vector(); - void insert(size_t pos, Instruction instr); - void erase(size_t pos); - size_t size() const noexcept; - bool is_empty() const noexcept; - Instruction& at(size_t) noexcept; - const Instruction& at(size_t) const noexcept; - -private: - template - static decltype(auto) visit(F&& lambda, V&& instr); -}; - -inline const char* get_type_name(Instruction::Type type) -{ - switch (type) { -#define REALM_INSTRUCTION_TYPE_TO_STRING(X) \ - case Instruction::Type::X: \ - return #X; - REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_INSTRUCTION_TYPE_TO_STRING) -#undef REALM_INSTRUCTION_TYPE_TO_STRING - } - return "(invalid)"; -} - -inline std::ostream& operator<<(std::ostream& os, Instruction::Type type) -{ - return os << get_type_name(type); -} - -inline const char* get_type_name(Instruction::Payload::Type type) -{ - using Type = Instruction::Payload::Type; - switch (type) { - case Type::Erased: - return "Erased"; - case Type::Set: - return "Set"; - case Type::List: - return "List"; - case Type::Dictionary: - return "Dictionary"; - case Type::ObjectValue: - return "ObjectValue"; - case Type::GlobalKey: - return "GlobalKey"; - case Type::Null: - return "Null"; - case Type::Int: - return "Int"; - case Type::Bool: - return "Bool"; - case Type::String: - return "String"; - case Type::Binary: - return "Binary"; - case Type::Timestamp: - return "Timestamp"; - case Type::Float: - return "Float"; - case Type::Double: - return "Double"; - case Type::Decimal: - return "Decimal"; - case Type::Link: - return "Link"; - case Type::ObjectId: - return "ObjectId"; - case Type::UUID: - return "UUID"; - } - return "(unknown)"; -} - -inline const char* get_collection_type(Instruction::CollectionType type) -{ - using Type = Instruction::CollectionType; - switch (type) { - case Type::Single: - return "Single"; - case Type::List: - return "List"; - case Type::Dictionary: - return "Dictionary"; - case Type::Set: - return "Set"; - } - return "(unknown)"; -} - -inline const char* get_type_name(util::Optional type) -{ - if (type) { - return get_type_name(*type); - } - else { - return "Mixed"; - } -} - -inline std::ostream& operator<<(std::ostream& os, Instruction::Payload::Type type) -{ - return os << get_type_name(type); -} - -inline bool is_valid_key_type(Instruction::Payload::Type type) noexcept -{ - using Type = Instruction::Payload::Type; - switch (type) { - case Type::Int: - [[fallthrough]]; - case Type::String: - [[fallthrough]]; - case Type::ObjectId: - [[fallthrough]]; - case Type::UUID: - [[fallthrough]]; - case Type::GlobalKey: - return true; - case Type::Null: // Mixed is not a valid primary key - [[fallthrough]]; - default: - return false; - } -} - -inline DataType get_data_type(Instruction::Payload::Type type) noexcept -{ - using Type = Instruction::Payload::Type; - switch (type) { - case Type::Int: - return type_Int; - case Type::Bool: - return type_Bool; - case Type::String: - return type_String; - case Type::Binary: - return type_Binary; - case Type::Timestamp: - return type_Timestamp; - case Type::Float: - return type_Float; - case Type::Double: - return type_Double; - case Type::Decimal: - return type_Decimal; - case Type::Link: - return type_Link; - case Type::ObjectId: - return type_ObjectId; - case Type::UUID: - return type_UUID; - case Type::Null: // Mixed is encoded as null - return type_Mixed; - case Type::Erased: - [[fallthrough]]; - case Type::Dictionary: - [[fallthrough]]; - case Type::List: - [[fallthrough]]; - case Type::Set: - [[fallthrough]]; - case Type::ObjectValue: - [[fallthrough]]; - case Type::GlobalKey: - REALM_TERMINATE(util::format("Invalid data type: %1", int8_t(type)).c_str()); - } - return type_Int; // Make compiler happy -} - -// 0x3f is the largest value that fits in a single byte in the variable-length -// encoded integer instruction format. -static constexpr uint8_t InstrTypeInternString = 0x3f; - -// This instruction code is only ever used internally by the Changeset class -// to allow insertion/removal while keeping iterators stable. Should never -// make it onto the wire. -static constexpr uint8_t InstrTypeMultiInstruction = 0xff; - -struct InstructionHandler { - /// Notify the handler that an InternString meta-instruction was found. - virtual void set_intern_string(uint32_t index, StringBufferRange) = 0; - - /// Notify the handler of the string value. The handler guarantees that the - /// returned string range is valid at least until the next invocation of - /// add_string_range(). - /// - /// Instances of `StringBufferRange` passed to operator() after invoking - /// this function are assumed to refer to ranges in this buffer. - virtual StringBufferRange add_string_range(StringData) = 0; - - /// Handle an instruction. - virtual void operator()(const Instruction&) = 0; -}; - - -/// Implementation: - -#define REALM_DEFINE_INSTRUCTION_GET_TYPE(X) \ - template <> \ - struct Instruction::GetType { \ - using Type = Instruction::X; \ - }; \ - template <> \ - struct Instruction::GetInstructionType { \ - static const Instruction::Type value = Instruction::Type::X; \ - }; -REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_INSTRUCTION_GET_TYPE) -#undef REALM_DEFINE_INSTRUCTION_GET_TYPE - -template -Instruction::Instruction(T instr) - : m_instr(std::move(instr)) -{ - static_assert(!std::is_same_v); -} - -template -inline decltype(auto) Instruction::visit(F&& lambda, V&& instr) -{ - // Cannot use std::visit, because it does not pass lvalue references to the visitor. - if (mpark::holds_alternative(instr)) { - REALM_TERMINATE("visiting instruction vector"); - } -#define REALM_VISIT_VARIANT(X) \ - else if (auto ptr = mpark::get_if(&instr)) \ - { \ - return lambda(*ptr); \ - } - REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_VARIANT) -#undef REALM_VISIT_VARIANT - else - { - REALM_TERMINATE("Unhandled instruction variant entry"); - } -} - -template -inline decltype(auto) Instruction::visit(F&& lambda) -{ - return visit(std::forward(lambda), m_instr); -} - -template -inline decltype(auto) Instruction::visit(F&& lambda) const -{ - return visit(std::forward(lambda), m_instr); -} - -inline Instruction::Type Instruction::type() const noexcept -{ - return visit([](auto&& instr) { - using T = std::remove_cv_t>; - return GetInstructionType::value; - }); -} - -inline bool Instruction::operator==(const Instruction& other) const noexcept -{ - return m_instr == other.m_instr; -} - -template -REALM_NOINLINE T* Instruction::get_if() noexcept -{ - // FIXME: Is there a way to express this without giant switch statements? Note: Putting the - // base class into a union does not seem to be allowed by the standard. - if constexpr (std::is_same_v) { - // This should compile to nothing but a comparison of the type. - return visit([](auto& instr) -> TableInstruction* { - return &instr; - }); - } - else if constexpr (std::is_same_v) { - // This should compile to nothing but a comparison of the type. - return visit(util::overload{ - [](AddTable&) -> ObjectInstruction* { - return nullptr; - }, - [](EraseTable&) -> ObjectInstruction* { - return nullptr; - }, - [](AddColumn&) -> ObjectInstruction* { - return nullptr; - }, - [](EraseColumn&) -> ObjectInstruction* { - return nullptr; - }, - [](auto& instr) -> ObjectInstruction* { - return &instr; - }, - }); - } - else if constexpr (std::is_same_v) { - // This should compile to nothing but a comparison of the type. - return visit(util::overload{ - [](AddTable&) -> PathInstruction* { - return nullptr; - }, - [](EraseTable&) -> PathInstruction* { - return nullptr; - }, - [](AddColumn&) -> PathInstruction* { - return nullptr; - }, - [](EraseColumn&) -> PathInstruction* { - return nullptr; - }, - [](CreateObject&) -> PathInstruction* { - return nullptr; - }, - [](EraseObject&) -> PathInstruction* { - return nullptr; - }, - [](auto& instr) -> PathInstruction* { - return &instr; - }, - }); - } - else { - return mpark::get_if(&m_instr); - } -} - -inline size_t Instruction::size() const noexcept -{ - if (auto vec = mpark::get_if(&m_instr)) { - return vec->size(); - } - return 1; -} - -inline bool Instruction::is_empty() const noexcept -{ - return size() == 0; -} - -inline Instruction& Instruction::at(size_t idx) noexcept -{ - if (auto vec = mpark::get_if(&m_instr)) { - REALM_ASSERT(idx < vec->size()); - return (*vec)[idx]; - } - REALM_ASSERT(idx == 0); - return *this; -} - -inline const Instruction& Instruction::at(size_t idx) const noexcept -{ - if (auto vec = mpark::get_if(&m_instr)) { - REALM_ASSERT(idx < vec->size()); - return (*vec)[idx]; - } - REALM_ASSERT(idx == 0); - return *this; -} - -inline size_t Instruction::path_length() const noexcept -{ - // Find the path length of the instruction. This affects how OT decides - // which instructions are potentially nesting. - // - // AddTable/EraseTable: Length 1 - // AddColumn/EraseColumn: Length 2 (table, field) - // Object instructions: Length 2 (table, object) - // Path instructions: Length 3 + m_path.size (table, object, field, path...) - if (auto path_instr = get_if()) { - return 3 + path_instr->path.size(); - } - if (get_if()) { - return 2; - } - switch (type()) { - case Instruction::Type::AddColumn: - [[fallthrough]]; - case Instruction::Type::EraseColumn: { - return 2; - } - case Instruction::Type::AddTable: - [[fallthrough]]; - case Instruction::Type::EraseTable: { - return 1; - } - default: - REALM_TERMINATE("Unhandled instruction type in Instruction::path_len()"); - } -} - -inline Instruction::Vector& Instruction::convert_to_vector() -{ - if (auto v = mpark::get_if(&m_instr)) { - return *v; - } - else { - Vector vec; - vec.emplace_back(std::move(*this)); - m_instr = std::move(vec); - return mpark::get(m_instr); - } -} - -inline void Instruction::insert(size_t idx, Instruction instr) -{ - auto& vec = convert_to_vector(); - REALM_ASSERT(idx <= vec.size()); - vec.emplace(vec.begin() + idx, std::move(instr)); -} - -inline void Instruction::erase(size_t idx) -{ - auto& vec = convert_to_vector(); - REALM_ASSERT(idx < vec.size()); - vec.erase(vec.begin() + idx); -} - -} // namespace sync -} // namespace realm - -#endif // REALM_IMPL_INSTRUCTIONS_HPP diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp deleted file mode 100644 index c2b69d64d25..00000000000 --- a/src/realm/sync/network/default_socket.cpp +++ /dev/null @@ -1,646 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace realm::sync::websocket { - -namespace { - -/// -/// DefaultWebSocketImpl - websocket implementation for the default socket provider -/// -class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { -public: - DefaultWebSocketImpl(const std::shared_ptr& logger_ptr, network::Service& service, - std::mt19937_64& random, const std::string user_agent, - std::unique_ptr observer, WebSocketEndpoint&& endpoint) - : m_logger_ptr{logger_ptr} - , m_network_logger{*m_logger_ptr} - , m_random{random} - , m_service{service} - , m_user_agent{user_agent} - , m_observer{std::move(observer)} - , m_endpoint{std::move(endpoint)} - , m_websocket(*this) - { - initiate_resolve(); - } - - virtual ~DefaultWebSocketImpl() = default; - - void async_write_binary(util::Span data, SyncSocketProvider::FunctionHandler&& handler) override - { - m_websocket.async_write_binary(data.data(), data.size(), - [write_handler = std::move(handler)](std::error_code ec, size_t) { - write_handler(DefaultWebSocketImpl::get_status_from_util_error(ec)); - }); - } - - std::string_view get_appservices_request_id() const noexcept override - { - return m_app_services_coid; - } - - void force_handshake_response_for_testing(int status_code, std::string body = "") override - { - m_websocket.force_handshake_response_for_testing(status_code, body); - } - - // public for HTTPClient CRTP, but not on the EZSocket interface, so de-facto private - void async_read(char*, std::size_t, ReadCompletionHandler) override; - void async_read_until(char*, std::size_t, char, ReadCompletionHandler) override; - void async_write(const char*, std::size_t, WriteCompletionHandler) override; - -private: - using milliseconds_type = std::int_fast64_t; - - const std::shared_ptr& websocket_get_logger() noexcept override - { - return m_logger_ptr; - } - std::mt19937_64& websocket_get_random() noexcept override - { - return m_random; - } - - void websocket_handshake_completion_handler(const HTTPHeaders& headers) override - { - const std::string empty; - if (auto it = headers.find("X-Appservices-Request-Id"); it != headers.end()) { - m_app_services_coid = it->second; - } - auto it = headers.find("Sec-WebSocket-Protocol"); - m_observer->websocket_connected_handler(it == headers.end() ? empty : it->second); - } - void websocket_read_error_handler(std::error_code ec) override - { - m_network_logger.error("Reading failed: %1", ec.message()); // Throws - constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, WebSocketError::websocket_read_error, ec.message()); - } - void websocket_write_error_handler(std::error_code ec) override - { - m_network_logger.error("Writing failed: %1", ec.message()); // Throws - constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, WebSocketError::websocket_write_error, ec.message()); - } - void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, std::string_view body) override - { - WebSocketError error = WebSocketError::websocket_ok; - bool was_clean = true; - - if (ec == websocket::HttpError::bad_response_301_moved_permanently || - ec == websocket::HttpError::bad_response_308_permanent_redirect) { - error = WebSocketError::websocket_moved_permanently; - } - else if (ec == websocket::HttpError::bad_response_3xx_redirection) { - error = WebSocketError::websocket_retry_error; - was_clean = false; - } - else if (ec == websocket::HttpError::bad_response_401_unauthorized) { - error = WebSocketError::websocket_unauthorized; - } - else if (ec == websocket::HttpError::bad_response_403_forbidden) { - error = WebSocketError::websocket_forbidden; - } - else if (ec == websocket::HttpError::bad_response_5xx_server_error || - ec == websocket::HttpError::bad_response_500_internal_server_error || - ec == websocket::HttpError::bad_response_502_bad_gateway || - ec == websocket::HttpError::bad_response_503_service_unavailable || - ec == websocket::HttpError::bad_response_504_gateway_timeout) { - error = WebSocketError::websocket_internal_server_error; - was_clean = false; - } - else { - error = WebSocketError::websocket_fatal_error; - was_clean = false; - } - - websocket_error_and_close_handler(was_clean, error, body.empty() ? ec.message() : body); - } - void websocket_protocol_error_handler(std::error_code ec) override - { - constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, WebSocketError::websocket_protocol_error, ec.message()); - } - bool websocket_close_message_received(WebSocketError code, std::string_view message) override - { - constexpr bool was_clean = true; - - return websocket_error_and_close_handler(was_clean, code, message); - } - bool websocket_error_and_close_handler(bool was_clean, WebSocketError code, std::string_view reason) - { - if (!was_clean) { - m_observer->websocket_error_handler(); - } - return m_observer->websocket_closed_handler(was_clean, code, reason); - } - bool websocket_binary_message_received(const char* ptr, std::size_t size) override - { - return m_observer->websocket_binary_message_received(util::Span(ptr, size)); - } - - static Status get_status_from_util_error(std::error_code ec) - { - if (!ec) { - return Status::OK(); - } - switch (ec.value()) { - case util::error::operation_aborted: - return {ErrorCodes::Error::OperationAborted, "Write operation cancelled"}; - case util::error::address_family_not_supported: - [[fallthrough]]; - case util::error::invalid_argument: - return {ErrorCodes::Error::InvalidArgument, ec.message()}; - case util::error::no_memory: - return {ErrorCodes::Error::OutOfMemory, ec.message()}; - case util::error::connection_aborted: - [[fallthrough]]; - case util::error::connection_reset: - [[fallthrough]]; - case util::error::broken_pipe: - [[fallthrough]]; - case util::error::resource_unavailable_try_again: - return {ErrorCodes::Error::ConnectionClosed, ec.message()}; - default: - return {ErrorCodes::Error::UnknownError, ec.message()}; - } - } - - void initiate_resolve(); - void handle_resolve(std::error_code, network::Endpoint::List); - void initiate_tcp_connect(network::Endpoint::List, std::size_t); - void handle_tcp_connect(std::error_code, network::Endpoint::List, std::size_t); - void initiate_http_tunnel(); - void initiate_websocket_or_ssl_handshake(); - void initiate_ssl_handshake(); - void handle_ssl_handshake(std::error_code); - void initiate_websocket_handshake(); - void handle_connection_established(); - - void schedule_urgent_ping(); - void initiate_ping_delay(milliseconds_type now); - void handle_ping_delay(); - void initiate_pong_timeout(); - void handle_pong_timeout(); - - const std::shared_ptr m_logger_ptr; - util::Logger& m_network_logger; - std::mt19937_64& m_random; - network::Service& m_service; - const std::string m_user_agent; - std::string m_app_services_coid; - - std::unique_ptr m_observer; - - const WebSocketEndpoint m_endpoint; - util::Optional m_resolver; - util::Optional m_socket; - util::Optional m_ssl_context; - util::Optional m_ssl_stream; - network::ReadAheadBuffer m_read_ahead_buffer; - websocket::Socket m_websocket; - util::Optional> m_proxy_client; -}; - -void DefaultWebSocketImpl::async_read(char* buffer, std::size_t size, ReadCompletionHandler handler) -{ - REALM_ASSERT(m_socket); - if (m_ssl_stream) { - m_ssl_stream->async_read(buffer, size, m_read_ahead_buffer, std::move(handler)); // Throws - } - else { - m_socket->async_read(buffer, size, m_read_ahead_buffer, std::move(handler)); // Throws - } -} - - -void DefaultWebSocketImpl::async_read_until(char* buffer, std::size_t size, char delim, ReadCompletionHandler handler) -{ - REALM_ASSERT(m_socket); - if (m_ssl_stream) { - m_ssl_stream->async_read_until(buffer, size, delim, m_read_ahead_buffer, std::move(handler)); // Throws - } - else { - m_socket->async_read_until(buffer, size, delim, m_read_ahead_buffer, std::move(handler)); // Throws - } -} - - -void DefaultWebSocketImpl::async_write(const char* data, std::size_t size, WriteCompletionHandler handler) -{ - REALM_ASSERT(m_socket); - if (m_ssl_stream) { - m_ssl_stream->async_write(data, size, std::move(handler)); // Throws - } - else { - m_socket->async_write(data, size, std::move(handler)); // Throws - } -} - - -void DefaultWebSocketImpl::initiate_resolve() -{ - const std::string& address = m_endpoint.proxy ? m_endpoint.proxy->address : m_endpoint.address; - const port_type& port = m_endpoint.proxy ? m_endpoint.proxy->port : m_endpoint.port; - - if (m_endpoint.proxy) { - // logger.detail("Using %1 proxy", proxy->type); // Throws - } - - m_network_logger.detail("Resolving '%1:%2'", address, port); // Throws - - network::Resolver::Query query(address, util::to_string(port)); // Throws - auto handler = [this](std::error_code ec, network::Endpoint::List endpoints) { - // If the operation is aborted, the connection object may have been - // destroyed. - if (ec != util::error::operation_aborted) - handle_resolve(ec, std::move(endpoints)); // Throws - }; - m_resolver.emplace(m_service); // Throws - m_resolver->async_resolve(std::move(query), std::move(handler)); // Throws -} - - -void DefaultWebSocketImpl::handle_resolve(std::error_code ec, network::Endpoint::List endpoints) -{ - if (ec) { - m_network_logger.error("Failed to resolve '%1:%2': %3", m_endpoint.address, m_endpoint.port, - ec.message()); // Throws - constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, WebSocketError::websocket_resolve_failed, - ec.message()); // Throws - return; - } - - initiate_tcp_connect(std::move(endpoints), 0); // Throws -} - - -void DefaultWebSocketImpl::initiate_tcp_connect(network::Endpoint::List endpoints, std::size_t i) -{ - REALM_ASSERT(i < endpoints.size()); - - network::Endpoint ep = *(endpoints.begin() + i); - std::size_t n = endpoints.size(); - m_socket.emplace(m_service); // Throws - m_socket->async_connect(ep, [this, endpoints = std::move(endpoints), i](std::error_code ec) mutable { - // If the operation is aborted, the connection object may have been - // destroyed. - if (ec != util::error::operation_aborted) - handle_tcp_connect(ec, std::move(endpoints), i); // Throws - }); - m_network_logger.detail("Connecting to endpoint '%1:%2' (%3/%4)", ep.address(), ep.port(), (i + 1), n); // Throws -} - -void DefaultWebSocketImpl::handle_tcp_connect(std::error_code ec, network::Endpoint::List endpoints, std::size_t i) -{ - REALM_ASSERT(i < endpoints.size()); - const network::Endpoint& ep = *(endpoints.begin() + i); - if (ec) { - m_network_logger.error("Failed to connect to endpoint '%1:%2': %3", ep.address(), ep.port(), - ec.message()); // Throws - std::size_t i_2 = i + 1; - if (i_2 < endpoints.size()) { - initiate_tcp_connect(std::move(endpoints), i_2); // Throws - return; - } - // All endpoints failed - m_network_logger.error("Failed to connect to '%1:%2': All endpoints failed", m_endpoint.address, - m_endpoint.port); - constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, - ec.message()); // Throws - return; - } - - REALM_ASSERT(m_socket); - network::Endpoint ep_2 = m_socket->local_endpoint(); - m_network_logger.info("Connected to endpoint '%1:%2' (from '%3:%4')", ep.address(), ep.port(), ep_2.address(), - ep_2.port()); // Throws - - // TODO: Handle HTTPS proxies - if (m_endpoint.proxy) { - initiate_http_tunnel(); // Throws - return; - } - - initiate_websocket_or_ssl_handshake(); // Throws -} - -void DefaultWebSocketImpl::initiate_websocket_or_ssl_handshake() -{ - if (m_endpoint.is_ssl) { - initiate_ssl_handshake(); // Throws - } - else { - initiate_websocket_handshake(); // Throws - } -} - -void DefaultWebSocketImpl::initiate_http_tunnel() -{ - HTTPRequest req; - req.method = HTTPMethod::Connect; - req.headers.emplace("Host", util::format("%1:%2", m_endpoint.address, m_endpoint.port)); - // TODO handle proxy authorization - - m_proxy_client.emplace(*this, m_logger_ptr); - auto handler = [this](HTTPResponse response, std::error_code ec) { - if (ec && ec != util::error::operation_aborted) { - m_network_logger.error("Failed to establish HTTP tunnel: %1", ec.message()); - constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, - ec.message()); // Throws - return; - } - - if (response.status != HTTPStatus::Ok) { - m_network_logger.error("Proxy server returned response '%1 %2'", response.status, - response.reason); // Throws - constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, WebSocketError::websocket_connection_failed, - response.reason); // Throws - return; - } - - initiate_websocket_or_ssl_handshake(); // Throws - }; - - m_proxy_client->async_request(req, std::move(handler)); // Throws -} - -void DefaultWebSocketImpl::initiate_ssl_handshake() -{ - using namespace network::ssl; - - if (!m_ssl_context) { - m_ssl_context.emplace(); // Throws - if (m_endpoint.verify_servers_ssl_certificate) { - if (m_endpoint.ssl_trust_certificate_path) { - m_ssl_context->use_verify_file(*m_endpoint.ssl_trust_certificate_path); // Throws - } - else if (!m_endpoint.ssl_verify_callback) { - m_ssl_context->use_default_verify(); // Throws -#if REALM_INCLUDE_CERTS - // On platforms like Windows or Android where OpenSSL is not normally found - // `use_default_verify()` won't actually be able to load any default certificates. - // That's why we bundle a set of trusted certificates ourselves. - m_ssl_context->use_included_certificate_roots(); // Throws -#endif - } - } - } - - m_ssl_stream.emplace(*m_socket, *m_ssl_context, Stream::client); // Throws - m_ssl_stream->set_logger(m_logger_ptr.get()); - m_ssl_stream->set_host_name(m_endpoint.address); // Throws - if (m_endpoint.verify_servers_ssl_certificate) { - m_ssl_stream->set_verify_mode(VerifyMode::peer); // Throws - m_ssl_stream->set_server_port(m_endpoint.port); - if (!m_endpoint.ssl_trust_certificate_path) { - if (m_endpoint.ssl_verify_callback) { - m_ssl_stream->use_verify_callback(m_endpoint.ssl_verify_callback); - } - } - } - - auto handler = [this](std::error_code ec) { - // If the operation is aborted, the connection object may have been - // destroyed. - if (ec != util::error::operation_aborted) - handle_ssl_handshake(ec); // Throws - }; - m_ssl_stream->async_handshake(std::move(handler)); // Throws - - // FIXME: We also need to perform the SSL shutdown operation somewhere -} - - -void DefaultWebSocketImpl::handle_ssl_handshake(std::error_code ec) -{ - if (ec) { - REALM_ASSERT(ec != util::error::operation_aborted); - constexpr bool was_clean = false; - WebSocketError parsed_error_code; - if (ec == network::ssl::Errors::tls_handshake_failed) { - parsed_error_code = WebSocketError::websocket_tls_handshake_failed; - } - else { - parsed_error_code = WebSocketError::websocket_connection_failed; - } - - websocket_error_and_close_handler(was_clean, parsed_error_code, ec.message()); // Throws - return; - } - - initiate_websocket_handshake(); // Throws -} - - -void DefaultWebSocketImpl::initiate_websocket_handshake() -{ - auto headers = HTTPHeaders(m_endpoint.headers.begin(), m_endpoint.headers.end()); - headers["User-Agent"] = m_user_agent; - - // Compute the value of the "Host" header. - const std::uint_fast16_t default_port = (m_endpoint.is_ssl ? 443 : 80); - auto host = m_endpoint.port == default_port ? m_endpoint.address - : util::format("%1:%2", m_endpoint.address, m_endpoint.port); - - // Convert the list of protocols to a string - std::ostringstream protocol_list; - protocol_list.exceptions(std::ios_base::failbit | std::ios_base::badbit); - protocol_list.imbue(std::locale::classic()); - if (m_endpoint.protocols.size() > 1) - std::copy(m_endpoint.protocols.begin(), m_endpoint.protocols.end() - 1, - std::ostream_iterator(protocol_list, ", ")); - protocol_list << m_endpoint.protocols.back(); - - m_websocket.initiate_client_handshake(m_endpoint.path, std::move(host), protocol_list.str(), - std::move(headers)); // Throws -} -} // namespace - -/// -/// DefaultSocketProvider - default socket provider implementation -/// - -DefaultSocketProvider::DefaultSocketProvider(const std::shared_ptr& logger, - const std::string& user_agent, - const std::shared_ptr& observer_ptr, - AutoStart auto_start) - : m_logger_ptr{std::make_shared(util::LogCategory::network, logger)} - , m_observer_ptr{observer_ptr} - , m_user_agent{user_agent} - , m_state{State::Stopped} -{ - REALM_ASSERT(m_logger_ptr); // Make sure the logger is valid - util::seed_prng_nondeterministically(m_random); // Throws - if (auto_start) { - start(); - } -} - -DefaultSocketProvider::~DefaultSocketProvider() -{ - m_logger_ptr->trace("Default event loop teardown"); - // Wait for the thread to stop - stop(true); - // Shutting down - no need to lock mutex before check - REALM_ASSERT(m_state == State::Stopped); -} - -void DefaultSocketProvider::start() -{ - util::CheckedUniqueLock lock(m_mutex); - // Has the thread already been started or is running - if (m_state == State::Starting || m_state == State::Running) - return; // early return - - // If the thread has been previously run, make sure it has been joined first - if (m_thread.joinable()) { - state_wait_for(lock, State::Stopped); - m_thread.join(); - } - - m_logger_ptr->trace("Default event loop: start()"); - REALM_ASSERT(m_state == State::Stopped); - - do_state_update(State::Starting); - m_thread = std::thread{&DefaultSocketProvider::event_loop, this}; - // Wait for the thread to start before continuing - state_wait_for(lock, State::Running); -} - -void DefaultSocketProvider::event_loop() -{ - m_logger_ptr->trace("Default event loop: thread running"); - // Calls will_destroy_thread() when destroyed - auto will_destroy_thread = util::make_scope_exit([&]() noexcept { - m_logger_ptr->trace("Default event loop: thread exiting"); - if (m_observer_ptr) - m_observer_ptr->will_destroy_thread(); - - { - util::CheckedLockGuard lock(m_mutex); - // Did we get here due to an unhandled exception? - if (m_state != State::Stopping) { - m_logger_ptr->error("Default event loop: thread exited unexpectedly"); - } - m_state = State::Stopped; - } - m_state_cv.notify_all(); - }); - - if (m_observer_ptr) - m_observer_ptr->did_create_thread(); - - { - util::CheckedLockGuard lock(m_mutex); - REALM_ASSERT(m_state == State::Starting); - } - - // We update the state to Running from inside the event loop so that start() is blocked until - // the event loop is actually ready to receive work. - m_service.post([this, my_generation = ++m_event_loop_generation](Status status) { - if (status == ErrorCodes::OperationAborted) { - return; - } - - REALM_ASSERT(status.is_ok()); - - util::CheckedLockGuard lock(m_mutex); - // This is a callback from a previous generation - if (m_event_loop_generation != my_generation) { - return; - } - if (m_state == State::Stopping) { - return; - } - m_logger_ptr->trace("Default event loop: service run"); - REALM_ASSERT(m_state == State::Starting); - do_state_update(State::Running); - }); - - // If there is no event loop observer or handle_error function registered, then just - // allow the exception to bubble to the top so we can get a true stack trace - if (!m_observer_ptr || !m_observer_ptr->has_handle_error()) { - m_service.run_until_stopped(); // Throws - } - else { - try { - m_service.run_until_stopped(); // Throws - } - catch (const std::exception& e) { - { - util::CheckedLockGuard lock(m_mutex); - // Service is no longer running, event loop thread is stopping - m_state = State::Stopping; - } - m_state_cv.notify_all(); - m_logger_ptr->error("Default event loop exception: ", e.what()); - // If the error was not handled by the thread loop observer, then rethrow - if (!m_observer_ptr->handle_error(e)) - throw; - } - } -} - -void DefaultSocketProvider::stop(bool wait_for_stop) -{ - util::CheckedUniqueLock lock(m_mutex); - - // Do nothing if the thread is not started or running or stop has already been called - if (m_state == State::Starting || m_state == State::Running) { - m_logger_ptr->trace("Default event loop: stop()"); - do_state_update(State::Stopping); - // Updating state to Stopping will free a start() if it is waiting for the thread to - // start and may cause the thread to exit early before calling service.run() - m_service.stop(); // Unblocks m_service.run() - } - - // Wait until the thread is stopped (exited) if requested - if (wait_for_stop) { - m_logger_ptr->trace("Default event loop: wait for stop"); - state_wait_for(lock, State::Stopped); - if (m_thread.joinable()) { - m_thread.join(); - } - } -} - -// +---------------------------------------+ -// \/ | -// State Machine: Stopped -> Starting -> Running -> Stopping -+ -// | | ^ -// +----------------------+ - -void DefaultSocketProvider::do_state_update(State new_state) -{ - m_state = new_state; - m_state_cv.notify_all(); // Let any waiters check the state -} - -void DefaultSocketProvider::state_wait_for(util::CheckedUniqueLock& lock, State expected_state) -{ - m_state_cv.wait(lock.native_handle(), [this, expected_state]() REQUIRES(m_mutex) { - return m_state >= expected_state; - }); -} - -std::unique_ptr DefaultSocketProvider::connect(std::unique_ptr observer, - WebSocketEndpoint&& endpoint) -{ - return std::make_unique(m_logger_ptr, m_service, m_random, m_user_agent, - std::move(observer), std::move(endpoint)); -} - -} // namespace realm::sync::websocket diff --git a/src/realm/sync/network/default_socket.hpp b/src/realm/sync/network/default_socket.hpp deleted file mode 100644 index c4b532f95d2..00000000000 --- a/src/realm/sync/network/default_socket.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace realm::sync::network { -class Service; -} // namespace realm::sync::network - -namespace realm::sync::websocket { -using port_type = sync::port_type; - -class DefaultSocketProvider : public SyncSocketProvider { -public: - class Timer final : public SyncSocketProvider::Timer { - public: - friend class DefaultSocketProvider; - - /// Cancels the timer and destroys the timer instance. - ~Timer() = default; - - /// Cancel the timer immediately - void cancel() override - { - m_timer.cancel(); - } - - private: - network::DeadlineTimer m_timer; - - Timer(network::Service& service, std::chrono::milliseconds delay, FunctionHandler&& handler) - : m_timer{service} - { - m_timer.async_wait(delay, std::move(handler)); - } - }; - - using AutoStart = util::TaggedBool; - DefaultSocketProvider(const std::shared_ptr& logger, const std::string& user_agent, - const std::shared_ptr& observer_ptr = nullptr, - AutoStart auto_start = AutoStart{true}); - - ~DefaultSocketProvider(); - - // Start the event loop if it is not started already. Otherwise, do nothing. - void start() REQUIRES(!m_mutex); - - /// Temporary workaround until client shutdown has been updated in a separate PR - these functions - /// will be handled internally when this happens. - /// Stops the internal event loop (provided by network::Service) - void stop(bool wait_for_stop = false) override REQUIRES(!m_mutex); - - std::unique_ptr connect(std::unique_ptr, WebSocketEndpoint&&) override; - - void post(FunctionHandler&& handler) override - { - // Don't post empty handlers onto the event loop - if (!handler) - return; - m_service.post(std::move(handler)); - } - - SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) override - { - return std::unique_ptr(new DefaultSocketProvider::Timer(m_service, delay, std::move(handler))); - } - -private: - enum class State { Starting, Running, Stopping, Stopped }; - - /// Block until the state reaches the expected or later state - void state_wait_for(util::CheckedUniqueLock& lock, State expected_state) REQUIRES(m_mutex); - /// Internal function for updating the state and signaling the wait_for_state condvar - void do_state_update(State new_state) REQUIRES(m_mutex); - /// The execution code for the event loop thread - void event_loop() REQUIRES(!m_mutex); - - std::shared_ptr m_logger_ptr; - const std::shared_ptr m_observer_ptr; - network::Service m_service; - std::mt19937_64 m_random; - const std::string m_user_agent; - util::CheckedMutex m_mutex; - uint64_t m_event_loop_generation = 0; - State m_state GUARDED_BY(m_mutex); - std::condition_variable m_state_cv; - std::thread m_thread GUARDED_BY(m_mutex); -}; - -/// Class for the Default Socket Provider websockets that allows a simulated -/// http response to be specified for testing. -class DefaultWebSocket : public WebSocketInterface { -public: - virtual ~DefaultWebSocket() = default; - - virtual void force_handshake_response_for_testing(int status_code, std::string body = "") = 0; -}; - -} // namespace realm::sync::websocket diff --git a/src/realm/sync/network/http.cpp b/src/realm/sync/network/http.cpp deleted file mode 100644 index 92df0647f83..00000000000 --- a/src/realm/sync/network/http.cpp +++ /dev/null @@ -1,493 +0,0 @@ -#include -#include -#include -#include - -#include - -using namespace realm; - -namespace { - -StringData trim_whitespace(StringData str) -{ - auto p0 = str.data(); - auto p1 = str.data() + str.size(); - while (p1 > p0 && std::isspace(*(p1 - 1))) - --p1; - while (p0 < p1 && std::isspace(*p0)) - ++p0; - return StringData(p0, p1 - p0); -} - - -struct HTTPParserErrorCategory : std::error_category { - HTTPParserErrorCategory() {} - - const char* name() const noexcept override - { - return "HTTP Parser Error"; - } - - std::string message(int condition) const override - { - using sync::HTTPParserError; - switch (HTTPParserError(condition)) { - case HTTPParserError::None: - return "None"; - case HTTPParserError::ContentTooLong: - return "Content too long"; - case HTTPParserError::HeaderLineTooLong: - return "Header line too long"; - case HTTPParserError::MalformedResponse: - return "Malformed response"; - case HTTPParserError::MalformedRequest: - return "Malformed request"; - default: - REALM_TERMINATE("Invalid HTTP Parser Error"); - } - } -}; - -const HTTPParserErrorCategory g_http_parser_error_category; - -} // unnamed namespace - - -namespace realm::sync { - -bool valid_http_status_code(unsigned int code) -{ - if (code < 100) - return false; - if (code > 101 && code < 200) - return false; - if (code > 206 && code < 300) - return false; - if (code > 308 && code < 400) - return false; - if (code > 451 && code < 500) - return false; - if (code > 511) - return false; - - return true; -} - - -HTTPAuthorization parse_authorization(const std::string& header_value) -{ - // StringData line{header_value.c_str(), header_value.length()}; - - HTTPAuthorization auth; - auto p = header_value.begin(); - auto end = header_value.end(); - auto space = std::find(p, end, ' '); - - auth.scheme = std::string(p, space); - - while (space != end) { - p = space + 1; - space = std::find(p, end, ' '); - auto eq = std::find(p, space, '='); - - if (eq == space) { - continue; - } - - auto key_begin = p; - auto key_end = eq; - auto value_begin = eq + 1; - auto value_end = space; - - std::string key(key_begin, key_end); - std::string value(value_begin, value_end); - - if (key.size() == 0) - continue; - - auth.values[key] = value; - } - - return auth; -} - - -void HTTPParserBase::set_write_buffer(const HTTPRequest& req) -{ - std::stringstream ss; - ss << req; - m_write_buffer = ss.str(); -} - - -void HTTPParserBase::set_write_buffer(const HTTPResponse& res) -{ - std::stringstream ss; - ss << res; - m_write_buffer = ss.str(); -} - - -bool HTTPParserBase::parse_header_line(size_t len) -{ - StringData line{m_read_buffer.get(), len}; - auto p = line.data(); - auto end = line.data() + line.size(); - auto colon = std::find(p, end, ':'); - - if (colon == end) { - network_logger.error("Bad header line in HTTP message:\n%1", line); - return false; - } - - auto key_begin = p; - auto key_end = colon; - auto value_begin = colon + 1; - auto value_end = end; - - StringData key(key_begin, key_end - key_begin); - StringData value(value_begin, value_end - value_begin); - - key = trim_whitespace(key); - value = trim_whitespace(value); - - if (key.size() == 0) { - network_logger.error("Bad header line in HTTP message:\n%1", line); - return false; - } - - if (key == "Content-Length" || key == "content-length") { - if (value.size() == 0) { - // We consider the empty Content-Length to mean 0. - // A warning is logged. - network_logger.warn("Empty Content-Length header in HTTP message:\n%1", line); - m_found_content_length = 0; - } - else { - std::stringstream ss; - ss.str(value); - size_t content_length; - if (ss >> content_length && ss.eof()) { - m_found_content_length = content_length; - } - else { - network_logger.error("Bad Content-Length header in HTTP message:\n%1", line); - return false; - } - } - } - - if ((key == "Transfer-Encoding" || key == "transfer-encoding") && (value == "Chunked" || value == "chunked")) { - m_has_chunked_encoding = true; - m_chunked_encoding_ss.emplace(std::stringstream()); - } - - this->on_header(key, value); - return true; -} - - -util::Optional HTTPParserBase::parse_method_string(StringData method) -{ - if (method == "OPTIONS") - return HTTPMethod::Options; - if (method == "GET") - return HTTPMethod::Get; - if (method == "HEAD") - return HTTPMethod::Head; - if (method == "POST") - return HTTPMethod::Post; - if (method == "PUT") - return HTTPMethod::Put; - if (method == "PATCH") - return HTTPMethod::Patch; - if (method == "DELETE") - return HTTPMethod::Delete; - if (method == "TRACE") - return HTTPMethod::Trace; - if (method == "CONNECT") - return HTTPMethod::Connect; - return none; -} - - -bool HTTPParserBase::parse_first_line_of_request(StringData line, HTTPMethod& out_method, StringData& out_uri) -{ - line = trim_whitespace(line); - auto p = line.data(); - auto end = line.data() + line.size(); - auto sp = std::find(p, end, ' '); - if (sp == end) - return false; - StringData method(p, sp - p); - auto request_uri_begin = sp + 1; - sp = std::find(request_uri_begin, end, ' '); - if (sp == end) - return false; - out_uri = StringData(request_uri_begin, sp - request_uri_begin); - auto http_version_begin = sp + 1; - StringData http_version(http_version_begin, end - http_version_begin); - if (http_version != "HTTP/1.1") { - return false; - } - auto parsed_method = HTTPParserBase::parse_method_string(method); - if (!parsed_method) - return false; - out_method = *parsed_method; - return true; -} - - -bool HTTPParserBase::parse_first_line_of_response(StringData line, HTTPStatus& out_status, StringData& out_reason, - util::Logger& logger) -{ - line = trim_whitespace(line); - auto p = line.data(); - auto end = line.data() + line.size(); - auto sp = std::find(p, end, ' '); - if (sp == end) { - logger.error("Invalid HTTP response:\n%1", line); - return false; - } - StringData http_version(p, sp - p); - if (http_version != "HTTP/1.1") { - logger.error("Invalid version in HTTP response:\n%1", line); - return false; - } - auto status_code_begin = sp + 1; - sp = std::find(status_code_begin, end, ' '); - auto status_code_end = sp; - if (status_code_end != end) { - // Some proxies don't give a "Reason-Phrase". This is not valid - // according to the HTTP/1.1 standard, but what are we gonna do... - auto reason_begin = sp + 1; - auto reason_end = end; - out_reason = StringData(reason_begin, reason_end - reason_begin); - } - - StringData status_code_str(status_code_begin, status_code_end - status_code_begin); - std::stringstream ss; - ss << status_code_str; - unsigned int code; - if (ss >> code && valid_http_status_code(code)) { - out_status = static_cast(code); - } - else { - logger.error("Invalid status code in HTTP response:\n%1", line); - return false; - } - return true; -} - - -std::ostream& operator<<(std::ostream& os, HTTPMethod method) -{ - switch (method) { - case HTTPMethod::Options: - return os << "OPTIONS"; - case HTTPMethod::Get: - return os << "GET"; - case HTTPMethod::Head: - return os << "HEAD"; - case HTTPMethod::Post: - return os << "POST"; - case HTTPMethod::Put: - return os << "PUT"; - case HTTPMethod::Patch: - return os << "PATCH"; - case HTTPMethod::Delete: - return os << "DELETE"; - case HTTPMethod::Trace: - return os << "TRACE"; - case HTTPMethod::Connect: - return os << "CONNECT"; - } - REALM_TERMINATE("Invalid HTTPRequest object."); -} - - -std::ostream& operator<<(std::ostream& os, HTTPStatus status) -{ - os << int(status) << ' '; - switch (status) { - case HTTPStatus::Unknown: - return os << "Unknown Status"; - case HTTPStatus::Continue: - return os << "Continue"; - case HTTPStatus::SwitchingProtocols: - return os << "Switching Protocols"; - case HTTPStatus::Ok: - return os << "OK"; - case HTTPStatus::Created: - return os << "Created"; - case HTTPStatus::Accepted: - return os << "Accepted"; - case HTTPStatus::NonAuthoritative: - return os << "Non-Authoritative Information"; - case HTTPStatus::NoContent: - return os << "No Content"; - case HTTPStatus::ResetContent: - return os << "Reset Content"; - case HTTPStatus::PartialContent: - return os << "Partial Content"; - case HTTPStatus::MultipleChoices: - return os << "Multiple Choices"; - case HTTPStatus::MovedPermanently: - return os << "Moved Permanently"; - case HTTPStatus::Found: - return os << "Found"; - case HTTPStatus::SeeOther: - return os << "See Other"; - case HTTPStatus::NotModified: - return os << "Not Modified"; - case HTTPStatus::UseProxy: - return os << "Use Proxy"; - case HTTPStatus::SwitchProxy: - return os << "Switch Proxy"; - case HTTPStatus::TemporaryRedirect: - return os << "Temporary Redirect"; - case HTTPStatus::PermanentRedirect: - return os << "Permanent Redirect"; - case HTTPStatus::BadRequest: - return os << "Bad Request"; - case HTTPStatus::Unauthorized: - return os << "Unauthorized"; - case HTTPStatus::PaymentRequired: - return os << "Payment Required"; - case HTTPStatus::Forbidden: - return os << "Forbidden"; - case HTTPStatus::NotFound: - return os << "Not Found"; - case HTTPStatus::MethodNotAllowed: - return os << "Method Not Allowed"; - case HTTPStatus::NotAcceptable: - return os << "Not Acceptable"; - case HTTPStatus::ProxyAuthenticationRequired: - return os << "Proxy Authentication Required"; - case HTTPStatus::RequestTimeout: - return os << "Request Timeout"; - case HTTPStatus::Conflict: - return os << "Conflict"; - case HTTPStatus::Gone: - return os << "Gone"; - case HTTPStatus::LengthRequired: - return os << "Length Required"; - case HTTPStatus::PreconditionFailed: - return os << "Precondition Failed"; - case HTTPStatus::PayloadTooLarge: - return os << "Payload Too Large"; - case HTTPStatus::UriTooLong: - return os << "URI Too Long"; - case HTTPStatus::UnsupportedMediaType: - return os << "Unsupported Media Type"; - case HTTPStatus::RangeNotSatisfiable: - return os << "Range Not Satisfiable"; - case HTTPStatus::ExpectationFailed: - return os << "Expectation Failed"; - case HTTPStatus::ImATeapot: - return os << "I'm A Teapot"; - case HTTPStatus::MisdirectedRequest: - return os << "Misdirected Request"; - case HTTPStatus::UpgradeRequired: - return os << "Upgrade Required"; - case HTTPStatus::PreconditionRequired: - return os << "Precondition Required"; - case HTTPStatus::TooManyRequests: - return os << "Too Many Requests"; - case HTTPStatus::RequestHeaderFieldsTooLarge: - return os << "Request Header Fields Too Large"; - case HTTPStatus::UnavailableForLegalReasons: - return os << "Unavailable For Legal Reasons"; - case HTTPStatus::InternalServerError: - return os << "Internal Server Error"; - case HTTPStatus::NotImplemented: - return os << "Not Implemented"; - case HTTPStatus::BadGateway: - return os << "Bad Gateway"; - case HTTPStatus::ServiceUnavailable: - return os << "Service Unavailable"; - case HTTPStatus::GatewayTimeout: - return os << "Gateway Timeout"; - case HTTPStatus::HttpVersionNotSupported: - return os << "HTTP Version not supported"; - case HTTPStatus::VariantAlsoNegotiates: - return os << "Variant Also Negotiates"; - case HTTPStatus::NotExtended: - return os << "Not Extended"; - case HTTPStatus::NetworkAuthenticationRequired: - return os << "Network Authentication Required"; - } - return os; -} - - -std::ostream& operator<<(std::ostream& os, const HTTPRequest& request) -{ - auto host = request.headers.find("Host"); - - os << request.method << ' '; - - if (request.method == HTTPMethod::Connect) { - REALM_ASSERT_RELEASE(host != request.headers.end()); - os << host->second; - } - else if (request.path.size() == 0) { - os << '/'; - } - else { - os << request.path; - } - os << " HTTP/1.1\r\n"; - - { - os << "Host:"; - if (host != request.headers.end()) - os << " " << host->second; - os << "\r\n"; - } - - for (auto& pair : request.headers) { - if (pair.first == "Host") - continue; - // FIXME: No need for trimming here. There should be extra white space - // when, and only when the application specifies it. - StringData trimmed_value = trim_whitespace(pair.second); - os << pair.first << ": " << trimmed_value << "\r\n"; - } - os << "\r\n"; - if (request.body) - os.write(request.body->data(), request.body->size()); - - bool content_length_exists = request.headers.find("Content-Length") != request.headers.end(); - bool body_exists = bool(request.body); - REALM_ASSERT(content_length_exists == body_exists); - - return os; -} - - -std::ostream& operator<<(std::ostream& os, const HTTPResponse& response) -{ - os << "HTTP/1.1 " << response.status; - os << "\r\n"; - - for (auto& pair : response.headers) { - StringData trimmed_value = trim_whitespace(pair.second); - os << pair.first << ": " << trimmed_value << "\r\n"; - } - os << "\r\n"; - if (response.body) { - os.write(response.body->data(), response.body->size()); - } - - return os; -} - - -std::error_code make_error_code(HTTPParserError error) -{ - return std::error_code(static_cast(error), g_http_parser_error_category); -} - -} // namespace realm::sync diff --git a/src/realm/sync/network/http.hpp b/src/realm/sync/network/http.hpp deleted file mode 100644 index 1eff44b80ab..00000000000 --- a/src/realm/sync/network/http.hpp +++ /dev/null @@ -1,570 +0,0 @@ -/************************************************************************* - * - * Copyright 2022 Realm Inc. - * - * 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 - -#include -#include -#include -#include - -namespace realm::sync { -enum class HTTPParserError { - None = 0, - ContentTooLong, - HeaderLineTooLong, - MalformedResponse, - MalformedRequest, - BadRequest, -}; -std::error_code make_error_code(HTTPParserError); -} // namespace realm::sync - -namespace std { -template <> -struct is_error_code_enum : std::true_type {}; -} // namespace std - -namespace realm::sync { - -/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html -/// -/// It is guaranteed that the backing integer value of this enum corresponds -/// to the numerical code representing the status. -enum class HTTPStatus { - Unknown = 0, - - Continue = 100, - SwitchingProtocols = 101, - - Ok = 200, - Created = 201, - Accepted = 202, - NonAuthoritative = 203, - NoContent = 204, - ResetContent = 205, - PartialContent = 206, - - MultipleChoices = 300, - MovedPermanently = 301, - Found = 302, - SeeOther = 303, - NotModified = 304, - UseProxy = 305, - SwitchProxy = 306, - TemporaryRedirect = 307, - PermanentRedirect = 308, - - BadRequest = 400, - Unauthorized = 401, - PaymentRequired = 402, - Forbidden = 403, - NotFound = 404, - MethodNotAllowed = 405, - NotAcceptable = 406, - ProxyAuthenticationRequired = 407, - RequestTimeout = 408, - Conflict = 409, - Gone = 410, - LengthRequired = 411, - PreconditionFailed = 412, - PayloadTooLarge = 413, - UriTooLong = 414, - UnsupportedMediaType = 415, - RangeNotSatisfiable = 416, - ExpectationFailed = 417, - ImATeapot = 418, - MisdirectedRequest = 421, - UpgradeRequired = 426, - PreconditionRequired = 428, - TooManyRequests = 429, - RequestHeaderFieldsTooLarge = 431, - UnavailableForLegalReasons = 451, - - InternalServerError = 500, - NotImplemented = 501, - BadGateway = 502, - ServiceUnavailable = 503, - GatewayTimeout = 504, - HttpVersionNotSupported = 505, - VariantAlsoNegotiates = 506, - NotExtended = 510, - NetworkAuthenticationRequired = 511, -}; - -bool valid_http_status_code(unsigned int code); - -/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html -enum class HTTPMethod { - Options, - Get, - Head, - Post, - Put, - Patch, - Delete, - Trace, - Connect, -}; - -struct HTTPAuthorization { - std::string scheme; - std::map values; -}; - -HTTPAuthorization parse_authorization(const std::string&); - -class HeterogeneousCaseInsensitiveCompare { -public: - using is_transparent = std::true_type; - template - bool operator()(const A& a, const B& b) const noexcept - { - return comp(std::string_view(a), std::string_view(b)); - } - -private: - bool comp(std::string_view a, std::string_view b) const noexcept - { - auto cmp = [](char lhs, char rhs) { - return std::tolower(lhs, std::locale::classic()) < std::tolower(rhs, std::locale::classic()); - }; - return std::lexicographical_compare(begin(a), end(a), begin(b), end(b), cmp); - } -}; - -/// Case-insensitive map suitable for storing HTTP headers. -using HTTPHeaders = std::map; - -struct HTTPRequest { - HTTPMethod method = HTTPMethod::Get; - HTTPHeaders headers; - std::string path; - - /// If the request object has a body, the Content-Length header MUST be - /// set to a string representation of the number of bytes in the body. - /// FIXME: Relax this restriction, and also support Transfer-Encoding - /// and other HTTP/1.1 features. - util::Optional body; -}; - -struct HTTPResponse { - HTTPStatus status = HTTPStatus::Unknown; - std::string reason; - HTTPHeaders headers; - - // A body is only read from the response stream if the server sent the - // Content-Length header. - // FIXME: Support other transfer methods, including Transfer-Encoding and - // HTTP/1.1 features. - util::Optional body; -}; - - -/// Serialize HTTP request to output stream. -std::ostream& operator<<(std::ostream&, const HTTPRequest&); -/// Serialize HTTP response to output stream. -std::ostream& operator<<(std::ostream&, const HTTPResponse&); -/// Serialize HTTP method to output stream ("GET", "POST", etc.). -std::ostream& operator<<(std::ostream&, HTTPMethod); -/// Serialize HTTP status to output stream, include reason string ("200 OK" etc.) -std::ostream& operator<<(std::ostream&, HTTPStatus); - - -struct HTTPParserBase { - const std::shared_ptr logger_ptr; - util::Logger& network_logger; - - // FIXME: Generally useful? - struct CallocDeleter { - void operator()(void* ptr) - { - std::free(ptr); - } - }; - - HTTPParserBase(const std::shared_ptr& logger_ptr) - : logger_ptr{std::make_shared(util::LogCategory::network, logger_ptr)} - , network_logger{*logger_ptr} - { - // Allocating read buffer with calloc to avoid accidentally spilling - // data from other sessions in case of a buffer overflow exploit. - m_read_buffer.reset(static_cast(std::calloc(read_buffer_size, 1))); - } - virtual ~HTTPParserBase() {} - - std::string m_write_buffer; - std::unique_ptr m_read_buffer; - util::Optional m_found_content_length; - bool m_has_chunked_encoding = false; - std::optional m_chunked_encoding_ss; - static const size_t read_buffer_size = 8192; - static const size_t max_header_line_length = read_buffer_size; - - /// Parses the contents of m_read_buffer as a HTTP header line, - /// and calls on_header() as appropriate. on_header() will be called at - /// most once per invocation. - /// Returns false if the contents of m_read_buffer is not a valid HTTP - /// header line. - bool parse_header_line(size_t len); - - virtual std::error_code on_first_line(StringData line) = 0; - virtual void on_header(StringData key, StringData value) = 0; - virtual void on_body(StringData body) = 0; - virtual void on_complete(std::error_code = std::error_code{}) = 0; - - /// If the input matches a known HTTP method string, return the appropriate - /// HTTPMethod enum value. Otherwise, returns none. - static util::Optional parse_method_string(StringData method); - - /// Interpret line as the first line of an HTTP request. If the return value - /// is true, out_method and out_uri have been assigned the appropriate - /// values found in the request line. - static bool parse_first_line_of_request(StringData line, HTTPMethod& out_method, StringData& out_uri); - - /// Interpret line as the first line of an HTTP response. If the return - /// value is true, out_status and out_reason have been assigned the - /// appropriate values found in the response line. - static bool parse_first_line_of_response(StringData line, HTTPStatus& out_status, StringData& out_reason, - util::Logger& logger); - - void set_write_buffer(const HTTPRequest&); - void set_write_buffer(const HTTPResponse&); -}; - - -template -struct HTTPParser : protected HTTPParserBase { - explicit HTTPParser(Socket& socket, const std::shared_ptr& logger_ptr) - : HTTPParserBase(logger_ptr) - , m_socket(socket) - { - } - - void read_first_line() - { - auto handler = [this](std::error_code ec, size_t n) { - if (ec == util::error::operation_aborted) { - return; - } - if (ec) { - on_complete(ec); - return; - } - ec = on_first_line(StringData(m_read_buffer.get(), n)); - if (ec) { - on_complete(ec); - return; - } - read_headers(); - }; - m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler)); - } - - void read_headers() - { - auto handler = [this](std::error_code ec, size_t n) { - if (ec == util::error::operation_aborted) { - return; - } - if (ec) { - on_complete(ec); - return; - } - if (n <= 2) { - read_body(); - return; - } - if (!parse_header_line(n)) { - on_complete(HTTPParserError::BadRequest); - return; - } - - // FIXME: Limit the total size of headers. Apache uses 8K. - read_headers(); - }; - m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler)); - } - - void read_body() - { - if (m_found_content_length) { - // FIXME: Support longer bodies. - // FIXME: Support multipart and other body types (no body shaming). - if (*m_found_content_length > read_buffer_size) { - on_complete(HTTPParserError::ContentTooLong); - return; - } - - auto handler = [this](std::error_code ec, size_t n) { - if (ec == util::error::operation_aborted) { - return; - } - if (!ec) { - on_body(std::string_view(m_read_buffer.get(), n)); - } - on_complete(ec); - }; - m_socket.async_read(m_read_buffer.get(), *m_found_content_length, std::move(handler)); - } - else if (m_has_chunked_encoding) { - auto content_length_handler = [this](std::error_code ec, size_t chunk_start_index) { - if (ec == util::error::operation_aborted) { - on_complete(ec); - return; - } - - auto content_length = - std::strtoul(std::string(m_read_buffer.get(), chunk_start_index - 2).c_str(), nullptr, 16); - - if (content_length == 0) { - on_body(m_chunked_encoding_ss->str()); - on_complete(ec); - return; - } - - auto handler = [this](std::error_code ec, size_t n) { - if (ec == util::error::operation_aborted) { - on_complete(ec); - return; - } - auto chunk_data = std::string_view(m_read_buffer.get(), n - 2); // -2 to strip \r\n - *m_chunked_encoding_ss << chunk_data; - read_body(); - }; - m_socket.async_read(m_read_buffer.get(), content_length + 2, - std::move(handler)); // +2 to account for \r\n - }; - - // First get the content-length - m_socket.async_read_until(m_read_buffer.get(), 8, '\n', - content_length_handler); // buffer of 8 is enough to read hex value - } - else { - // No body, just finish. - on_complete(); - } - } - - void write_buffer(util::UniqueFunction handler) - { - m_socket.async_write(m_write_buffer.data(), m_write_buffer.size(), std::move(handler)); - } - - Socket& m_socket; -}; - - -template -struct HTTPClient : protected HTTPParser { - using Handler = void(HTTPResponse, std::error_code); - - explicit HTTPClient(Socket& socket, const std::shared_ptr& logger_ptr) - : HTTPParser(socket, logger_ptr) - { - } - - /// Serialize and send \a request over the connected socket asynchronously. - /// - /// When the response has been received, or an error occurs, \a handler will - /// be invoked with the appropriate parameters. The HTTPResponse object - /// passed to \a handler will only be complete in non-error conditions, but - /// may be partially populated. - /// - /// It is an error to start a request before the \a handler of a previous - /// request has been invoked. It is permitted to call async_request() from - /// the handler, unless an error has been reported representing a condition - /// where the underlying socket is no longer able to communicate (for - /// example, if it has been closed). - /// - /// If a request is already in progress, an exception will be thrown. - /// - /// This method is *NOT* thread-safe. - void async_request(const HTTPRequest& request, util::UniqueFunction handler) - { - if (REALM_UNLIKELY(m_handler)) { - throw LogicError(ErrorCodes::LogicError, "Request already in progress."); - } - this->set_write_buffer(request); - m_handler = std::move(handler); - this->write_buffer([this](std::error_code ec, size_t bytes_written) { - static_cast(bytes_written); - if (ec == util::error::operation_aborted) { - return; - } - if (ec) { - this->on_complete(ec); - return; - } - this->read_first_line(); - }); - } - -private: - util::UniqueFunction m_handler; - HTTPResponse m_response; - - std::error_code on_first_line(StringData line) override final - { - HTTPStatus status; - StringData reason; - if (this->parse_first_line_of_response(line, status, reason, this->network_logger)) { - m_response.status = status; - m_response.reason = reason; - return std::error_code{}; - } - return HTTPParserError::MalformedResponse; - } - - void on_header(StringData key, StringData value) override final - { - // FIXME: Multiple headers with the same key should show up as a - // comma-separated list of their values, rather than overwriting. - m_response.headers[std::string(key)] = std::string(value); - } - - void on_body(StringData body) override final - { - m_response.body = std::string(body); - } - - void on_complete(std::error_code ec) override final - { - auto handler = std::move(m_handler); - m_handler = nullptr; - handler(std::move(m_response), ec); - } -}; - - -template -struct HTTPServer : protected HTTPParser { - using RequestHandler = void(HTTPRequest, std::error_code); - using RespondHandler = void(std::error_code); - - explicit HTTPServer(Socket& socket, const std::shared_ptr& logger_ptr) - : HTTPParser(socket, logger_ptr) - { - } - - /// Receive a request on the underlying socket asynchronously. - /// - /// This function starts an asynchronous read operation and keeps reading - /// until an HTTP request has been received. \a handler is invoked when a - /// request has been received, or an error occurs. - /// - /// After a request is received, callers MUST invoke async_send_response() - /// to provide the client with a valid HTTP response, unless the error - /// passed to the handler represents a condition where the underlying socket - /// is no longer able to communicate (for example, if it has been closed). - /// - /// It is an error to attempt to receive a request before any previous - /// requests have been fully responded to, i.e. the \a handler argument of - /// async_send_response() must have been invoked before attempting to - /// receive the next request. - /// - /// This function is *NOT* thread-safe. - void async_receive_request(util::UniqueFunction handler) - { - if (REALM_UNLIKELY(m_request_handler)) { - throw LogicError(ErrorCodes::LogicError, "Request already in progress"); - } - m_request_handler = std::move(handler); - this->read_first_line(); - } - - /// Send an HTTP response to a client asynchronously. - /// - /// This function starts an asynchronous write operation on the underlying - /// socket. \a handler is invoked when the response has been written to the - /// socket, or an error occurs. - /// - /// It is an error to call async_receive_request() again before \a handler - /// has been invoked, and it is an error to call async_send_response() - /// before the \a handler of a previous invocation has been invoked. - /// - /// This function is *NOT* thread-safe. - void async_send_response(const HTTPResponse& response, util::UniqueFunction handler) - { - if (REALM_UNLIKELY(!m_request_handler)) { - throw LogicError(ErrorCodes::LogicError, "No request in progress"); - } - if (m_respond_handler) { - // FIXME: Proper exception type. - throw LogicError(ErrorCodes::LogicError, "Already responding to request"); - } - m_respond_handler = std::move(handler); - this->set_write_buffer(response); - this->write_buffer([this](std::error_code ec, size_t) { - if (ec == util::error::operation_aborted) { - return; - } - m_request_handler = nullptr; - auto handler = std::move(m_respond_handler); - handler(ec); - }); - ; - } - -private: - util::UniqueFunction m_request_handler; - util::UniqueFunction m_respond_handler; - HTTPRequest m_request; - - std::error_code on_first_line(StringData line) override final - { - HTTPMethod method; - StringData uri; - if (this->parse_first_line_of_request(line, method, uri)) { - m_request.method = method; - m_request.path = uri; - return std::error_code{}; - } - return HTTPParserError::MalformedRequest; - } - - void on_header(StringData key, StringData value) override final - { - // FIXME: Multiple headers with the same key should show up as a - // comma-separated list of their values, rather than overwriting. - m_request.headers[std::string(key)] = std::string(value); - } - - void on_body(StringData body) override final - { - m_request.body = std::string(body); - } - - void on_complete(std::error_code ec) override final - { - // Deliberately not nullifying m_request_handler so that we can - // check for invariants in async_send_response. - m_request_handler(std::move(m_request), ec); - } -}; - -} // namespace realm::sync diff --git a/src/realm/sync/network/network.cpp b/src/realm/sync/network/network.cpp deleted file mode 100644 index 0090c4db326..00000000000 --- a/src/realm/sync/network/network.cpp +++ /dev/null @@ -1,2637 +0,0 @@ - -#define _WINSOCK_DEPRECATED_NO_WARNINGS - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifndef _WIN32 -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include - -#if defined _GNU_SOURCE && !REALM_ANDROID -#define HAVE_LINUX_PIPE2 1 -#else -#define HAVE_LINUX_PIPE2 0 -#endif - -// Note: Linux specific accept4() is not available on Android. -#if defined _GNU_SOURCE && defined SOCK_NONBLOCK && defined SOCK_CLOEXEC && !REALM_ANDROID -#define HAVE_LINUX_ACCEPT4 1 -#else -#define HAVE_LINUX_ACCEPT4 0 -#endif - -#if defined _GNU_SOURCE && defined SOCK_CLOEXEC -#define HAVE_LINUX_SOCK_CLOEXEC 1 -#else -#define HAVE_LINUX_SOCK_CLOEXEC 0 -#endif - -#ifndef _WIN32 - -#if REALM_NETWORK_USE_EPOLL -#include -#include -#elif REALM_HAVE_KQUEUE -#include -#include -#include -#else -#include -#endif - -#endif - -// On Linux kernels earlier than 2.6.37, epoll can't handle timeout values -// bigger than (LONG_MAX - 999ULL)/HZ. HZ in the wild can be as big as 1000, -// and LONG_MAX can be as small as (2**31)-1, so the largest number of -// milliseconds we can be sure to support on those early kernels is 2147482. -#if REALM_NETWORK_USE_EPOLL -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) -#define EPOLL_LARGE_TIMEOUT_BUG 1 -#endif -#endif - -using namespace realm::util; -using namespace realm::sync::network; - - -namespace { - -using native_handle_type = SocketBase::native_handle_type; - -#ifdef _WIN32 - -// This Winsock initialization call is required prior to any other Winsock API call -// made by the process. It is OK if a process calls it multiple times. -struct ProcessInitialization { - ProcessInitialization() - { - WSADATA wsaData; - int i = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (i != 0) { - throw std::system_error(i, std::system_category(), "WSAStartup() Winsock initialization failed"); - } - } - - ~ProcessInitialization() - { - // Must be called 1 time for each call to WSAStartup() that has taken place - WSACleanup(); - } -}; - -ProcessInitialization g_process_initialization; - -std::error_code make_winsock_error_code(int error_code) -{ - switch (error_code) { - case WSAEAFNOSUPPORT: - return make_basic_system_error_code(EAFNOSUPPORT); - case WSAEINVAL: - return make_basic_system_error_code(EINVAL); - case WSAECANCELLED: - return make_basic_system_error_code(ECANCELED); - case WSAECONNABORTED: - return make_basic_system_error_code(ECONNABORTED); - case WSAECONNRESET: - return make_basic_system_error_code(ECONNRESET); - case WSAEWOULDBLOCK: - return make_basic_system_error_code(EAGAIN); - } - - // Microsoft's STL can map win32 (and winsock!) error codes to known (posix-compatible) errc ones. - auto ec = std::system_category().default_error_condition(error_code); - if (ec.category() == std::generic_category()) - return make_basic_system_error_code(ec.value()); - return std::error_code(ec.value(), ec.category()); -} - -#endif // defined _WIN32 - -inline bool check_socket_error(int ret, std::error_code& ec) -{ -#ifdef _WIN32 - if (REALM_UNLIKELY(ret == SOCKET_ERROR)) { - ec = make_winsock_error_code(WSAGetLastError()); - return true; - } -#else - if (REALM_UNLIKELY(ret == -1)) { - ec = make_basic_system_error_code(errno); - return true; - } -#endif - return false; -} - -// Set file status flag O_NONBLOCK if `value` is true, otherwise clear it. -// -// Note that these flags are set at the file description level, and are therfore -// shared between duplicated descriptors (dup()). -// -// `ec` untouched on success. -std::error_code set_nonblock_flag(native_handle_type fd, bool value, std::error_code& ec) noexcept -{ -#ifdef _WIN32 - u_long flags = value ? 1 : 0; - int r = ioctlsocket(fd, FIONBIO, &flags); - if (r == SOCKET_ERROR) { - ec = make_winsock_error_code(WSAGetLastError()); - return ec; - } -#else - int flags = ::fcntl(fd, F_GETFL, 0); - if (REALM_UNLIKELY(flags == -1)) { - ec = make_basic_system_error_code(errno); - return ec; - } - flags &= ~O_NONBLOCK; - flags |= (value ? O_NONBLOCK : 0); - int ret = ::fcntl(fd, F_SETFL, flags); - if (REALM_UNLIKELY(ret == -1)) { - ec = make_basic_system_error_code(errno); - return ec; - } -#endif - - return std::error_code(); // Success -} - -// Set file status flag O_NONBLOCK. See set_nonblock_flag(int, bool, -// std::error_code&) for details. Throws std::system_error on failure. -void set_nonblock_flag(native_handle_type fd, bool value = true) -{ - std::error_code ec; - if (set_nonblock_flag(fd, value, ec)) - throw std::system_error(ec); -} - -// Set file descriptor flag FD_CLOEXEC if `value` is true, otherwise clear it. -// -// Note that this method of setting FD_CLOEXEC is subject to a race condition if -// another thread calls any of the exec functions concurrently. For that reason, -// this function should only be used when there is no better alternative. For -// example, Linux generally offers ways to set this flag atomically with the -// creation of a new file descriptor. -// -// `ec` untouched on success. -std::error_code set_cloexec_flag(native_handle_type fd, bool value, std::error_code& ec) noexcept -{ -#ifndef _WIN32 - int flags = ::fcntl(fd, F_GETFD, 0); - if (REALM_UNLIKELY(flags == -1)) { - ec = make_basic_system_error_code(errno); - return ec; - } - flags &= ~FD_CLOEXEC; - flags |= (value ? FD_CLOEXEC : 0); - int ret = ::fcntl(fd, F_SETFD, flags); - if (REALM_UNLIKELY(ret == -1)) { - ec = make_basic_system_error_code(errno); - return ec; - } -#endif - return std::error_code(); // Success -} - -// Set file descriptor flag FD_CLOEXEC. See set_cloexec_flag(int, bool, -// std::error_code&) for details. Throws std::system_error on failure. -REALM_UNUSED inline void set_cloexec_flag(native_handle_type fd, bool value = true) -{ - std::error_code ec; - if (set_cloexec_flag(fd, value, ec)) - throw std::system_error(ec); -} - - -inline void checked_close(native_handle_type fd) noexcept -{ -#ifdef _WIN32 - int status = closesocket(fd); - if (status == -1) { - BOOL b = CloseHandle((HANDLE)fd); - REALM_ASSERT(b || GetLastError() != ERROR_INVALID_HANDLE); - } -#else - int ret = ::close(fd); - // We can accept various errors from close(), but they must be ignored as - // the file descriptor is closed in any case (not necessarily according to - // POSIX, but we shall assume it anyway). `EBADF`, however, would indicate - // an implementation bug, so we don't want to ignore that. - REALM_ASSERT(ret != -1 || errno != EBADF); -#endif -} - - -class CloseGuard { -public: - CloseGuard() noexcept {} - explicit CloseGuard(native_handle_type fd) noexcept - : m_fd{fd} - { - REALM_ASSERT(fd != -1); - } - CloseGuard(CloseGuard&& cg) noexcept - : m_fd{cg.release()} - { - } - ~CloseGuard() noexcept - { - if (m_fd != -1) - checked_close(m_fd); - } - void reset(native_handle_type fd) noexcept - { - REALM_ASSERT(fd != -1); - if (m_fd != -1) - checked_close(m_fd); - m_fd = fd; - } - operator native_handle_type() const noexcept - { - return m_fd; - } - native_handle_type release() noexcept - { - native_handle_type fd = m_fd; - m_fd = -1; - return fd; - } - -private: - native_handle_type m_fd = -1; -}; - - -#ifndef _WIN32 - -class WakeupPipe { -public: - WakeupPipe() - { - int fildes[2]; -#if HAVE_LINUX_PIPE2 - int flags = O_CLOEXEC; - int ret = ::pipe2(fildes, flags); -#else - int ret = ::pipe(fildes); -#endif - if (REALM_UNLIKELY(ret == -1)) { - std::error_code ec = make_basic_system_error_code(errno); - throw std::system_error(ec); - } - m_read_fd.reset(fildes[0]); - m_write_fd.reset(fildes[1]); -#if !HAVE_LINUX_PIPE2 - set_cloexec_flag(m_read_fd); // Throws - set_cloexec_flag(m_write_fd); // Throws -#endif - } - - // Thread-safe. - int wait_fd() const noexcept - { - return m_read_fd; - } - - // Cause the wait descriptor (wait_fd()) to become readable within a short - // amount of time. - // - // Thread-safe. - void signal() noexcept - { - std::lock_guard lock{m_mutex}; - if (!m_signaled) { - char c = 0; - ssize_t ret = ::write(m_write_fd, &c, 1); - REALM_ASSERT_RELEASE(ret == 1); - m_signaled = true; - } - } - - // Must be called after the wait descriptor (wait_fd()) becomes readable. - // - // Thread-safe. - void acknowledge_signal() noexcept - { - std::lock_guard lock{m_mutex}; - if (m_signaled) { - char c; - ssize_t ret = ::read(m_read_fd, &c, 1); - REALM_ASSERT_RELEASE(ret == 1); - m_signaled = false; - } - } - -private: - CloseGuard m_read_fd, m_write_fd; - std::mutex m_mutex; - bool m_signaled = false; // Protected by `m_mutex`. -}; - -#else // defined _WIN32 - -class WakeupPipe { -public: - SOCKET wait_fd() const noexcept - { - return INVALID_SOCKET; - } - - void signal() noexcept - { - m_signal_count++; - } - - bool is_signaled() const noexcept - { - return m_signal_count > 0; - } - - void acknowledge_signal() noexcept - { - m_signal_count--; - } - -private: - std::atomic m_signal_count = 0; -}; - -#endif // defined _WIN32 - - -std::error_code translate_addrinfo_error(int err) noexcept -{ - switch (err) { - case EAI_AGAIN: - return ResolveErrors::host_not_found_try_again; - case EAI_BADFLAGS: - return error::invalid_argument; - case EAI_FAIL: - return ResolveErrors::no_recovery; - case EAI_FAMILY: - return error::address_family_not_supported; - case EAI_MEMORY: - return error::no_memory; - case EAI_NONAME: -#if defined(EAI_ADDRFAMILY) - case EAI_ADDRFAMILY: -#endif -#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) - case EAI_NODATA: -#endif - return ResolveErrors::host_not_found; - case EAI_SERVICE: - return ResolveErrors::service_not_found; - case EAI_SOCKTYPE: - return ResolveErrors::socket_type_not_supported; - default: - return error::unknown; - } -} - - -struct GetaddrinfoResultOwner { - struct addrinfo* ptr; - GetaddrinfoResultOwner(struct addrinfo* p) - : ptr{p} - { - } - ~GetaddrinfoResultOwner() noexcept - { - if (ptr) - freeaddrinfo(ptr); - } -}; - -} // unnamed namespace - - -class Service::IoReactor { -public: - IoReactor(); - ~IoReactor() noexcept; - - // Add an initiated I/O operation that did not complete immediately. - void add_oper(Descriptor&, LendersIoOperPtr, Want); - void remove_canceled_ops(Descriptor&, OperQueue& completed_ops) noexcept; - - bool wait_and_advance(clock::time_point timeout, clock::time_point now, bool& interrupted, - OperQueue& completed_ops); - - // The reactor is considered empty when no operations are currently managed - // by it. An operation is managed by a reactor if it was added through - // add_oper() and not yet passed out through `completed_ops` of - // wait_and_advance(). - bool empty() const noexcept; - - // Cause wait_and_advance() to return within a short amount of time. - // - // Thread-safe. - void interrupt() noexcept; - -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - void register_desc(Descriptor&); - void deregister_desc(Descriptor&) noexcept; -#endif - -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - clock::duration get_and_reset_sleep_time() noexcept; -#endif - -private: -#if REALM_NETWORK_USE_EPOLL - - static constexpr int s_epoll_event_buffer_size = 256; - const std::unique_ptr m_epoll_event_buffer; - const CloseGuard m_epoll_fd; - - static std::unique_ptr make_epoll_event_buffer(); - static CloseGuard make_epoll_fd(); - -#elif REALM_HAVE_KQUEUE // !REALM_NETWORK_USE_EPOLL && REALM_HAVE_KQUEUE - - static constexpr int s_kevent_buffer_size = 256; - const std::unique_ptr m_kevent_buffer; - const CloseGuard m_kqueue_fd; - - static std::unique_ptr make_kevent_buffer(); - static CloseGuard make_kqueue_fd(); - -#endif // !REALM_NETWORK_USE_EPOLL && REALM_HAVE_KQUEUE - -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - - OperQueue m_active_ops; - - // If there are already active operations, just activate as many additional - // operations as can be done without blocking. Otherwise, block until at - // least one operation can be activated or the timeout is reached. Then, if - // the timeout was not reached, activate as many additional operations as - // can be done without any further blocking. - // - // May occasionally return with no active operations and before the timeout - // has been reached, but this can be assumed to happen rarely enough that it - // will never amount to a performance problem. - // - // Argument `now` is unused if `timeout.time_since_epoch() <= 0`. - // - // Returns true if, and only if a wakeup pipe signal was - // received. Operations may already have been activated in this case. - bool wait_and_activate(clock::time_point timeout, clock::time_point now); - - void advance_active_ops(OperQueue& completed_ops) noexcept; - -#else // !(REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE) - - struct OperSlot { - std::size_t pollfd_slot_ndx = 0; // Zero when slot is unused - OperQueue read_ops, write_ops; - }; - - std::vector m_operations; // Indexed by file descriptor - - // First entry in `m_pollfd_slots` is always the read end of the wakeup - // pipe. There is then an additional entry for each entry in `m_operations` - // where `pollfd_slot_ndx` is nonzero. All entries always have `pollfd::fd` - // >= 0. - // - // INVARIANT: m_pollfd_slots.size() == 1 + N, where N is the number of - // entries in m_operations where pollfd_slot_ndx is nonzero. - std::vector m_pollfd_slots; - - void discard_pollfd_slot_by_move_last_over(OperSlot&) noexcept; - -#endif // !(REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE) - - std::size_t m_num_operations = 0; - WakeupPipe m_wakeup_pipe; - -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - clock::duration m_sleep_time = clock::duration::zero(); -#endif -}; - - -inline bool Service::IoReactor::empty() const noexcept -{ - return (m_num_operations == 0); -} - - -inline void Service::IoReactor::interrupt() noexcept -{ - m_wakeup_pipe.signal(); -} - - -#if REALM_NETWORK_USE_EPOLL - -inline Service::IoReactor::IoReactor() - : m_epoll_event_buffer{make_epoll_event_buffer()} // Throws - , m_epoll_fd{make_epoll_fd()} // Throws - , m_wakeup_pipe{} // Throws -{ - epoll_event event = epoll_event(); // Clear - event.events = EPOLLIN; - event.data.ptr = nullptr; - int ret = epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_wakeup_pipe.wait_fd(), &event); - if (REALM_UNLIKELY(ret == -1)) { - std::error_code ec = make_basic_system_error_code(errno); - throw std::system_error(ec); - } -} - - -inline Service::IoReactor::~IoReactor() noexcept {} - - -inline void Service::IoReactor::register_desc(Descriptor& desc) -{ - epoll_event event = epoll_event(); // Clear - event.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET; // Enable edge triggering - event.data.ptr = &desc; - int ret = epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, desc.m_fd, &event); - if (REALM_UNLIKELY(ret == -1)) { - std::error_code ec = make_basic_system_error_code(errno); - throw std::system_error(ec); - } -} - - -inline void Service::IoReactor::deregister_desc(Descriptor& desc) noexcept -{ - epoll_event event = epoll_event(); // Clear - int ret = epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, desc.m_fd, &event); - REALM_ASSERT(ret != -1); -} - - -inline std::unique_ptr Service::IoReactor::make_epoll_event_buffer() -{ - return std::make_unique(s_epoll_event_buffer_size); // Throws -} - - -inline CloseGuard Service::IoReactor::make_epoll_fd() -{ - int flags = 0; - flags |= EPOLL_CLOEXEC; - int ret = epoll_create1(flags); - if (REALM_UNLIKELY(ret == -1)) { - std::error_code ec = make_basic_system_error_code(errno); - throw std::system_error(ec); - } - int epoll_fd = ret; - return CloseGuard{epoll_fd}; -} - - -bool Service::IoReactor::wait_and_activate(clock::time_point timeout, clock::time_point now) -{ - int max_wait_millis = 0; - bool allow_blocking_wait = m_active_ops.empty(); - if (allow_blocking_wait) { - if (timeout.time_since_epoch().count() <= 0) { - max_wait_millis = -1; // Allow indefinite blocking - } - else if (now < timeout) { - auto diff = timeout - now; - int max_int_millis = std::numeric_limits::max(); - // 17592186044415 is the largest value (45-bit signed integer) - // garanteed to be supported by std::chrono::milliseconds. In the - // worst case, `int` is a 16-bit integer, meaning that we can only - // wait about 30 seconds at a time. In the best case - // (17592186044415) we can wait more than 500 years at a time. In - // the typical case (`int` has 32 bits), we can wait 24 days at a - // time. - long long max_chrono_millis = 17592186044415; - if (max_chrono_millis < max_int_millis) - max_int_millis = int(max_chrono_millis); -#if EPOLL_LARGE_TIMEOUT_BUG - long max_safe_millis = 2147482; // Circa 35 minutes - if (max_safe_millis < max_int_millis) - max_int_millis = int(max_safe_millis); -#endif - if (diff > std::chrono::milliseconds(max_int_millis)) { - max_wait_millis = max_int_millis; - } - else { - // Overflow is impossible here, due to the preceding check - auto diff_millis = std::chrono::duration_cast(diff); - // The conversion to milliseconds will round down if the tick - // period of `diff` is less than a millisecond, which it usually - // is. This is a problem, because it can lead to premature - // wakeups, which in turn could cause extranous iterations in - // the event loop. This is especially problematic when a small - // `diff` is rounded down to zero milliseconds, becuase that can - // easily produce a "busy wait" condition for up to a - // millisecond every time this happens. Obviously, the solution - // is to round up, instead of down. - if (diff_millis < diff) { - // Note that the following increment cannot overflow, - // because diff_millis < diff <= max_int_millis <= - // std::numeric_limits::max(). - ++diff_millis; - } - max_wait_millis = int(diff_millis.count()); - } - } - } - for (int i = 0; i < 2; ++i) { -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - clock::time_point sleep_start_time = clock::now(); -#endif - int ret = epoll_wait(m_epoll_fd, m_epoll_event_buffer.get(), s_epoll_event_buffer_size, max_wait_millis); - if (REALM_UNLIKELY(ret == -1)) { - int err = errno; - if (err == EINTR) - return false; // Infrequent premature return is ok - std::error_code ec = make_basic_system_error_code(err); - throw std::system_error(ec); - } - REALM_ASSERT(ret >= 0); -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - m_sleep_time += clock::now() - sleep_start_time; -#endif - int n = ret; - bool got_wakeup_pipe_signal = false; - for (int j = 0; j < n; ++j) { - const epoll_event& event = m_epoll_event_buffer[j]; - bool is_wakeup_pipe_signal = !event.data.ptr; - if (REALM_UNLIKELY(is_wakeup_pipe_signal)) { - m_wakeup_pipe.acknowledge_signal(); - got_wakeup_pipe_signal = true; - continue; - } - Descriptor& desc = *static_cast(event.data.ptr); - if ((event.events & (EPOLLIN | EPOLLHUP | EPOLLERR)) != 0) { - if (!desc.m_read_ready) { - desc.m_read_ready = true; - m_active_ops.push_back(desc.m_suspended_read_ops); - } - } - if ((event.events & (EPOLLOUT | EPOLLHUP | EPOLLERR)) != 0) { - if (!desc.m_write_ready) { - desc.m_write_ready = true; - m_active_ops.push_back(desc.m_suspended_write_ops); - } - } - if ((event.events & EPOLLRDHUP) != 0) - desc.m_imminent_end_of_input = true; - } - if (got_wakeup_pipe_signal) - return true; - if (n < s_epoll_event_buffer_size) - break; - max_wait_millis = 0; - } - return false; -} - - -#elif REALM_HAVE_KQUEUE // !REALM_NETWORK_USE_EPOLL && REALM_HAVE_KQUEUE - - -inline Service::IoReactor::IoReactor() - : m_kevent_buffer{make_kevent_buffer()} // Throws - , m_kqueue_fd{make_kqueue_fd()} // Throws - , m_wakeup_pipe{} // Throws -{ - struct kevent event; - EV_SET(&event, m_wakeup_pipe.wait_fd(), EVFILT_READ, EV_ADD, 0, 0, nullptr); - int ret = ::kevent(m_kqueue_fd, &event, 1, nullptr, 0, nullptr); - if (REALM_UNLIKELY(ret == -1)) { - std::error_code ec = make_basic_system_error_code(errno); - throw std::system_error(ec); - } -} - - -inline Service::IoReactor::~IoReactor() noexcept {} - - -inline void Service::IoReactor::register_desc(Descriptor& desc) -{ - struct kevent events[2]; - // EV_CLEAR enables edge-triggered behavior - EV_SET(&events[0], desc.m_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, &desc); - EV_SET(&events[1], desc.m_fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, &desc); - int ret = ::kevent(m_kqueue_fd, events, 2, nullptr, 0, nullptr); - if (REALM_UNLIKELY(ret == -1)) { - std::error_code ec = make_basic_system_error_code(errno); - throw std::system_error(ec); - } -} - - -inline void Service::IoReactor::deregister_desc(Descriptor& desc) noexcept -{ - struct kevent events[2]; - EV_SET(&events[0], desc.m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - EV_SET(&events[1], desc.m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - int ret = ::kevent(m_kqueue_fd, events, 2, nullptr, 0, nullptr); - REALM_ASSERT(ret != -1); -} - - -inline std::unique_ptr Service::IoReactor::make_kevent_buffer() -{ - return std::make_unique(s_kevent_buffer_size); // Throws -} - - -inline CloseGuard Service::IoReactor::make_kqueue_fd() -{ - int ret = ::kqueue(); - if (REALM_UNLIKELY(ret == -1)) { - std::error_code ec = make_basic_system_error_code(errno); - throw std::system_error(ec); - } - int epoll_fd = ret; - return CloseGuard{epoll_fd}; -} - - -bool Service::IoReactor::wait_and_activate(clock::time_point timeout, clock::time_point now) -{ - timespec max_wait_time{}; // Clear to zero - bool allow_blocking_wait = m_active_ops.empty(); - if (allow_blocking_wait) { - // Note that ::kevent() will silently clamp `max_wait_time` to 24 hours - // (86400 seconds), but that is ok, because the caller is prepared for - // premature return as long as it happens infrequently enough to not - // pose a performance problem. - constexpr std::time_t max_wait_seconds = 86400; - if (timeout.time_since_epoch().count() <= 0) { - max_wait_time.tv_sec = max_wait_seconds; - } - else if (now < timeout) { - auto diff = timeout - now; - auto secs = std::chrono::duration_cast(diff); - auto nsecs = std::chrono::duration_cast(diff - secs); - auto secs_2 = std::min(secs.count(), std::chrono::seconds::rep(max_wait_seconds)); - max_wait_time.tv_sec = std::time_t(secs_2); - max_wait_time.tv_nsec = long(nsecs.count()); - } - } - for (int i = 0; i < 4; ++i) { -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - clock::time_point sleep_start_time = clock::now(); -#endif - int ret = ::kevent(m_kqueue_fd, nullptr, 0, m_kevent_buffer.get(), s_kevent_buffer_size, &max_wait_time); - if (REALM_UNLIKELY(ret == -1)) { - int err = errno; - if (err == EINTR) - return false; // Infrequent premature return is ok - std::error_code ec = make_basic_system_error_code(err); - throw std::system_error(ec); - } - REALM_ASSERT(ret >= 0); -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - m_sleep_time += clock::now() - sleep_start_time; -#endif - int n = ret; - bool got_wakeup_pipe_signal = false; - for (int j = 0; j < n; ++j) { - const struct kevent& event = m_kevent_buffer[j]; - bool is_wakeup_pipe_signal = !event.udata; - if (REALM_UNLIKELY(is_wakeup_pipe_signal)) { - REALM_ASSERT(m_wakeup_pipe.wait_fd() == int(event.ident)); - m_wakeup_pipe.acknowledge_signal(); - got_wakeup_pipe_signal = true; - continue; - } - Descriptor& desc = *static_cast(event.udata); - REALM_ASSERT(desc.m_fd == int(event.ident)); - if (event.filter == EVFILT_READ) { - if (!desc.m_read_ready) { - desc.m_read_ready = true; - m_active_ops.push_back(desc.m_suspended_read_ops); - } - if ((event.flags & EV_EOF) != 0) - desc.m_imminent_end_of_input = true; - } - if (event.filter == EVFILT_WRITE) { - if (!desc.m_write_ready) { - desc.m_write_ready = true; - m_active_ops.push_back(desc.m_suspended_write_ops); - } - } - } - if (got_wakeup_pipe_signal) - return true; - if (n < s_kevent_buffer_size) - break; - // Clear to zero to disable blocking for any additional opportunistic - // event extractions. - max_wait_time = timespec{}; - } - return false; -} - -#endif // !REALM_NETWORK_USE_EPOLL && REALM_HAVE_KQUEUE - - -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - -void Service::IoReactor::add_oper(Descriptor& desc, LendersIoOperPtr op, Want want) -{ - if (REALM_UNLIKELY(!desc.m_is_registered)) { - register_desc(desc); // Throws - desc.m_is_registered = true; - } - - switch (want) { - case Want::read: - if (REALM_UNLIKELY(desc.m_read_ready)) - goto active; - desc.m_suspended_read_ops.push_back(std::move(op)); - goto proceed; - case Want::write: - if (REALM_UNLIKELY(desc.m_write_ready)) - goto active; - desc.m_suspended_write_ops.push_back(std::move(op)); - goto proceed; - case Want::nothing: - break; - } - REALM_ASSERT(false); - -active: - m_active_ops.push_back(std::move(op)); - -proceed: - ++m_num_operations; -} - - -void Service::IoReactor::remove_canceled_ops(Descriptor& desc, OperQueue& completed_ops) noexcept -{ - // Note: Canceled operations that are currently active (in m_active_ops) - // will be removed later by advance_active_ops(). - - while (LendersIoOperPtr op = desc.m_suspended_read_ops.pop_front()) { - completed_ops.push_back(std::move(op)); - --m_num_operations; - } - while (LendersIoOperPtr op = desc.m_suspended_write_ops.pop_front()) { - completed_ops.push_back(std::move(op)); - --m_num_operations; - } -} - - -bool Service::IoReactor::wait_and_advance(clock::time_point timeout, clock::time_point now, bool& interrupted, - OperQueue& completed_ops) -{ - clock::time_point now_2 = now; - for (;;) { - bool wakeup_pipe_signal = wait_and_activate(timeout, now_2); // Throws - if (REALM_UNLIKELY(wakeup_pipe_signal)) { - interrupted = true; - return false; - } - advance_active_ops(completed_ops); - if (!completed_ops.empty()) - return true; - if (timeout.time_since_epoch().count() > 0) { - now_2 = clock::now(); - bool timed_out = (now_2 >= timeout); - if (timed_out) - return false; - } - } -} - - -void Service::IoReactor::advance_active_ops(OperQueue& completed_ops) noexcept -{ - OperQueue new_active_ops; - while (LendersIoOperPtr op = m_active_ops.pop_front()) { - if (op->is_canceled()) { - completed_ops.push_back(std::move(op)); - --m_num_operations; - continue; - } - Want want = op->advance(); - switch (want) { - case Want::nothing: - REALM_ASSERT(op->is_complete()); - completed_ops.push_back(std::move(op)); - --m_num_operations; - continue; - case Want::read: { - Descriptor& desc = op->descriptor(); - if (REALM_UNLIKELY(desc.m_read_ready)) - goto still_active; - desc.m_suspended_read_ops.push_back(std::move(op)); - continue; - } - case Want::write: { - Descriptor& desc = op->descriptor(); - if (REALM_UNLIKELY(desc.m_write_ready)) - goto still_active; - desc.m_suspended_write_ops.push_back(std::move(op)); - continue; - } - } - REALM_ASSERT(false); - - still_active: - new_active_ops.push_back(std::move(op)); - } - m_active_ops.push_back(new_active_ops); -} - - -#else // !(REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE) - - -inline Service::IoReactor::IoReactor() - : m_wakeup_pipe{} // Throws -{ - pollfd slot = pollfd(); // Cleared slot - slot.fd = m_wakeup_pipe.wait_fd(); - slot.events = POLLRDNORM; - m_pollfd_slots.emplace_back(slot); // Throws -} - - -inline Service::IoReactor::~IoReactor() noexcept -{ -#if REALM_ASSERTIONS_ENABLED - std::size_t n = 0; - for (std::size_t i = 0; i < m_operations.size(); ++i) { - OperSlot& oper_slot = m_operations[i]; - while (oper_slot.read_ops.pop_front()) - ++n; - while (oper_slot.write_ops.pop_front()) - ++n; - } - REALM_ASSERT(n == m_num_operations); -#endif -} - - -void Service::IoReactor::add_oper(Descriptor& desc, LendersIoOperPtr op, Want want) -{ - native_handle_type fd = desc.m_fd; - - // Make sure there are enough slots in m_operations - { - std::size_t n = std::size_t(fd) + 1; // FIXME: Check for arithmetic overflow - if (m_operations.size() < n) - m_operations.resize(n); // Throws - } - - // Allocate a pollfd_slot unless we already have one - OperSlot& oper_slot = m_operations[fd]; - if (oper_slot.pollfd_slot_ndx == 0) { - pollfd pollfd_slot = pollfd(); // Cleared slot - pollfd_slot.fd = fd; - std::size_t pollfd_slot_ndx = m_pollfd_slots.size(); - REALM_ASSERT(pollfd_slot_ndx > 0); - m_pollfd_slots.emplace_back(pollfd_slot); // Throws - oper_slot.pollfd_slot_ndx = pollfd_slot_ndx; - } - - pollfd& pollfd_slot = m_pollfd_slots[oper_slot.pollfd_slot_ndx]; - REALM_ASSERT(pollfd_slot.fd == fd); - REALM_ASSERT(((pollfd_slot.events & POLLRDNORM) != 0) == !oper_slot.read_ops.empty()); - REALM_ASSERT(((pollfd_slot.events & POLLWRNORM) != 0) == !oper_slot.write_ops.empty()); - REALM_ASSERT((pollfd_slot.events & ~(POLLRDNORM | POLLWRNORM)) == 0); - switch (want) { - case Want::nothing: - break; - case Want::read: - pollfd_slot.events |= POLLRDNORM; - oper_slot.read_ops.push_back(std::move(op)); - goto finish; - case Want::write: - pollfd_slot.events |= POLLWRNORM; - oper_slot.write_ops.push_back(std::move(op)); - goto finish; - } - REALM_ASSERT(false); - return; - -finish: - ++m_num_operations; -} - - -void Service::IoReactor::remove_canceled_ops(Descriptor& desc, OperQueue& completed_ops) noexcept -{ - native_handle_type fd = desc.m_fd; - REALM_ASSERT(fd >= 0); - REALM_ASSERT(std::size_t(fd) < m_operations.size()); - OperSlot& oper_slot = m_operations[fd]; - REALM_ASSERT(oper_slot.pollfd_slot_ndx > 0); - REALM_ASSERT(!oper_slot.read_ops.empty() || !oper_slot.write_ops.empty()); - pollfd& pollfd_slot = m_pollfd_slots[oper_slot.pollfd_slot_ndx]; - REALM_ASSERT(pollfd_slot.fd == fd); - while (LendersIoOperPtr op = oper_slot.read_ops.pop_front()) { - completed_ops.push_back(std::move(op)); - --m_num_operations; - } - while (LendersIoOperPtr op = oper_slot.write_ops.pop_front()) { - completed_ops.push_back(std::move(op)); - --m_num_operations; - } - discard_pollfd_slot_by_move_last_over(oper_slot); -} - - -bool Service::IoReactor::wait_and_advance(clock::time_point timeout, clock::time_point now, bool& interrupted, - OperQueue& completed_ops) -{ -#ifdef _WIN32 - using nfds_type = std::size_t; -#else - using nfds_type = nfds_t; -#endif - clock::time_point now_2 = now; - std::size_t num_ready_descriptors = 0; - { - // std::vector guarantees contiguous storage - pollfd* fds = &m_pollfd_slots.front(); - nfds_type nfds = nfds_type(m_pollfd_slots.size()); - for (;;) { - int max_wait_millis = -1; // Wait indefinitely - if (timeout.time_since_epoch().count() > 0) { - if (now_2 >= timeout) - return false; // No operations completed - auto diff = timeout - now_2; - int max_int_millis = std::numeric_limits::max(); - // 17592186044415 is the largest value (45-bit signed integer) - // garanteed to be supported by std::chrono::milliseconds. In - // the worst case, `int` is a 16-bit integer, meaning that we - // can only wait about 30 seconds at a time. In the best case - // (17592186044415) we can wait more than 500 years at a - // time. In the typical case (`int` has 32 bits), we can wait 24 - // days at a time. - long long max_chrono_millis = 17592186044415; - if (max_int_millis > max_chrono_millis) - max_int_millis = int(max_chrono_millis); - if (diff > std::chrono::milliseconds(max_int_millis)) { - max_wait_millis = max_int_millis; - } - else { - // Overflow is impossible here, due to the preceeding check - auto diff_millis = std::chrono::duration_cast(diff); - // The conversion to milliseconds will round down if the - // tick period of `diff` is less than a millisecond, which - // it usually is. This is a problem, because it can lead to - // premature wakeups, which in turn could cause extranous - // iterations in the event loop. This is especially - // problematic when a small `diff` is rounded down to zero - // milliseconds, becuase that can easily produce a "busy - // wait" condition for up to a millisecond every time this - // happens. Obviously, the solution is to round up, instead - // of down. - if (diff_millis < diff) { - // Note that the following increment cannot overflow, - // because diff_millis < diff <= max_int_millis <= - // std::numeric_limits::max(). - ++diff_millis; - } - max_wait_millis = int(diff_millis.count()); - } - } - -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - clock::time_point sleep_start_time = clock::now(); -#endif - -#ifdef _WIN32 - max_wait_millis = 1000; - - // Windows does not have a single API call to wait for pipes and - // sockets with a timeout. So we repeatedly poll them individually - // in a loop until max_wait_millis has elapsed or an event happend. - // - // FIXME: Maybe switch to Windows IOCP instead. - - // Following variable is the poll time for the sockets in - // miliseconds. Adjust it to find a balance between CPU usage and - // response time: - constexpr INT socket_poll_timeout = 10; - - for (size_t t = 0; t < m_pollfd_slots.size(); t++) - m_pollfd_slots[t].revents = 0; - - using namespace std::chrono; - auto started = steady_clock::now(); - int ret = 0; - - for (;;) { - if (m_pollfd_slots.size() > 1) { - // Poll all network sockets - ret = WSAPoll(LPWSAPOLLFD(&m_pollfd_slots[1]), ULONG(m_pollfd_slots.size() - 1), - socket_poll_timeout); - REALM_ASSERT(ret != SOCKET_ERROR); - } - - if (m_wakeup_pipe.is_signaled()) { - m_pollfd_slots[0].revents = POLLIN; - ret++; - } - - if (ret != 0 || - (duration_cast(steady_clock::now() - started).count() >= max_wait_millis)) { - break; - } - - // If we don't have any sockets to poll for (m_pollfd_slots is less than 2) and no one signals - // the wakeup pipe, we'd be stuck busy waiting for either condition to become true. - std::this_thread::sleep_for(std::chrono::milliseconds(socket_poll_timeout)); - } - -#else // !defined _WIN32 - int ret = ::poll(fds, nfds, max_wait_millis); -#endif - bool interrupted_2 = false; - if (REALM_UNLIKELY(ret == -1)) { -#ifndef _WIN32 - int err = errno; - if (REALM_UNLIKELY(err != EINTR)) { - std::error_code ec = make_basic_system_error_code(err); - throw std::system_error(ec); - } -#endif - interrupted_2 = true; - } - -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - m_sleep_time += clock::now() - sleep_start_time; -#endif - - if (REALM_LIKELY(!interrupted_2)) { - REALM_ASSERT(ret >= 0); - num_ready_descriptors = ret; - break; - } - - // Retry on interruption by system signal - if (timeout.time_since_epoch().count() > 0) - now_2 = clock::now(); - } - } - - if (num_ready_descriptors == 0) - return false; // No operations completed - - // Check wake-up descriptor - if (m_pollfd_slots[0].revents != 0) { - REALM_ASSERT((m_pollfd_slots[0].revents & POLLNVAL) == 0); - m_wakeup_pipe.acknowledge_signal(); - interrupted = true; - return false; - } - - std::size_t orig_num_operations = m_num_operations; - std::size_t num_pollfd_slots = m_pollfd_slots.size(); - std::size_t pollfd_slot_ndx = 1; - while (pollfd_slot_ndx < num_pollfd_slots && num_ready_descriptors > 0) { - pollfd& pollfd_slot = m_pollfd_slots[pollfd_slot_ndx]; - REALM_ASSERT(pollfd_slot.fd >= 0); - if (REALM_LIKELY(pollfd_slot.revents == 0)) { - ++pollfd_slot_ndx; - continue; - } - --num_ready_descriptors; - - REALM_ASSERT((pollfd_slot.revents & POLLNVAL) == 0); - - // Treat errors like read and/or write-readiness - if ((pollfd_slot.revents & (POLLHUP | POLLERR)) != 0) { - REALM_ASSERT((pollfd_slot.events & (POLLRDNORM | POLLWRNORM)) != 0); - if ((pollfd_slot.events & POLLRDNORM) != 0) - pollfd_slot.revents |= POLLRDNORM; - if ((pollfd_slot.events & POLLWRNORM) != 0) - pollfd_slot.revents |= POLLWRNORM; - } - - OperSlot& oper_slot = m_operations[pollfd_slot.fd]; - REALM_ASSERT(oper_slot.pollfd_slot_ndx == pollfd_slot_ndx); - - OperQueue new_read_ops, new_write_ops; - auto advance_ops = [&](OperQueue& ops) noexcept { - while (LendersIoOperPtr op = ops.pop_front()) { - Want want = op->advance(); - switch (want) { - case Want::nothing: - REALM_ASSERT(op->is_complete()); - completed_ops.push_back(std::move(op)); - --m_num_operations; - continue; - case Want::read: - new_read_ops.push_back(std::move(op)); - continue; - case Want::write: - new_write_ops.push_back(std::move(op)); - continue; - } - REALM_ASSERT(false); - } - }; - - // Check read-readiness - if ((pollfd_slot.revents & POLLRDNORM) != 0) { - REALM_ASSERT(!oper_slot.read_ops.empty()); - advance_ops(oper_slot.read_ops); - pollfd_slot.events &= ~POLLRDNORM; - } - - // Check write-readiness - if ((pollfd_slot.revents & POLLWRNORM) != 0) { - REALM_ASSERT(!oper_slot.write_ops.empty()); - advance_ops(oper_slot.write_ops); - pollfd_slot.events &= ~POLLWRNORM; - } - - if (!new_read_ops.empty()) { - oper_slot.read_ops.push_back(new_read_ops); - pollfd_slot.events |= POLLRDNORM; - } - - if (!new_write_ops.empty()) { - oper_slot.write_ops.push_back(new_write_ops); - pollfd_slot.events |= POLLWRNORM; - } - - if (pollfd_slot.events == 0) { - discard_pollfd_slot_by_move_last_over(oper_slot); - --num_pollfd_slots; - } - else { - ++pollfd_slot_ndx; - } - } - - REALM_ASSERT(num_ready_descriptors == 0); - - bool any_operations_completed = (m_num_operations < orig_num_operations); - return any_operations_completed; -} - - -void Service::IoReactor::discard_pollfd_slot_by_move_last_over(OperSlot& oper_slot) noexcept -{ - std::size_t pollfd_slot_ndx = oper_slot.pollfd_slot_ndx; - oper_slot.pollfd_slot_ndx = 0; // Mark unused - if (pollfd_slot_ndx < m_pollfd_slots.size() - 1) { - pollfd& last_pollfd_slot = m_pollfd_slots.back(); - m_operations[last_pollfd_slot.fd].pollfd_slot_ndx = pollfd_slot_ndx; - m_pollfd_slots[pollfd_slot_ndx] = last_pollfd_slot; - } - m_pollfd_slots.pop_back(); -} - -#endif // !(REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE) - - -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - -auto Service::IoReactor::get_and_reset_sleep_time() noexcept -> clock::duration -{ - clock::duration sleep_time = m_sleep_time; - m_sleep_time = clock::duration::zero(); - return sleep_time; -} - -#endif // REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - - -class Service::Impl { -public: - Service& service; - IoReactor io_reactor; - - Impl(Service& s) - : service{s} - , io_reactor{} // Throws - { - } - - ~Impl() - { - bool resolver_thread_started = m_resolver_thread.joinable(); - if (resolver_thread_started) { - { - std::lock_guard lock{m_mutex}; - m_stop_resolver_thread = true; - m_resolver_cond.notify_all(); - } - m_resolver_thread.join(); - } - - // Avoid calls to recycle_post_oper() after destruction has begun. - m_completed_operations.clear(); - } - - void report_event_loop_metrics(util::UniqueFunction handler) - { -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - m_event_loop_metrics_timer.emplace(service); - m_event_loop_metrics_timer->async_wait( - std::chrono::seconds{30}, [this, handler = std::move(handler)](Status status) { - REALM_ASSERT(status.is_ok()); - clock::time_point now = clock::now(); - clock::duration elapsed_time = now - m_event_loop_metrics_start_time; - clock::duration sleep_time = io_reactor.get_and_reset_sleep_time(); - clock::duration nonsleep_time = elapsed_time - sleep_time; - double saturation = double(nonsleep_time.count()) / double(elapsed_time.count()); - clock::duration internal_exec_time = nonsleep_time - m_handler_exec_time; - internal_exec_time += now - m_handler_exec_start_time; - double inefficiency = double(internal_exec_time.count()) / double(elapsed_time.count()); - m_event_loop_metrics_start_time = now; - m_handler_exec_start_time = now; - m_handler_exec_time = clock::duration::zero(); - handler(saturation, inefficiency); // Throws - report_event_loop_metrics(std::move(handler)); // Throws - }); // Throws -#else - static_cast(handler); -#endif - } - - void run() - { - run_impl(true); - } - - void run_until_stopped() - { - run_impl(false); - } - - void stop() noexcept - { - { - std::lock_guard lock{m_mutex}; - if (m_stopped) - return; - m_stopped = true; - } - io_reactor.interrupt(); - } - - void reset() noexcept - { - std::lock_guard lock{m_mutex}; - m_stopped = false; - } - - static Endpoint::List resolve(const Resolver::Query&, std::error_code&); - - void add_resolve_oper(LendersResolveOperPtr op) - { - { - std::lock_guard lock{m_mutex}; - m_resolve_operations.push_back(std::move(op)); // Throws - m_resolver_cond.notify_all(); - } - bool resolver_thread_started = m_resolver_thread.joinable(); - if (resolver_thread_started) - return; - auto func = [this]() noexcept { - resolver_thread(); - }; - m_resolver_thread = std::thread{std::move(func)}; - } - - void add_wait_oper(LendersWaitOperPtr op) - { - m_wait_operations.push(std::move(op)); // Throws - } - - void post(PostOperConstr constr, std::size_t size, void* cookie) - { - { - std::lock_guard lock{m_mutex}; - std::unique_ptr mem; - if (m_post_oper && m_post_oper->m_size >= size) { - // Reuse old memory - AsyncOper* op = m_post_oper.release(); - REALM_ASSERT(dynamic_cast(op)); - static_cast(op)->UnusedOper::~UnusedOper(); // Static dispatch - mem.reset(static_cast(static_cast(op))); - } - else { - // Allocate new memory - mem.reset(new char[size]); // Throws - } - - std::unique_ptr op; - op.reset((*constr)(mem.get(), size, *this, cookie)); // Throws - mem.release(); - m_completed_operations_2.push_back(std::move(op)); - } - io_reactor.interrupt(); - } - - void recycle_post_oper(PostOperBase* op) noexcept - { - std::size_t size = op->m_size; - op->~PostOperBase(); // Dynamic dispatch - OwnersOperPtr op_2(new (op) UnusedOper(size)); // Does not throw - - // Keep the larger memory chunk (`op_2` or m_post_oper) - { - std::lock_guard lock{m_mutex}; - if (!m_post_oper || m_post_oper->m_size < size) - swap(op_2, m_post_oper); - } - } - - void trigger_exec(TriggerExecOperBase& op) noexcept - { - { - std::lock_guard lock{m_mutex}; - if (op.m_in_use) - return; - op.m_in_use = true; - bind_ptr op_2{&op}; // Increment use count - LendersOperPtr op_3{op_2.release()}; - m_completed_operations_2.push_back(std::move(op_3)); - } - io_reactor.interrupt(); - } - - void reset_trigger_exec(TriggerExecOperBase& op) noexcept - { - std::lock_guard lock{m_mutex}; - op.m_in_use = false; - } - - void add_completed_oper(LendersOperPtr op) noexcept - { - m_completed_operations.push_back(std::move(op)); - } - - void remove_canceled_ops(Descriptor& desc) noexcept - { - io_reactor.remove_canceled_ops(desc, m_completed_operations); - } - - void cancel_resolve_oper(ResolveOperBase& op) noexcept - { - std::lock_guard lock{m_mutex}; - op.cancel(); - } - - void cancel_incomplete_wait_oper(WaitOperBase& op) noexcept - { - auto p = std::equal_range(m_wait_operations.begin(), m_wait_operations.end(), op.m_expiration_time, - WaitOperCompare{}); - auto pred = [&op](const LendersWaitOperPtr& op_2) { - return &*op_2 == &op; - }; - auto i = std::find_if(p.first, p.second, pred); - REALM_ASSERT(i != p.second); - m_completed_operations.push_back(m_wait_operations.erase(i)); - } - -private: - OperQueue m_completed_operations; // Completed, canceled, and post operations - - struct WaitOperCompare { - bool operator()(const LendersWaitOperPtr& a, clock::time_point b) - { - return a->m_expiration_time > b; - } - bool operator()(clock::time_point a, const LendersWaitOperPtr& b) - { - return a > b->m_expiration_time; - } - bool operator()(const LendersWaitOperPtr& a, const LendersWaitOperPtr& b) - { - return a->m_expiration_time > b->m_expiration_time; - } - }; - - using WaitQueue = util::PriorityQueue, WaitOperCompare>; - WaitQueue m_wait_operations; - - std::mutex m_mutex; - OwnersOperPtr m_post_oper; // Protected by `m_mutex` - OperQueue m_resolve_operations; // Protected by `m_mutex` - OperQueue m_completed_operations_2; // Protected by `m_mutex` - bool m_stopped = false; // Protected by `m_mutex` - bool m_stop_resolver_thread = false; // Protected by `m_mutex` - bool m_resolve_in_progress = false; // Protected by `m_mutex` - std::condition_variable m_resolver_cond; // Protected by `m_mutex` - - std::thread m_resolver_thread; - -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - util::Optional m_event_loop_metrics_timer; - clock::time_point m_event_loop_metrics_start_time = clock::now(); - clock::time_point m_handler_exec_start_time; - clock::duration m_handler_exec_time = clock::duration::zero(); -#endif - void run_impl(bool return_when_idle) - { - bool no_incomplete_resolve_operations; - - on_handlers_executed_or_interrupted : { - std::lock_guard lock{m_mutex}; - if (m_stopped) - return; - // Note: Order of post operations must be preserved. - m_completed_operations.push_back(m_completed_operations_2); - no_incomplete_resolve_operations = (!m_resolve_in_progress && m_resolve_operations.empty()); - - if (m_completed_operations.empty()) - goto on_time_progressed; - } - - on_operations_completed : { -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - m_handler_exec_start_time = clock::now(); -#endif - while (LendersOperPtr op = m_completed_operations.pop_front()) - execute(op); // Throws -#ifdef REALM_UTIL_NETWORK_EVENT_LOOP_METRICS - m_handler_exec_time += clock::now() - m_handler_exec_start_time; -#endif - goto on_handlers_executed_or_interrupted; - } - - on_time_progressed : { - clock::time_point now = clock::now(); - if (process_timers(now)) - goto on_operations_completed; - - bool no_incomplete_operations = - (io_reactor.empty() && m_wait_operations.empty() && no_incomplete_resolve_operations); - if (no_incomplete_operations && return_when_idle) { - // We can only get to this point when there are no completion - // handlers ready to execute. It happens either because of a - // fall-through from on_operations_completed, or because of a - // jump to on_time_progressed, but that only happens if no - // completions handlers became ready during - // wait_and_process_io(). - // - // We can also only get to this point when there are no - // asynchronous operations in progress (due to the preceeding - // if-condition. - // - // It is possible that an other thread has added new post - // operations since we checked, but there is really no point in - // rechecking that, as it is always possible, even after a - // recheck, that new post handlers get added after we decide to - // return, but before we actually do return. Also, if would - // offer no additional guarantees to the application. - return; // Out of work - } - - // Blocking wait for I/O - bool interrupted = false; - if (wait_and_process_io(now, interrupted)) // Throws - goto on_operations_completed; - if (interrupted) - goto on_handlers_executed_or_interrupted; - goto on_time_progressed; - } - } - bool process_timers(clock::time_point now) - { - bool any_operations_completed = false; - for (;;) { - if (m_wait_operations.empty()) - break; - auto& op = m_wait_operations.top(); - if (now < op->m_expiration_time) - break; - op->complete(); - m_completed_operations.push_back(m_wait_operations.pop_top()); - any_operations_completed = true; - } - return any_operations_completed; - } - - bool wait_and_process_io(clock::time_point now, bool& interrupted) - { - clock::time_point timeout; - if (!m_wait_operations.empty()) - timeout = m_wait_operations.top()->m_expiration_time; - bool operations_completed = io_reactor.wait_and_advance(timeout, now, interrupted, - m_completed_operations); // Throws - return operations_completed; - } - - static void execute(LendersOperPtr& lenders_ptr) - { - lenders_ptr.release()->recycle_and_execute(); // Throws - } - - void resolver_thread() noexcept - { - LendersResolveOperPtr op; - for (;;) { - { - std::unique_lock lock{m_mutex}; - if (op) { - m_completed_operations_2.push_back(std::move(op)); - io_reactor.interrupt(); - } - m_resolve_in_progress = false; - while (m_resolve_operations.empty() && !m_stop_resolver_thread) - m_resolver_cond.wait(lock); - if (m_stop_resolver_thread) - return; - op = m_resolve_operations.pop_front(); - m_resolve_in_progress = true; - if (op->is_canceled()) - continue; - } - try { - op->m_endpoints = resolve(op->m_query, op->m_error_code); // Throws only std::bad_alloc - } - catch (std::bad_alloc&) { - op->m_error_code = make_basic_system_error_code(ENOMEM); - } - op->complete(); - } - } -}; - - -// This function promises to only ever throw std::bad_alloc. -Endpoint::List Service::Impl::resolve(const Resolver::Query& query, std::error_code& ec) -{ - Endpoint::List list; - - using addrinfo_type = struct addrinfo; - addrinfo_type hints = addrinfo_type(); // Clear - hints.ai_flags = query.m_flags; - hints.ai_family = query.m_protocol.m_family; - hints.ai_socktype = query.m_protocol.m_socktype; - hints.ai_protocol = query.m_protocol.m_protocol; - - const char* query_host = query.m_host.empty() ? 0 : query.m_host.c_str(); - const char* query_service = query.m_service.empty() ? 0 : query.m_service.c_str(); - struct addrinfo* first = nullptr; - int ret = ::getaddrinfo(query_host, query_service, &hints, &first); - if (REALM_UNLIKELY(ret != 0)) { -#ifdef EAI_SYSTEM - if (ret == EAI_SYSTEM) { - if (errno != 0) { - ec = make_basic_system_error_code(errno); - } - else { - ec = error::unknown; - } - return list; - } -#endif - ec = translate_addrinfo_error(ret); - return list; - } - - GetaddrinfoResultOwner gro(first); - - // Count number of IPv4/IPv6 endpoints - std::size_t num_endpoints = 0; - { - struct addrinfo* curr = first; - while (curr) { - bool ip_v4 = curr->ai_family == AF_INET; - bool ip_v6 = curr->ai_family == AF_INET6; - if (ip_v4 || ip_v6) - ++num_endpoints; - curr = curr->ai_next; - } - } - REALM_ASSERT(num_endpoints >= 1); - - // Copy the IPv4/IPv6 endpoints - list.m_endpoints.set_size(num_endpoints); // Throws - struct addrinfo* curr = first; - std::size_t endpoint_ndx = 0; - while (curr) { - bool ip_v4 = curr->ai_family == AF_INET; - bool ip_v6 = curr->ai_family == AF_INET6; - if (ip_v4 || ip_v6) { - REALM_ASSERT((ip_v4 && curr->ai_addrlen == sizeof(Endpoint::sockaddr_ip_v4_type)) || - (ip_v6 && curr->ai_addrlen == sizeof(Endpoint::sockaddr_ip_v6_type))); - Endpoint& ep = list.m_endpoints[endpoint_ndx]; - ep.m_protocol.m_family = curr->ai_family; - ep.m_protocol.m_socktype = curr->ai_socktype; - ep.m_protocol.m_protocol = curr->ai_protocol; - if (ip_v4) { - ep.m_sockaddr_union.m_ip_v4 = reinterpret_cast(*curr->ai_addr); - } - else { - ep.m_sockaddr_union.m_ip_v6 = reinterpret_cast(*curr->ai_addr); - } - ++endpoint_ndx; - } - curr = curr->ai_next; - } - - ec = std::error_code(); // Success - return list; -} - - -Service::Service() - : m_impl{std::make_unique(*this)} // Throws -{ -} - - -Service::~Service() noexcept {} - - -void Service::run() -{ - m_impl->run(); // Throws -} - - -void Service::run_until_stopped() -{ - m_impl->run_until_stopped(); -} - - -void Service::stop() noexcept -{ - m_impl->stop(); -} - - -void Service::reset() noexcept -{ - m_impl->reset(); -} - - -void Service::report_event_loop_metrics(util::UniqueFunction handler) -{ - m_impl->report_event_loop_metrics(std::move(handler)); // Throws -} - - -void Service::do_post(PostOperConstr constr, std::size_t size, void* cookie) -{ - m_impl->post(constr, size, cookie); // Throws -} - - -void Service::recycle_post_oper(Impl& impl, PostOperBase* op) noexcept -{ - impl.recycle_post_oper(op); -} - - -void Service::trigger_exec(Impl& impl, TriggerExecOperBase& op) noexcept -{ - impl.trigger_exec(op); -} - - -void Service::reset_trigger_exec(Impl& impl, TriggerExecOperBase& op) noexcept -{ - impl.reset_trigger_exec(op); -} - - -void Service::Descriptor::accept(Descriptor& desc, StreamProtocol protocol, Endpoint* ep, - std::error_code& ec) noexcept -{ - REALM_ASSERT(is_open()); - - union union_type { - Endpoint::sockaddr_union_type m_sockaddr_union; - char m_extra_byte[sizeof(Endpoint::sockaddr_union_type) + 1]; - }; - union_type buffer; - struct sockaddr* addr = &buffer.m_sockaddr_union.m_base; - socklen_t addr_len = sizeof buffer; - CloseGuard new_sock_fd; - for (;;) { -#if HAVE_LINUX_ACCEPT4 - // On Linux (HAVE_LINUX_ACCEPT4), make the accepted socket inherit the - // O_NONBLOCK status flag from the accepting socket to avoid an extra - // call to fcntl(). Note, it is deemed most likely that the accepted - // socket is going to be used in nonblocking when, and only when the - // accepting socket is used in nonblocking mode. Other platforms are - // handled below. - int flags = SOCK_CLOEXEC; - if (!in_blocking_mode()) - flags |= SOCK_NONBLOCK; - native_handle_type ret = ::accept4(m_fd, addr, &addr_len, flags); -#else - native_handle_type ret = ::accept(m_fd, addr, &addr_len); -#endif -#ifdef _WIN32 - if (ret == INVALID_SOCKET) { - int err = WSAGetLastError(); - if (err == WSAEINTR) - continue; // Retry on interruption by system signal - set_read_ready(err != WSAEWOULDBLOCK); - ec = make_winsock_error_code(err); // Failure - return; - } -#else - if (REALM_UNLIKELY(ret == -1)) { - int err = errno; - if (err == EINTR) - continue; // Retry on interruption by system signal - if (err == EWOULDBLOCK) - err = EAGAIN; - set_read_ready(err != EAGAIN); - ec = make_basic_system_error_code(err); // Failure - return; - } -#endif - new_sock_fd.reset(ret); - -#if REALM_PLATFORM_APPLE - int optval = 1; - ret = ::setsockopt(new_sock_fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof optval); - if (REALM_UNLIKELY(ret == -1)) { - // setsockopt() reports EINVAL if the other side disconnected while - // the connection was waiting in the listen queue. - int err = errno; - if (err == EINVAL) { - continue; - } - ec = make_basic_system_error_code(err); - return; - } -#endif - - set_read_ready(true); - break; - } - socklen_t expected_addr_len = - protocol.is_ip_v4() ? sizeof(Endpoint::sockaddr_ip_v4_type) : sizeof(Endpoint::sockaddr_ip_v6_type); - if (REALM_UNLIKELY(addr_len != expected_addr_len)) - REALM_TERMINATE("Unexpected peer address length"); - -#if !HAVE_LINUX_ACCEPT4 - { - bool value = true; - if (REALM_UNLIKELY(set_cloexec_flag(new_sock_fd, value, ec))) - return; - } -#endif - - // On some platforms (such as Mac OS X), the accepted socket automatically - // inherits file status flags from the accepting socket, but on other - // systems, this is not the case. In the case of Linux (HAVE_LINUX_ACCEPT4), - // the inheriting behaviour is obtained by using the Linux specific - // accept4() system call. - // - // For other platforms, we need to be sure that m_in_blocking_mode for the - // new socket is initialized to reflect the actual state of O_NONBLOCK on - // the new socket. - // - // Note: This implementation currently never modifies status flags other - // than O_NONBLOCK, so we only need to consider that flag. - -#if !REALM_PLATFORM_APPLE && !HAVE_LINUX_ACCEPT4 - // Make the accepted socket inherit the state of O_NONBLOCK from the - // accepting socket. - { - bool value = !m_in_blocking_mode; - if (::set_nonblock_flag(new_sock_fd, value, ec)) - return; - } -#endif - - desc.assign(new_sock_fd.release(), m_in_blocking_mode); - desc.set_write_ready(true); - if (ep) { - ep->m_protocol = protocol; - ep->m_sockaddr_union = buffer.m_sockaddr_union; - } - ec = std::error_code(); // Success -} - - -std::size_t Service::Descriptor::read_some(char* buffer, std::size_t size, std::error_code& ec) noexcept -{ - if (REALM_UNLIKELY(assume_read_would_block())) { - ec = error::resource_unavailable_try_again; // Failure - return 0; - } - for (;;) { - int flags = 0; -#ifdef _WIN32 - ssize_t ret = ::recv(m_fd, buffer, int(size), flags); - if (ret == SOCKET_ERROR) { - int err = WSAGetLastError(); - // Retry on interruption by system signal - if (err == WSAEINTR) - continue; - set_read_ready(err != WSAEWOULDBLOCK); - ec = make_winsock_error_code(err); // Failure - return 0; - } -#else - ssize_t ret = ::recv(m_fd, buffer, size, flags); - if (ret == -1) { - int err = errno; - // Retry on interruption by system signal - if (err == EINTR) - continue; - if (err == EWOULDBLOCK) - err = EAGAIN; - set_read_ready(err != EAGAIN); - ec = make_basic_system_error_code(err); // Failure - return 0; - } -#endif - if (REALM_UNLIKELY(ret == 0)) { - set_read_ready(true); - ec = MiscExtErrors::end_of_input; - return 0; - } - REALM_ASSERT(ret > 0); - std::size_t n = std::size_t(ret); - REALM_ASSERT(n <= size); -#if REALM_NETWORK_USE_EPOLL - // On Linux a partial read (n < size) on a nonblocking stream-mode - // socket is guaranteed to only ever happen if a complete read would - // have been impossible without blocking (i.e., without failing with - // EAGAIN/EWOULDBLOCK), or if the end of input from the remote peer was - // detected by the Linux kernel. - // - // Further more, after a partial read, and when working with Linux epoll - // in edge-triggered mode (EPOLLET), it is safe to suspend further - // reading until a new read-readiness notification is received, provided - // that we registered interest in EPOLLRDHUP events, and an EPOLLRDHUP - // event was not received prior to the partial read. This is safe in the - // sense that reading is guaranteed to be resumed in a timely fashion - // (without unnessesary blocking), and in a manner that is free of race - // conditions. Note in particular that if a read was partial because the - // kernel had detected the end of input prior to that read, but the - // EPOLLRDHUP event was not received prior the that read, then reading - // will still be resumed immediately by the pending EPOLLRDHUP event. - // - // Note that without this extra "loss of read-readiness" trigger, it - // would have been necessary for the caller to immediately follow up - // with an (otherwise redundant) additional invocation of read_some() - // just to detect the loss of read-readiness. - // - // FIXME: Will this scheme also work with Kqueue on FreeBSD and macOS? - // In particular, do we know that a partial read (n < size) on a - // nonblocking stream-mode socket is guaranteed to only ever happen if a - // complete read would have been impossible without blocking, or if the - // end of input from the remote peer was detected by the FreeBSD and/or - // macOS kernel? See http://stackoverflow.com/q/40123626/1698548. - set_read_ready(n == size || m_imminent_end_of_input); -#else - set_read_ready(true); -#endif - ec = std::error_code(); // Success - return n; - } -} - - -std::size_t Service::Descriptor::write_some(const char* data, std::size_t size, std::error_code& ec) noexcept -{ - if (REALM_UNLIKELY(assume_write_would_block())) { - ec = error::resource_unavailable_try_again; // Failure - return 0; - } - for (;;) { - int flags = 0; -#ifdef __linux__ - // Prevent SIGPIPE when remote peer has closed the connection. - flags |= MSG_NOSIGNAL; -#endif -#ifdef _WIN32 - ssize_t ret = ::send(m_fd, data, int(size), flags); - if (ret == SOCKET_ERROR) { - int err = WSAGetLastError(); - // Retry on interruption by system signal - if (err == WSAEINTR) - continue; - set_write_ready(err != WSAEWOULDBLOCK); - ec = make_winsock_error_code(err); // Failure - return 0; - } -#else - ssize_t ret = ::send(m_fd, data, size, flags); - if (ret == -1) { - int err = errno; - // Retry on interruption by system signal - if (err == EINTR) - continue; -#if REALM_PLATFORM_APPLE - // The macOS kernel can generate an undocumented EPROTOTYPE in - // certain cases where the peer has closed the connection (in - // tcp_usr_send() in bsd/netinet/tcp_usrreq.c) See also - // http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/. - if (REALM_UNLIKELY(err == EPROTOTYPE)) - err = EPIPE; -#endif - if (err == EWOULDBLOCK) - err = EAGAIN; - set_write_ready(err != EAGAIN); - ec = make_basic_system_error_code(err); // Failure - return 0; - } -#endif - REALM_ASSERT(ret >= 0); - std::size_t n = std::size_t(ret); - REALM_ASSERT(n <= size); -#if REALM_NETWORK_USE_EPOLL - // On Linux a partial write (n < size) on a nonblocking stream-mode - // socket is guaranteed to only ever happen if a complete write would - // have been impossible without blocking (i.e., without failing with - // EAGAIN/EWOULDBLOCK). - // - // Further more, after a partial write, and when working with Linux - // epoll in edge-triggered mode (EPOLLET), it is safe to suspend further - // writing until a new write-readiness notification is received. This is - // safe in the sense that writing is guaranteed to be resumed in a - // timely fashion (without unnessesary blocking), and in a manner that - // is free of race conditions. - // - // Note that without this extra "loss of write-readiness" trigger, it - // would have been necessary for the caller to immediately follow up - // with an (otherwise redundant) additional invocation of write_some() - // just to detect the loss of write-readiness. - // - // FIXME: Will this scheme also work with Kqueue on FreeBSD and macOS? - // In particular, do we know that a partial write (n < size) on a - // nonblocking stream-mode socket is guaranteed to only ever happen if a - // complete write would have been impossible without blocking? See - // http://stackoverflow.com/q/40123626/1698548. - set_write_ready(n == size); -#else - set_write_ready(true); -#endif - ec = std::error_code(); // Success - return n; - } -} - - -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - -void Service::Descriptor::deregister_for_async() noexcept -{ - service_impl.io_reactor.deregister_desc(*this); -} - -#endif // REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - - -void Service::Descriptor::set_nonblock_flag(bool value) -{ - ::set_nonblock_flag(m_fd, value); // Throws -} - - -void Service::Descriptor::add_initiated_oper(LendersIoOperPtr op, Want want) -{ - if (REALM_UNLIKELY(want == Want::nothing)) { - REALM_ASSERT(op->is_complete()); - service_impl.add_completed_oper(std::move(op)); - return; - } - REALM_ASSERT(!op->is_complete()); - service_impl.io_reactor.add_oper(*this, std::move(op), want); // Throws -} - - -void Service::Descriptor::do_close() noexcept -{ - checked_close(m_fd); - m_fd = -1; -} - - -auto Service::Descriptor::do_release() noexcept -> native_handle_type -{ - native_handle_type fd = m_fd; - m_fd = -1; - return fd; -} - - -Service& Resolver::get_service() noexcept -{ - return m_service_impl.service; -} - - -Endpoint::List Resolver::resolve(const Query& query, std::error_code& ec) -{ - return Service::Impl::resolve(query, ec); // Throws -} - - -void Resolver::cancel() noexcept -{ - if (m_resolve_oper && m_resolve_oper->in_use() && !m_resolve_oper->is_canceled()) { - Service::ResolveOperBase& op = static_cast(*m_resolve_oper); - m_service_impl.cancel_resolve_oper(op); - } -} - - -void Resolver::initiate_oper(Service::LendersResolveOperPtr op) -{ - m_service_impl.add_resolve_oper(std::move(op)); // Throws -} - - -Service& SocketBase::get_service() noexcept -{ - return m_desc.service_impl.service; -} - - -void SocketBase::cancel() noexcept -{ - bool any_incomplete = false; - if (m_read_oper && m_read_oper->in_use() && !m_read_oper->is_canceled()) { - m_read_oper->cancel(); - if (!m_read_oper->is_complete()) - any_incomplete = true; - } - if (m_write_oper && m_write_oper->in_use() && !m_write_oper->is_canceled()) { - m_write_oper->cancel(); - if (!m_write_oper->is_complete()) - any_incomplete = true; - } - if (any_incomplete) - m_desc.service_impl.remove_canceled_ops(m_desc); -} - - -std::error_code SocketBase::bind(const Endpoint& ep, std::error_code& ec) -{ - if (!is_open()) { - if (REALM_UNLIKELY(open(ep.protocol(), ec))) - return ec; - } - - native_handle_type sock_fd = m_desc.native_handle(); - socklen_t addr_len = - ep.m_protocol.is_ip_v4() ? sizeof(Endpoint::sockaddr_ip_v4_type) : sizeof(Endpoint::sockaddr_ip_v6_type); - - int ret = ::bind(sock_fd, &ep.m_sockaddr_union.m_base, addr_len); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return ec; - ec = std::error_code(); // Success - return ec; -} - - -Endpoint SocketBase::local_endpoint(std::error_code& ec) const -{ - Endpoint ep; - union union_type { - Endpoint::sockaddr_union_type m_sockaddr_union; - char m_extra_byte[sizeof(Endpoint::sockaddr_union_type) + 1]; - }; - native_handle_type sock_fd = m_desc.native_handle(); - union_type buffer; - struct sockaddr* addr = &buffer.m_sockaddr_union.m_base; - socklen_t addr_len = sizeof buffer; - int ret = ::getsockname(sock_fd, addr, &addr_len); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return ep; - - socklen_t expected_addr_len = - m_protocol.is_ip_v4() ? sizeof(Endpoint::sockaddr_ip_v4_type) : sizeof(Endpoint::sockaddr_ip_v6_type); - if (addr_len != expected_addr_len) - throw util::runtime_error("Unexpected local address length"); - ep.m_protocol = m_protocol; - ep.m_sockaddr_union = buffer.m_sockaddr_union; - ec = std::error_code(); // Success -#ifdef _WIN32 - ep.m_sockaddr_union.m_ip_v4.sin_addr.s_addr = inet_addr("127.0.0.1"); -#endif - return ep; -} - - -std::error_code SocketBase::open(const StreamProtocol& prot, std::error_code& ec) -{ - if (REALM_UNLIKELY(is_open())) - throw util::runtime_error("Socket is already open"); - int type = prot.m_socktype; -#if HAVE_LINUX_SOCK_CLOEXEC - type |= SOCK_CLOEXEC; -#endif - native_handle_type ret = ::socket(prot.m_family, type, prot.m_protocol); -#ifdef _WIN32 - if (REALM_UNLIKELY(ret == INVALID_SOCKET)) { - ec = make_winsock_error_code(WSAGetLastError()); - return ec; - } -#else - if (REALM_UNLIKELY(ret == -1)) { - ec = make_basic_system_error_code(errno); - return ec; - } -#endif - - CloseGuard sock_fd{ret}; - -#if !HAVE_LINUX_SOCK_CLOEXEC - { - bool value = true; - if (REALM_UNLIKELY(set_cloexec_flag(sock_fd, value, ec))) - return ec; - } -#endif - -#if REALM_PLATFORM_APPLE - { - int optval = 1; - int ret = setsockopt(sock_fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof optval); - if (REALM_UNLIKELY(ret == -1)) { - ec = make_basic_system_error_code(errno); - return ec; - } - } -#endif - - bool in_blocking_mode = true; // New sockets are in blocking mode by default - m_desc.assign(sock_fd.release(), in_blocking_mode); - m_protocol = prot; - ec = std::error_code(); // Success - return ec; -} - - -std::error_code SocketBase::do_assign(const StreamProtocol& prot, native_handle_type sock_fd, std::error_code& ec) -{ - if (REALM_UNLIKELY(is_open())) - throw util::runtime_error("Socket is already open"); - - // We need to know whether the specified socket is in blocking or in - // nonblocking mode. Rather than reading the current mode, we set it to - // blocking mode (disable nonblocking mode), and initialize - // `m_in_blocking_mode` to true. - { - bool value = false; - if (::set_nonblock_flag(sock_fd, value, ec)) - return ec; - } - - bool in_blocking_mode = true; // New sockets are in blocking mode by default - m_desc.assign(sock_fd, in_blocking_mode); - m_protocol = prot; - ec = std::error_code(); // Success - return ec; -} - - -void SocketBase::get_option(opt_enum opt, void* value_data, std::size_t& value_size, std::error_code& ec) const -{ - int level = 0; - int option_name = 0; - map_option(opt, level, option_name); - - native_handle_type sock_fd = m_desc.native_handle(); - socklen_t option_len = socklen_t(value_size); - int ret = ::getsockopt(sock_fd, level, option_name, static_cast(value_data), &option_len); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return; - value_size = std::size_t(option_len); - ec = std::error_code(); // Success -} - - -void SocketBase::set_option(opt_enum opt, const void* value_data, std::size_t value_size, std::error_code& ec) -{ - int level = 0; - int option_name = 0; - map_option(opt, level, option_name); - - native_handle_type sock_fd = m_desc.native_handle(); - int ret = ::setsockopt(sock_fd, level, option_name, static_cast(value_data), socklen_t(value_size)); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return; - ec = std::error_code(); // Success -} - - -void SocketBase::map_option(opt_enum opt, int& level, int& option_name) const -{ - switch (opt) { - case opt_ReuseAddr: - level = SOL_SOCKET; - option_name = SO_REUSEADDR; - return; - case opt_Linger: - level = SOL_SOCKET; -#if REALM_PLATFORM_APPLE - // By default, SO_LINGER on Darwin uses "ticks" instead of - // seconds for better accuracy, but we want to be cross-platform. - option_name = SO_LINGER_SEC; -#else - option_name = SO_LINGER; -#endif // REALM_PLATFORM_APPLE - return; - case opt_NoDelay: - level = IPPROTO_TCP; - option_name = TCP_NODELAY; // Specified by POSIX.1-2001 - return; - } - REALM_ASSERT(false); -} - - -std::error_code Socket::connect(const Endpoint& ep, std::error_code& ec) -{ - REALM_ASSERT(!m_write_oper || !m_write_oper->in_use()); - - if (!is_open()) { - if (REALM_UNLIKELY(open(ep.protocol(), ec))) - return ec; - } - - m_desc.ensure_blocking_mode(); // Throws - - native_handle_type sock_fd = m_desc.native_handle(); - socklen_t addr_len = - (ep.m_protocol.is_ip_v4() ? sizeof(Endpoint::sockaddr_ip_v4_type) : sizeof(Endpoint::sockaddr_ip_v6_type)); - int ret = ::connect(sock_fd, &ep.m_sockaddr_union.m_base, addr_len); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return ec; - ec = std::error_code(); // Success - return ec; -} - - -std::error_code Socket::shutdown(shutdown_type what, std::error_code& ec) -{ - native_handle_type sock_fd = m_desc.native_handle(); - int how = what; - int ret = ::shutdown(sock_fd, how); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return ec; - ec = std::error_code(); // Success - return ec; -} - - -bool Socket::initiate_async_connect(const Endpoint& ep, std::error_code& ec) -{ - if (!is_open()) { - if (REALM_UNLIKELY(open(ep.protocol(), ec))) - return true; // Failure - } - m_desc.ensure_nonblocking_mode(); // Throws - - // Initiate connect operation. - native_handle_type sock_fd = m_desc.native_handle(); - socklen_t addr_len = - ep.m_protocol.is_ip_v4() ? sizeof(Endpoint::sockaddr_ip_v4_type) : sizeof(Endpoint::sockaddr_ip_v6_type); - int ret = ::connect(sock_fd, &ep.m_sockaddr_union.m_base, addr_len); - if (ret != -1) { - ec = std::error_code(); // Success - return true; // Immediate completion. - } - - // EINPROGRESS (and on Windows, also WSAEWOULDBLOCK) indicates that the - // underlying connect operation was successfully initiated, but not - // immediately completed, and EALREADY indicates that an underlying connect - // operation was already initiated, and still not completed, presumably - // because a previous call to connect() or async_connect() failed, or was - // canceled. - -#ifdef _WIN32 - int err = WSAGetLastError(); - if (err != WSAEWOULDBLOCK) { - ec = make_winsock_error_code(err); - return true; // Failure - } -#else - int err = errno; - if (REALM_UNLIKELY(err != EINPROGRESS && err != EALREADY)) { - ec = make_basic_system_error_code(err); - return true; // Failure - } -#endif - - return false; // Successful initiation, but no immediate completion. -} - - -std::error_code Socket::finalize_async_connect(std::error_code& ec) noexcept -{ - native_handle_type sock_fd = m_desc.native_handle(); - int connect_errno = 0; - socklen_t connect_errno_size = sizeof connect_errno; - int ret = - ::getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&connect_errno), &connect_errno_size); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return ec; // getsockopt() failed - if (REALM_UNLIKELY(connect_errno)) { - ec = make_basic_system_error_code(connect_errno); - return ec; // connect failed - } - return std::error_code(); // Success -} - - -std::error_code Acceptor::listen(int backlog, std::error_code& ec) -{ - native_handle_type sock_fd = m_desc.native_handle(); - int ret = ::listen(sock_fd, backlog); - if (REALM_UNLIKELY(check_socket_error(ret, ec))) - return ec; - ec = std::error_code(); // Success - return ec; -} - - -Service& DeadlineTimer::get_service() noexcept -{ - return m_service_impl.service; -} - - -void DeadlineTimer::cancel() noexcept -{ - if (m_wait_oper && m_wait_oper->in_use() && !m_wait_oper->is_canceled()) { - m_wait_oper->cancel(); - if (!m_wait_oper->is_complete()) { - using WaitOperBase = Service::WaitOperBase; - WaitOperBase& wait_operation = static_cast(*m_wait_oper); - m_service_impl.cancel_incomplete_wait_oper(wait_operation); - } - } -} - - -void DeadlineTimer::initiate_oper(Service::LendersWaitOperPtr op) -{ - m_service_impl.add_wait_oper(std::move(op)); // Throws -} - - -bool ReadAheadBuffer::read(char*& begin, char* end, int delim, std::error_code& ec) noexcept -{ - std::size_t in_avail = m_end - m_begin; - std::size_t out_avail = end - begin; - std::size_t n = std::min(in_avail, out_avail); - // If n is 0, return whether or not the read expects 0 bytes for the completed response - if (n == 0) - return out_avail == 0; - - bool delim_mode = (delim != std::char_traits::eof()); - char* i = - (!delim_mode ? m_begin + n : std::find(m_begin, m_begin + n, std::char_traits::to_char_type(delim))); - begin = std::copy(m_begin, i, begin); - m_begin = i; - if (begin == end) { - if (delim_mode) - ec = MiscExtErrors::delim_not_found; - } - else { - if (m_begin == m_end) - return false; - REALM_ASSERT(delim_mode); - *begin++ = *m_begin++; // Transfer delimiter - } - return true; -} - - -namespace realm::sync::network { - -std::string host_name() -{ - // POSIX allows for gethostname() to report success even if the buffer is - // too small to hold the name, and in that case POSIX requires that the - // buffer is filled, but not that it contains a final null-termination. - char small_stack_buffer[256]; - int ret = ::gethostname(small_stack_buffer, sizeof small_stack_buffer); - if (ret != -1) { - // Check that a null-termination was included - char* end = small_stack_buffer + sizeof small_stack_buffer; - char* i = std::find(small_stack_buffer, end, 0); - if (i != end) - return std::string(small_stack_buffer, i); - } - constexpr std::size_t large_heap_buffer_size = 4096; - std::unique_ptr large_heap_buffer(new char[large_heap_buffer_size]); // Throws - ret = ::gethostname(large_heap_buffer.get(), large_heap_buffer_size); - if (REALM_LIKELY(ret != -1)) { - // Check that a null-termination was included - char* end = large_heap_buffer.get() + large_heap_buffer_size; - char* i = std::find(large_heap_buffer.get(), end, 0); - if (i != end) - return std::string(large_heap_buffer.get(), i); - } - throw std::system_error(errno, std::system_category(), "gethostname() failed"); -} - - -Address make_address(const char* c_str, std::error_code& ec) noexcept -{ - Address addr; - int ret = ::inet_pton(AF_INET6, c_str, &addr.m_union); - REALM_ASSERT(ret == 0 || ret == 1); - if (ret == 1) { - addr.m_is_ip_v6 = true; - ec = std::error_code(); // Success (IPv6) - return addr; - } - ret = ::inet_pton(AF_INET, c_str, &addr.m_union); - REALM_ASSERT(ret == 0 || ret == 1); - if (ret == 1) { - ec = std::error_code(); // Success (IPv4) - return addr; - } - ec = error::invalid_argument; - return Address(); - - // FIXME: Currently. `addr.m_ip_v6_scope_id` is always set to zero. It nees - // to be set based on a combined inspection of the original string - // representation, and the parsed address. The following code is "borrowed" - // from ASIO: - /* - *scope_id = 0; - if (const char* if_name = strchr(src, '%')) - { - in6_addr_type* ipv6_address = static_cast(dest); - bool is_link_local = ((ipv6_address->s6_addr[0] == 0xfe) - && ((ipv6_address->s6_addr[1] & 0xc0) == 0x80)); - bool is_multicast_link_local = ((ipv6_address->s6_addr[0] == 0xff) - && ((ipv6_address->s6_addr[1] & 0x0f) == 0x02)); - if (is_link_local || is_multicast_link_local) - *scope_id = if_nametoindex(if_name + 1); - if (*scope_id == 0) - *scope_id = atoi(if_name + 1); - } - */ -} - -class ResolveErrorCategory : public std::error_category { -public: - const char* name() const noexcept final - { - return "realm.sync.network.resolve"; - } - - std::string message(int value) const final - { - switch (ResolveErrors(value)) { - case ResolveErrors::host_not_found: - return "Host not found (authoritative)"; - case ResolveErrors::host_not_found_try_again: - return "Host not found (non-authoritative)"; - case ResolveErrors::no_data: - return "The query is valid but does not have associated address data"; - case ResolveErrors::no_recovery: - return "A non-recoverable error occurred"; - case ResolveErrors::service_not_found: - return "The service is not supported for the given socket type"; - case ResolveErrors::socket_type_not_supported: - return "The socket type is not supported"; - } - REALM_ASSERT(false); - return {}; - } -}; - -const std::error_category& resolve_error_category() noexcept -{ - static const ResolveErrorCategory resolve_error_category; - return resolve_error_category; -} - -std::error_code make_error_code(ResolveErrors err) -{ - return std::error_code(int(err), resolve_error_category()); -} - -} // namespace realm::sync::network diff --git a/src/realm/sync/network/network.hpp b/src/realm/sync/network/network.hpp deleted file mode 100644 index a08c843bd66..00000000000 --- a/src/realm/sync/network/network.hpp +++ /dev/null @@ -1,3592 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -#ifdef _WIN32 -#include -#include -#include -#include -#pragma comment(lib, "Ws2_32.lib") -#else -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Linux epoll -#if defined(REALM_USE_EPOLL) && !REALM_ANDROID -#define REALM_NETWORK_USE_EPOLL 1 -#else -#define REALM_NETWORK_USE_EPOLL 0 -#endif - -// FreeBSD Kqueue. -// -// Available on Mac OS X, FreeBSD, NetBSD, OpenBSD -#if (defined(__MACH__) && defined(__APPLE__)) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) -#if !defined(REALM_HAVE_KQUEUE) -#if !defined(REALM_DISABLE_UTIL_NETWORK_KQUEUE) -#define REALM_HAVE_KQUEUE 1 -#endif -#endif -#endif -#if !defined(REALM_HAVE_KQUEUE) -#define REALM_HAVE_KQUEUE 0 -#endif - - -// FIXME: Unfinished business around `Address::m_ip_v6_scope_id`. - -namespace realm::sync::network { - -/// \brief TCP/IP networking API. -/// -/// The design of this networking API is heavily inspired by the ASIO C++ -/// library (http://think-async.com). -/// -/// -/// ### Thread safety -/// -/// A *service context* is a set of objects consisting of an instance of -/// Service, and all the objects that are associated with that instance (\ref -/// Resolver, \ref Socket`, \ref Acceptor`, \ref DeadlineTimer, and -/// \ref ssl::Stream). -/// -/// In general, it is unsafe for two threads to call functions on the same -/// object, or on different objects in the same service context. This also -/// applies to destructors. Notable exceptions are the fully thread-safe -/// functions, such as Service::post(), Service::stop(), and Service::reset(). -/// -/// On the other hand, it is always safe for two threads to call functions on -/// objects belonging to different service contexts. -/// -/// One implication of these rules is that at most one thread must execute run() -/// at any given time, and if one thread is executing run(), then no other -/// thread is allowed to access objects in the same service context (with the -/// mentioned exceptions). -/// -/// Unless otherwise specified, free-standing objects, such as \ref -/// StreamProtocol, \ref Address, \ref Endpoint, and \ref Endpoint::List are -/// fully thread-safe as long as they are not mutated. If one thread is mutating -/// such an object, no other thread may access it. Note that these free-standing -/// objects are not associcated with an instance of Service, and are therefore -/// not part of a service context. -/// -/// -/// ### Comparison with ASIO -/// -/// There is a crucial difference between the two libraries in regards to the -/// guarantees that are provided about the cancelability of asynchronous -/// operations. The Realm networking library (this library) considers an -/// asynchronous operation to be complete precisely when the completion handler -/// starts to execute, and it guarantees that such an operation is cancelable up -/// until that point in time. In particular, if `cancel()` is called on a socket -/// or a deadline timer object before the completion handler starts to execute, -/// then that operation will be canceled, and will receive -/// `error::operation_aborted`. This guarantee is possible to provide (and free -/// of ambiguities) precisely because this library prohibits multiple threads -/// from executing the event loop concurrently, and because `cancel()` is -/// allowed to be called only from a completion handler (executed by the event -/// loop thread) or while no thread is executing the event loop. This guarantee -/// allows for safe destruction of sockets and deadline timers as long as the -/// completion handlers react appropriately to `error::operation_aborted`, in -/// particular, that they do not attempt to access the socket or deadline timer -/// object in such cases. -/// -/// ASIO, on the other hand, allows for an asynchronous operation to complete -/// and become **uncancellable** before the completion handler starts to -/// execute. For this reason, it is possible with ASIO to get the completion -/// handler of an asynchronous wait operation to start executing and receive an -/// error code other than "operation aborted" at a point in time where -/// `cancel()` has already been called on the deadline timer object, or even at -/// a point in timer where the deadline timer has been destroyed. This seems -/// like an inevitable consequence of the fact that ASIO allows for multiple -/// threads to execute the event loop concurrently. This generally forces ASIO -/// applications to invent ways of extending the lifetime of deadline timer and -/// socket objects until the completion handler starts executing. -/// -/// IMPORTANT: Even if ASIO is used in a way where at most one thread executes -/// the event loop, there is still no guarantee that an asynchronous operation -/// remains cancelable up until the point in time where the completion handler -/// starts to execute. - -std::string host_name(); - - -class StreamProtocol; -class Address; -class Endpoint; -class Service; -class Resolver; -class SocketBase; -class Socket; -class Acceptor; -class DeadlineTimer; -class ReadAheadBuffer; -namespace ssl { -class Stream; -} // namespace ssl - - -/// \brief An IP protocol descriptor. -class StreamProtocol { -public: - static StreamProtocol ip_v4(); - static StreamProtocol ip_v6(); - - bool is_ip_v4() const; - bool is_ip_v6() const; - - int protocol() const; - int family() const; - - StreamProtocol(); - ~StreamProtocol() noexcept {} - -private: - int m_family; - int m_socktype; - int m_protocol; - - friend class Service; - friend class SocketBase; -}; - - -/// \brief An IP address (IPv4 or IPv6). -class Address { -public: - bool is_ip_v4() const; - bool is_ip_v6() const; - - template - friend std::basic_ostream& operator<<(std::basic_ostream&, const Address&); - - Address(); - ~Address() noexcept {} - -private: - using ip_v4_type = in_addr; - using ip_v6_type = in6_addr; - union union_type { - ip_v4_type m_ip_v4; - ip_v6_type m_ip_v6; - }; - union_type m_union; - std::uint_least32_t m_ip_v6_scope_id = 0; - bool m_is_ip_v6 = false; - - friend Address make_address(const char*, std::error_code&) noexcept; - friend class Endpoint; -}; - -Address make_address(const char* c_str); -Address make_address(const char* c_str, std::error_code& ec) noexcept; -Address make_address(const std::string&); -Address make_address(const std::string&, std::error_code& ec) noexcept; - - -/// \brief An IP endpoint. -/// -/// An IP endpoint is a triplet (`protocol`, `address`, `port`). -class Endpoint { -public: - using port_type = std::uint_fast16_t; - class List; - - StreamProtocol protocol() const; - Address address() const; - port_type port() const; - - Endpoint(); - Endpoint(const StreamProtocol&, port_type); - Endpoint(const Address&, port_type); - ~Endpoint() noexcept {} - - using data_type = sockaddr; - data_type* data(); - const data_type* data() const; - -private: - StreamProtocol m_protocol; - - using sockaddr_base_type = sockaddr; - using sockaddr_ip_v4_type = sockaddr_in; - using sockaddr_ip_v6_type = sockaddr_in6; - union sockaddr_union_type { - sockaddr_base_type m_base; - sockaddr_ip_v4_type m_ip_v4; - sockaddr_ip_v6_type m_ip_v6; - }; - sockaddr_union_type m_sockaddr_union; - - friend class Service; - friend class Resolver; - friend class SocketBase; - friend class Socket; -}; - - -/// \brief A list of IP endpoints. -class Endpoint::List { -public: - using iterator = const Endpoint*; - - iterator begin() const noexcept; - iterator end() const noexcept; - std::size_t size() const noexcept; - bool empty() const noexcept; - - List() noexcept = default; - List(List&&) noexcept = default; - ~List() noexcept = default; - - List& operator=(List&&) noexcept = default; - -private: - util::Buffer m_endpoints; - - friend class Service; -}; - - -/// \brief TCP/IP networking service. -class Service { -public: - Service(); - ~Service() noexcept; - - /// \brief Execute the event loop. - /// - /// Execute completion handlers of completed asynchronous operations, or - /// wait for more completion handlers to become ready for - /// execution. Handlers submitted via post() are considered immeditely - /// ready. If there are no completion handlers ready for execution, and - /// there are no asynchronous operations in progress, run() returns. - /// - /// run_until_stopped() will continue running even if there are no completion - /// handlers ready for execution, and no asynchronous operations in progress, - /// until stop() is called. - /// - /// All completion handlers, including handlers submitted via post() will be - /// executed from run(), that is, by the thread that executes run(). If no - /// thread executes run(), then the completion handlers will not be - /// executed. - /// - /// Exceptions thrown by completion handlers will always propagate back - /// through run(). - /// - /// Syncronous operations (e.g., Socket::connect()) execute independently of - /// the event loop, and do not require that any thread calls run(). - void run(); - void run_until_stopped(); - - /// @{ \brief Stop event loop execution. - /// - /// stop() puts the event loop into the stopped mode. If a thread is - /// currently executing run(), it will be made to return in a timely - /// fashion, that is, without further blocking. If a thread is currently - /// blocked in run(), it will be unblocked. Handlers that can be executed - /// immediately, may, or may not be executed before run() returns, but new - /// handlers submitted by these, will not be executed before run() - /// returns. Also, if a handler is submitted by a call to post, and that - /// call happens after stop() returns, then that handler is guaranteed to - /// not be executed before run() returns (assuming that reset() is not called - /// before run() returns). - /// - /// The event loop will remain in the stopped mode until reset() is - /// called. If reset() is called before run() returns, it may, or may not - /// cause run() to resume normal operation without returning. - /// - /// Both stop() and reset() are thread-safe, that is, they may be called by - /// any thread. Also, both of these function may be called from completion - /// handlers (including posted handlers). - void stop() noexcept; - void reset() noexcept; - /// @} - - /// \brief Submit a handler to be executed by the event loop thread. - /// - /// Register the sepcified completion handler for immediate asynchronous - /// execution. The specified handler will be executed by an expression on - /// the form `handler(status)` where status is a Status object whose value - /// will always be OK, but may change in the future. If the handler object - /// is movable, it will never be copied. Otherwise, it will be copied as - /// necessary. - /// - /// This function is thread-safe, that is, it may be called by any - /// thread. It may also be called from other completion handlers. - /// - /// The handler will never be called as part of the execution of post(). It - /// will always be called by a thread that is executing run(). If no thread - /// is currently executing run(), the handler will not be executed until a - /// thread starts executing run(). If post() is called while another thread - /// is executing run(), the handler may be called before post() returns. If - /// post() is called from another completion handler, the submitted handler - /// is guaranteed to not be called during the execution of post(). - /// - /// Completion handlers added through post() will be executed in the order - /// that they are added. More precisely, if post() is called twice to add - /// two handlers, A and B, and the execution of post(A) ends before the - /// beginning of the execution of post(B), then A is guaranteed to execute - /// before B. - template - void post(H handler); - - /// Argument `saturation` is the fraction of time that is not spent - /// sleeping. Argument `inefficiency` is the fraction of time not spent - /// sleeping, and not spent executing completion handlers. Both values are - /// guaranteed to always be in the range 0 to 1 (both inclusive). The value - /// passed as `inefficiency` is guaranteed to always be less than, or equal - /// to the value passed as `saturation`. - using EventLoopMetricsHandler = void(double saturation, double inefficiency); - - /// \brief Report event loop metrics via the specified handler. - /// - /// The handler will be called approximately every 30 seconds. - /// - /// report_event_loop_metrics() must be called prior to any invocation of - /// run(). report_event_loop_metrics() is not thread-safe. - /// - /// This feature is only available if - /// `REALM_UTIL_NETWORK_EVENT_LOOP_METRICS` was defined during - /// compilation. When the feature is not available, the specified handler - /// will never be called. - void report_event_loop_metrics(util::UniqueFunction); - -private: - enum class Want { nothing = 0, read, write }; - - template - class OperQueue; - class Descriptor; - class AsyncOper; - class ResolveOperBase; - class WaitOperBase; - class TriggerExecOperBase; - class PostOperBase; - template - class PostOper; - class IoOper; - class UnusedOper; // Allocated, but currently unused memory - - template - class BasicStreamOps; - - struct OwnersOperDeleter { - void operator()(AsyncOper*) const noexcept; - }; - struct LendersOperDeleter { - void operator()(AsyncOper*) const noexcept; - }; - using OwnersOperPtr = std::unique_ptr; - using LendersOperPtr = std::unique_ptr; - using LendersResolveOperPtr = std::unique_ptr; - using LendersWaitOperPtr = std::unique_ptr; - using LendersIoOperPtr = std::unique_ptr; - - class IoReactor; - class Impl; - const std::unique_ptr m_impl; - - template - static std::unique_ptr alloc(OwnersOperPtr&, Args&&...); - - using PostOperConstr = PostOperBase*(void* addr, std::size_t size, Impl&, void* cookie); - void do_post(PostOperConstr, std::size_t size, void* cookie); - template - static PostOperBase* post_oper_constr(void* addr, std::size_t size, Impl&, void* cookie); - static void recycle_post_oper(Impl&, PostOperBase*) noexcept; - static void trigger_exec(Impl&, TriggerExecOperBase&) noexcept; - static void reset_trigger_exec(Impl&, TriggerExecOperBase&) noexcept; - - using clock = std::chrono::steady_clock; - - friend class Resolver; - friend class SocketBase; - friend class Socket; - friend class Acceptor; - friend class DeadlineTimer; - friend class ReadAheadBuffer; - friend class ssl::Stream; -}; - - -template -class Service::OperQueue { -public: - using LendersOperPtr = std::unique_ptr; - bool empty() const noexcept; - void push_back(LendersOperPtr) noexcept; - template - void push_back(OperQueue&) noexcept; - LendersOperPtr pop_front() noexcept; - void clear() noexcept; - OperQueue() noexcept = default; - OperQueue(OperQueue&&) noexcept; - ~OperQueue() noexcept - { - clear(); - } - -private: - Oper* m_back = nullptr; - template - friend class OperQueue; -}; - - -class Service::Descriptor { -public: -#ifdef _WIN32 - using native_handle_type = SOCKET; -#else - using native_handle_type = int; -#endif - - Impl& service_impl; - - Descriptor(Impl& service) noexcept; - ~Descriptor() noexcept; - - /// \param in_blocking_mode Must be true if, and only if the passed file - /// descriptor refers to a file description in which the file status flag - /// O_NONBLOCK is not set. - /// - /// The passed file descriptor must have the file descriptor flag FD_CLOEXEC - /// set. - void assign(native_handle_type fd, bool in_blocking_mode) noexcept; - void close() noexcept; - native_handle_type release() noexcept; - - bool is_open() const noexcept; - - native_handle_type native_handle() const noexcept; - bool in_blocking_mode() const noexcept; - - void accept(Descriptor&, StreamProtocol, Endpoint*, std::error_code&) noexcept; - std::size_t read_some(char* buffer, std::size_t size, std::error_code&) noexcept; - std::size_t write_some(const char* data, std::size_t size, std::error_code&) noexcept; - - /// \tparam Oper An operation type inherited from IoOper with an initate() - /// function that initiates the operation and figures out whether it needs - /// to read from, or write to the underlying descriptor to - /// proceed. `initiate()` must return Want::read if the operation needs to - /// read, or Want::write if the operation needs to write. If the operation - /// completes immediately (e.g. due to a failure during initialization), - /// `initiate()` must return Want::nothing. - template - void initiate_oper(std::unique_ptr, Args&&...); - - void ensure_blocking_mode(); - void ensure_nonblocking_mode(); - -private: - native_handle_type m_fd = -1; - bool m_in_blocking_mode; // Not in nonblocking mode - -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - bool m_read_ready; - bool m_write_ready; - bool m_imminent_end_of_input; // Kernel has seen the end of input - bool m_is_registered; - OperQueue m_suspended_read_ops, m_suspended_write_ops; - - void deregister_for_async() noexcept; -#endif - - bool assume_read_would_block() const noexcept; - bool assume_write_would_block() const noexcept; - - void set_read_ready(bool) noexcept; - void set_write_ready(bool) noexcept; - - void set_nonblock_flag(bool value); - void add_initiated_oper(LendersIoOperPtr, Want); - - void do_close() noexcept; - native_handle_type do_release() noexcept; - - friend class IoReactor; -}; - - -class Resolver { -public: - class Query; - - Resolver(Service&); - ~Resolver() noexcept; - - /// Thread-safe. - Service& get_service() noexcept; - - /// @{ \brief Resolve the specified query to one or more endpoints. - Endpoint::List resolve(const Query&); - Endpoint::List resolve(const Query&, std::error_code&); - /// @} - - /// \brief Perform an asynchronous resolve operation. - /// - /// Initiate an asynchronous resolve operation. The completion handler will - /// be called when the operation completes. The operation completes when it - /// succeeds, or an error occurs. - /// - /// The completion handler is always executed by the event loop thread, - /// i.e., by a thread that is executing Service::run(). Conversely, the - /// completion handler is guaranteed to not be called while no thread is - /// executing Service::run(). The execution of the completion handler is - /// always deferred to the event loop, meaning that it never happens as a - /// synchronous side effect of the execution of async_resolve(), even when - /// async_resolve() is executed by the event loop thread. The completion - /// handler is guaranteed to be called eventually, as long as there is time - /// enough for the operation to complete or fail, and a thread is executing - /// Service::run() for long enough. - /// - /// The operation can be canceled by calling cancel(), and will be - /// automatically canceled if the resolver object is destroyed. If the - /// operation is canceled, it will fail with `error::operation_aborted`. The - /// operation remains cancelable up until the point in time where the - /// completion handler starts to execute. This means that if cancel() is - /// called before the completion handler starts to execute, then the - /// completion handler is guaranteed to have `error::operation_aborted` - /// passed to it. This is true regardless of whether cancel() is called - /// explicitly or implicitly, such as when the resolver is destroyed. - /// - /// The specified handler will be executed by an expression on the form - /// `handler(ec, endpoints)` where `ec` is the error code and `endpoints` is - /// an object of type `Endpoint::List`. If the the handler object is - /// movable, it will never be copied. Otherwise, it will be copied as - /// necessary. - /// - /// It is an error to start a new resolve operation (synchronous or - /// asynchronous) while an asynchronous resolve operation is in progress via - /// the same resolver object. An asynchronous resolve operation is - /// considered complete as soon as the completion handler starts to - /// execute. This means that a new resolve operation can be started from the - /// completion handler. - template - void async_resolve(Query, H&& handler); - - /// \brief Cancel all asynchronous operations. - /// - /// Cause all incomplete asynchronous operations, that are associated with - /// this resolver (at most one), to fail with `error::operation_aborted`. An - /// asynchronous operation is complete precisely when its completion handler - /// starts executing. - /// - /// Completion handlers of canceled operations will become immediately ready - /// to execute, but will never be executed directly as part of the execution - /// of cancel(). - /// - /// Cancellation happens automatically when the resolver object is destroyed. - void cancel() noexcept; - -private: - template - class ResolveOper; - - Service::Impl& m_service_impl; - - Service::OwnersOperPtr m_resolve_oper; - - void initiate_oper(Service::LendersResolveOperPtr); -}; - - -class Resolver::Query { -public: - enum { - /// Locally bound socket endpoint (server side) - passive = AI_PASSIVE, - - /// Ignore families without a configured non-loopback address - address_configured = AI_ADDRCONFIG - }; - - Query(std::string service_port, int init_flags = passive | address_configured); - Query(const StreamProtocol&, std::string service_port, int init_flags = passive | address_configured); - Query(std::string host_name, std::string service_port, int init_flags = address_configured); - Query(const StreamProtocol&, std::string host_name, std::string service_port, - int init_flags = address_configured); - - ~Query() noexcept; - - int flags() const; - StreamProtocol protocol() const; - std::string host() const; - std::string service() const; - -private: - int m_flags; - StreamProtocol m_protocol; - std::string m_host; // hostname - std::string m_service; // port - - friend class Service; -}; - - -class SocketBase { -public: - using native_handle_type = Service::Descriptor::native_handle_type; - - ~SocketBase() noexcept; - - /// Thread-safe. - Service& get_service() noexcept; - - bool is_open() const noexcept; - native_handle_type native_handle() const noexcept; - - /// @{ \brief Open the socket for use with the specified protocol. - /// - /// It is an error to call open() on a socket that is already open. - void open(const StreamProtocol&); - std::error_code open(const StreamProtocol&, std::error_code&); - /// @} - - /// \brief Close this socket. - /// - /// If the socket is open, it will be closed. If it is already closed (or - /// never opened), this function does nothing (idempotency). - /// - /// A socket is automatically closed when destroyed. - /// - /// When the socket is closed, any incomplete asynchronous operation will be - /// canceled (as if cancel() was called). - void close() noexcept; - - /// \brief Cancel all asynchronous operations. - /// - /// Cause all incomplete asynchronous operations, that are associated with - /// this socket, to fail with `error::operation_aborted`. An asynchronous - /// operation is complete precisely when its completion handler starts - /// executing. - /// - /// Completion handlers of canceled operations will become immediately ready - /// to execute, but will never be executed directly as part of the execution - /// of cancel(). - void cancel() noexcept; - - template - void get_option(O& opt) const; - - template - std::error_code get_option(O& opt, std::error_code&) const; - - template - void set_option(const O& opt); - - template - std::error_code set_option(const O& opt, std::error_code&); - - void bind(const Endpoint&); - std::error_code bind(const Endpoint&, std::error_code&); - - Endpoint local_endpoint() const; - Endpoint local_endpoint(std::error_code&) const; - - /// Release the ownership of this socket object over the native handle and - /// return the native handle to the caller. The caller assumes ownership - /// over the returned handle. The socket is left in a closed - /// state. Incomplete asynchronous operations will be canceled as if close() - /// had been called. - /// - /// If called on a closed socket, this function is a no-op, and returns the - /// same value as would be returned by native_handle() - native_handle_type release_native_handle() noexcept; - -private: - enum opt_enum { - opt_ReuseAddr, ///< `SOL_SOCKET`, `SO_REUSEADDR` - opt_Linger, ///< `SOL_SOCKET`, `SO_LINGER` - opt_NoDelay, ///< `IPPROTO_TCP`, `TCP_NODELAY` (disable the Nagle algorithm) - }; - - template - class Option; - -public: - using reuse_address = Option; - using no_delay = Option; - - // linger struct defined by POSIX sys/socket.h. - struct linger_opt; - using linger = Option; - -protected: - Service::Descriptor m_desc; - -private: - StreamProtocol m_protocol; - -protected: - Service::OwnersOperPtr m_read_oper; // Read or accept - Service::OwnersOperPtr m_write_oper; // Write or connect - - SocketBase(Service&); - - const StreamProtocol& get_protocol() const noexcept; - std::error_code do_assign(const StreamProtocol&, native_handle_type, std::error_code&); - void do_close() noexcept; - - void get_option(opt_enum, void* value_data, std::size_t& value_size, std::error_code&) const; - void set_option(opt_enum, const void* value_data, std::size_t value_size, std::error_code&); - void map_option(opt_enum, int& level, int& option_name) const; - - friend class Acceptor; -}; - - -template -class SocketBase::Option { -public: - Option(T value = T()); - T value() const; - -private: - T m_value; - - void get(const SocketBase&, std::error_code&); - void set(SocketBase&, std::error_code&) const; - - friend class SocketBase; -}; - -struct SocketBase::linger_opt { - linger_opt(bool enable, int timeout_seconds = 0) - { - m_linger.l_onoff = enable ? 1 : 0; - m_linger.l_linger = timeout_seconds; - } - - ::linger m_linger; - - operator ::linger() const - { - return m_linger; - } - - bool enabled() const - { - return m_linger.l_onoff != 0; - } - int timeout() const - { - return m_linger.l_linger; - } -}; - - -/// Switching between synchronous and asynchronous operations is allowed, but -/// only in a nonoverlapping fashion. That is, a synchronous operation is not -/// allowed to run concurrently with an asynchronous one on the same -/// socket. Note that an asynchronous operation is considered to be running -/// until its completion handler starts executing. -class Socket : public SocketBase { -public: - Socket(Service&); - - /// \brief Create a socket with an already-connected native socket handle. - /// - /// This constructor is shorthand for creating the socket with the - /// one-argument constructor, and then calling the two-argument assign() - /// with the specified protocol and native handle. - Socket(Service&, const StreamProtocol&, native_handle_type); - - ~Socket() noexcept; - - void connect(const Endpoint&); - std::error_code connect(const Endpoint&, std::error_code&); - - /// @{ \brief Perform a synchronous read operation. - /// - /// read() will not return until the specified buffer is full, or an error - /// occurs. Reaching the end of input before the buffer is filled, is - /// considered an error, and will cause the operation to fail with - /// MiscExtErrors::end_of_input. - /// - /// read_until() will not return until the specified buffer contains the - /// specified delimiter, or an error occurs. If the buffer is filled before - /// the delimiter is found, the operation fails with - /// MiscExtErrors::delim_not_found. Otherwise, if the end of input is - /// reached before the delimiter is found, the operation fails with - /// MiscExtErrors::end_of_input. If the operation succeeds, the last byte - /// placed in the buffer is the delimiter. - /// - /// The versions that take a ReadAheadBuffer argument will read through that - /// buffer. This allows for fewer larger reads on the underlying - /// socket. Since unconsumed data may be left in the read-ahead buffer after - /// a read operation returns, it is important that the same read-ahead - /// buffer is passed to the next read operation. - /// - /// The versions of read() and read_until() that do not take an - /// `std::error_code&` argument will throw std::system_error on failure. - /// - /// The versions that do take an `std::error_code&` argument will set \a ec - /// to `std::error_code()` on success, and to something else on failure. On - /// failure they will return the number of bytes placed in the specified - /// buffer before the error occured. - /// - /// \return The number of bytes places in the specified buffer upon return. - std::size_t read(char* buffer, std::size_t size); - std::size_t read(char* buffer, std::size_t size, std::error_code& ec); - std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&); - std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&, std::error_code& ec); - std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&); - std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&, std::error_code& ec); - /// @} - - /// @{ \brief Perform a synchronous write operation. - /// - /// write() will not return until all the specified bytes have been written - /// to the socket, or an error occurs. - /// - /// The versions of write() that does not take an `std::error_code&` - /// argument will throw std::system_error on failure. When it succeeds, it - /// always returns \a size. - /// - /// The versions that does take an `std::error_code&` argument will set \a - /// ec to `std::error_code()` on success, and to something else on - /// failure. On success it returns \a size. On faulure it returns the number - /// of bytes written before the failure occured. - std::size_t write(const char* data, std::size_t size); - std::size_t write(const char* data, std::size_t size, std::error_code& ec); - /// @} - - /// @{ \brief Read at least one byte from this socket. - /// - /// If \a size is zero, both versions of read_some() will return zero - /// without blocking. Read errors may or may not be detected in this case. - /// - /// Otherwise, if \a size is greater than zero, and at least one byte is - /// immediately available, that is, without blocking, then both versions - /// will read at least one byte (but generally as many immediately available - /// bytes as will fit into the specified buffer), and return without - /// blocking. - /// - /// Otherwise, both versions will block the calling thread until at least one - /// byte becomes available, or an error occurs. - /// - /// In this context, it counts as an error, if the end of input is reached - /// before at least one byte becomes available (see - /// MiscExtErrors::end_of_input). - /// - /// If no error occurs, both versions will return the number of bytes placed - /// in the specified buffer, which is generally as many as are immediately - /// available at the time when the first byte becomes available, although - /// never more than \a size. - /// - /// If no error occurs, the three-argument version will set \a ec to - /// indicate success. - /// - /// If an error occurs, the two-argument version will throw - /// `std::system_error`, while the three-argument version will set \a ec to - /// indicate the error, and return zero. - /// - /// As long as \a size is greater than zero, the two argument version will - /// always return a value that is greater than zero, while the three - /// argument version will return a value greater than zero when, and only - /// when \a ec is set to indicate success (no error, and no end of input). - std::size_t read_some(char* buffer, std::size_t size); - std::size_t read_some(char* buffer, std::size_t size, std::error_code& ec); - /// @} - - /// @{ \brief Write at least one byte to this socket. - /// - /// If \a size is zero, both versions of write_some() will return zero - /// without blocking. Write errors may or may not be detected in this case. - /// - /// Otherwise, if \a size is greater than zero, and at least one byte can be - /// written immediately, that is, without blocking, then both versions will - /// write at least one byte (but generally as many as can be written - /// immediately), and return without blocking. - /// - /// Otherwise, both versions will block the calling thread until at least one - /// byte can be written, or an error occurs. - /// - /// If no error occurs, both versions will return the number of bytes - /// written, which is generally as many as can be written immediately at the - /// time when the first byte can be written. - /// - /// If no error occurs, the three-argument version will set \a ec to - /// indicate success. - /// - /// If an error occurs, the two-argument version will throw - /// `std::system_error`, while the three-argument version will set \a ec to - /// indicate the error, and return zero. - /// - /// As long as \a size is greater than zero, the two argument version will - /// always return a value that is greater than zero, while the three - /// argument version will return a value greater than zero when, and only - /// when \a ec is set to indicate success. - std::size_t write_some(const char* data, std::size_t size); - std::size_t write_some(const char* data, std::size_t size, std::error_code&); - /// @} - - /// \brief Perform an asynchronous connect operation. - /// - /// Initiate an asynchronous connect operation. The completion handler is - /// called when the operation completes. The operation completes when the - /// connection is established, or an error occurs. - /// - /// The completion handler is always executed by the event loop thread, - /// i.e., by a thread that is executing Service::run(). Conversely, the - /// completion handler is guaranteed to not be called while no thread is - /// executing Service::run(). The execution of the completion handler is - /// always deferred to the event loop, meaning that it never happens as a - /// synchronous side effect of the execution of async_connect(), even when - /// async_connect() is executed by the event loop thread. The completion - /// handler is guaranteed to be called eventually, as long as there is time - /// enough for the operation to complete or fail, and a thread is executing - /// Service::run() for long enough. - /// - /// The operation can be canceled by calling cancel(), and will be - /// automatically canceled if the socket is closed. If the operation is - /// canceled, it will fail with `error::operation_aborted`. The operation - /// remains cancelable up until the point in time where the completion - /// handler starts to execute. This means that if cancel() is called before - /// the completion handler starts to execute, then the completion handler is - /// guaranteed to have `error::operation_aborted` passed to it. This is true - /// regardless of whether cancel() is called explicitly or implicitly, such - /// as when the socket is destroyed. - /// - /// If the socket is not already open, it will be opened as part of the - /// connect operation as if by calling `open(ep.protocol())`. If the opening - /// operation succeeds, but the connect operation fails, the socket will be - /// left in the opened state. - /// - /// The specified handler will be executed by an expression on the form - /// `handler(ec)` where `ec` is the error code. If the the handler object is - /// movable, it will never be copied. Otherwise, it will be copied as - /// necessary. - /// - /// It is an error to start a new connect operation (synchronous or - /// asynchronous) while an asynchronous connect operation is in progress. An - /// asynchronous connect operation is considered complete as soon as the - /// completion handler starts to execute. - /// - /// \param ep The remote endpoint of the connection to be established. - template - void async_connect(const Endpoint& ep, H&& handler); - - /// @{ \brief Perform an asynchronous read operation. - /// - /// Initiate an asynchronous buffered read operation on the associated - /// socket. The completion handler will be called when the operation - /// completes, or an error occurs. - /// - /// async_read() will continue reading until the specified buffer is full, - /// or an error occurs. If the end of input is reached before the buffer is - /// filled, the operation fails with MiscExtErrors::end_of_input. - /// - /// async_read_until() will continue reading until the specified buffer - /// contains the specified delimiter, or an error occurs. If the buffer is - /// filled before a delimiter is found, the operation fails with - /// MiscExtErrors::delim_not_found. Otherwise, if the end of input is - /// reached before a delimiter is found, the operation fails with - /// MiscExtErrors::end_of_input. Otherwise, if the operation succeeds, the - /// last byte placed in the buffer is the delimiter. - /// - /// The versions that take a ReadAheadBuffer argument will read through that - /// buffer. This allows for fewer larger reads on the underlying - /// socket. Since unconsumed data may be left in the read-ahead buffer after - /// a read operation completes, it is important that the same read-ahead - /// buffer is passed to the next read operation. - /// - /// The completion handler is always executed by the event loop thread, - /// i.e., by a thread that is executing Service::run(). Conversely, the - /// completion handler is guaranteed to not be called while no thread is - /// executing Service::run(). The execution of the completion handler is - /// always deferred to the event loop, meaning that it never happens as a - /// synchronous side effect of the execution of async_read() or - /// async_read_until(), even when async_read() or async_read_until() is - /// executed by the event loop thread. The completion handler is guaranteed - /// to be called eventually, as long as there is time enough for the - /// operation to complete or fail, and a thread is executing Service::run() - /// for long enough. - /// - /// The operation can be canceled by calling cancel() on the associated - /// socket, and will be automatically canceled if the associated socket is - /// closed. If the operation is canceled, it will fail with - /// `error::operation_aborted`. The operation remains cancelable up until - /// the point in time where the completion handler starts to execute. This - /// means that if cancel() is called before the completion handler starts to - /// execute, then the completion handler is guaranteed to have - /// `error::operation_aborted` passed to it. This is true regardless of - /// whether cancel() is called explicitly or implicitly, such as when the - /// socket is destroyed. - /// - /// The specified handler will be executed by an expression on the form - /// `handler(ec, n)` where `ec` is the error code, and `n` is the number of - /// bytes placed in the buffer (of type `std::size_t`). `n` is guaranteed to - /// be less than, or equal to \a size. If the the handler object is movable, - /// it will never be copied. Otherwise, it will be copied as necessary. - /// - /// It is an error to start a read operation before the associated socket is - /// connected. - /// - /// It is an error to start a new read operation (synchronous or - /// asynchronous) while an asynchronous read operation is in progress. An - /// asynchronous read operation is considered complete as soon as the - /// completion handler starts executing. This means that a new read - /// operation can be started from the completion handler of another - /// asynchronous buffered read operation. - template - void async_read(char* buffer, std::size_t size, H&& handler); - template - void async_read(char* buffer, std::size_t size, ReadAheadBuffer&, H&& handler); - template - void async_read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&, H&& handler); - /// @} - - /// \brief Perform an asynchronous write operation. - /// - /// Initiate an asynchronous write operation. The completion handler is - /// called when the operation completes. The operation completes when all - /// the specified bytes have been written to the socket, or an error occurs. - /// - /// The completion handler is always executed by the event loop thread, - /// i.e., by a thread that is executing Service::run(). Conversely, the - /// completion handler is guaranteed to not be called while no thread is - /// executing Service::run(). The execution of the completion handler is - /// always deferred to the event loop, meaning that it never happens as a - /// synchronous side effect of the execution of async_write(), even when - /// async_write() is executed by the event loop thread. The completion - /// handler is guaranteed to be called eventually, as long as there is time - /// enough for the operation to complete or fail, and a thread is executing - /// Service::run() for long enough. - /// - /// The operation can be canceled by calling cancel(), and will be - /// automatically canceled if the socket is closed. If the operation is - /// canceled, it will fail with `error::operation_aborted`. The operation - /// remains cancelable up until the point in time where the completion - /// handler starts to execute. This means that if cancel() is called before - /// the completion handler starts to execute, then the completion handler is - /// guaranteed to have `error::operation_aborted` passed to it. This is true - /// regardless of whether cancel() is called explicitly or implicitly, such - /// as when the socket is destroyed. - /// - /// The specified handler will be executed by an expression on the form - /// `handler(ec, n)` where `ec` is the error code, and `n` is the number of - /// bytes written (of type `std::size_t`). If the the handler object is - /// movable, it will never be copied. Otherwise, it will be copied as - /// necessary. - /// - /// It is an error to start an asynchronous write operation before the - /// socket is connected. - /// - /// It is an error to start a new write operation (synchronous or - /// asynchronous) while an asynchronous write operation is in progress. An - /// asynchronous write operation is considered complete as soon as the - /// completion handler starts to execute. This means that a new write - /// operation can be started from the completion handler of another - /// asynchronous write operation. - template - void async_write(const char* data, std::size_t size, H&& handler); - - template - void async_read_some(char* buffer, std::size_t size, H&& handler); - template - void async_write_some(const char* data, std::size_t size, H&& handler); - - enum shutdown_type { -#ifdef _WIN32 - /// Shutdown the receiving side of the socket. - shutdown_receive = SD_RECEIVE, - - /// Shutdown the sending side of the socket. - shutdown_send = SD_SEND, - - /// Shutdown both sending and receiving side of the socket. - shutdown_both = SD_BOTH -#else - shutdown_receive = SHUT_RD, - shutdown_send = SHUT_WR, - shutdown_both = SHUT_RDWR -#endif - }; - - /// @{ \brief Shut down the connected sockets sending and/or receiving - /// side. - /// - /// It is an error to call this function when the socket is not both open - /// and connected. - void shutdown(shutdown_type); - std::error_code shutdown(shutdown_type, std::error_code&); - /// @} - - /// @{ \brief Initialize socket with an already-connected native socket - /// handle. - /// - /// The specified native handle must refer to a socket that is already fully - /// open and connected. - /// - /// If the assignment operation succeeds, this socket object has taken - /// ownership of the specified native handle, and the handle will be closed - /// when the socket object is destroyed, (or when close() is called). If the - /// operation fails, the caller still owns the specified native handle. - /// - /// It is an error to call connect() or async_connect() on a socket object - /// that is initialized this way (unless it is first closed). - /// - /// It is an error to call this function on a socket object that is already - /// open. - void assign(const StreamProtocol&, native_handle_type); - std::error_code assign(const StreamProtocol&, native_handle_type, std::error_code&); - /// @} - - /// Returns a reference to this socket, as this socket is the lowest layer - /// of a stream. - Socket& lowest_layer() noexcept; - -private: - using Want = Service::Want; - using StreamOps = Service::BasicStreamOps; - - class ConnectOperBase; - template - class ConnectOper; - - using LendersConnectOperPtr = std::unique_ptr; - - // `ec` untouched on success, but no immediate completion - bool initiate_async_connect(const Endpoint&, std::error_code& ec); - // `ec` untouched on success - std::error_code finalize_async_connect(std::error_code& ec) noexcept; - - // See Service::BasicStreamOps for details on these these 6 functions. - void do_init_read_async(std::error_code&, Want&) noexcept; - void do_init_write_async(std::error_code&, Want&) noexcept; - std::size_t do_read_some_sync(char* buffer, std::size_t size, std::error_code&) noexcept; - std::size_t do_write_some_sync(const char* data, std::size_t size, std::error_code&) noexcept; - std::size_t do_read_some_async(char* buffer, std::size_t size, std::error_code&, Want&) noexcept; - std::size_t do_write_some_async(const char* data, std::size_t size, std::error_code&, Want&) noexcept; - - friend class Service::BasicStreamOps; - friend class Service::BasicStreamOps; - friend class ReadAheadBuffer; - friend class ssl::Stream; -}; - - -/// Switching between synchronous and asynchronous operations is allowed, but -/// only in a nonoverlapping fashion. That is, a synchronous operation is not -/// allowed to run concurrently with an asynchronous one on the same -/// acceptor. Note that an asynchronous operation is considered to be running -/// until its completion handler starts executing. -class Acceptor : public SocketBase { -public: - Acceptor(Service&); - ~Acceptor() noexcept; - - static constexpr int max_connections = SOMAXCONN; - - void listen(int backlog = max_connections); - std::error_code listen(int backlog, std::error_code&); - - void accept(Socket&); - void accept(Socket&, Endpoint&); - std::error_code accept(Socket&, std::error_code&); - std::error_code accept(Socket&, Endpoint&, std::error_code&); - - /// @{ \brief Perform an asynchronous accept operation. - /// - /// Initiate an asynchronous accept operation. The completion handler will - /// be called when the operation completes. The operation completes when the - /// connection is accepted, or an error occurs. If the operation succeeds, - /// the specified local socket will have become connected to a remote - /// socket. - /// - /// The completion handler is always executed by the event loop thread, - /// i.e., by a thread that is executing Service::run(). Conversely, the - /// completion handler is guaranteed to not be called while no thread is - /// executing Service::run(). The execution of the completion handler is - /// always deferred to the event loop, meaning that it never happens as a - /// synchronous side effect of the execution of async_accept(), even when - /// async_accept() is executed by the event loop thread. The completion - /// handler is guaranteed to be called eventually, as long as there is time - /// enough for the operation to complete or fail, and a thread is executing - /// Service::run() for long enough. - /// - /// The operation can be canceled by calling cancel(), and will be - /// automatically canceled if the acceptor is closed. If the operation is - /// canceled, it will fail with `error::operation_aborted`. The operation - /// remains cancelable up until the point in time where the completion - /// handler starts to execute. This means that if cancel() is called before - /// the completion handler starts to execute, then the completion handler is - /// guaranteed to have `error::operation_aborted` passed to it. This is true - /// regardless of whether cancel() is called explicitly or implicitly, such - /// as when the acceptor is destroyed. - /// - /// The specified handler will be executed by an expression on the form - /// `handler(ec)` where `ec` is the error code. If the the handler object is - /// movable, it will never be copied. Otherwise, it will be copied as - /// necessary. - /// - /// It is an error to start a new accept operation (synchronous or - /// asynchronous) while an asynchronous accept operation is in progress. An - /// asynchronous accept operation is considered complete as soon as the - /// completion handler starts executing. This means that a new accept - /// operation can be started from the completion handler. - /// - /// \param sock This is the local socket, that upon successful completion - /// will have become connected to the remote socket. It must be in the - /// closed state (Socket::is_open()) when async_accept() is called. - /// - template - void async_accept(Socket& sock, H&& handler); - /// \param ep Upon completion, the remote peer endpoint will have been - /// assigned to this variable. - template - void async_accept(Socket& sock, Endpoint& ep, H&& handler); - /// @} - -private: - using Want = Service::Want; - - class AcceptOperBase; - template - class AcceptOper; - - using LendersAcceptOperPtr = std::unique_ptr; - - std::error_code accept(Socket&, Endpoint*, std::error_code&); - Want do_accept_async(Socket&, Endpoint*, std::error_code&) noexcept; - - template - void async_accept(Socket&, Endpoint*, H&&); -}; - - -/// \brief A timer object supporting asynchronous wait operations. -/// -/// NOTE: The DeadlineTimer is not thread safe and async_wait() or cancel() -/// must be called prior to calling run() or directly from the event loop. -class DeadlineTimer { -public: - DeadlineTimer(Service&); - ~DeadlineTimer() noexcept; - - /// Thread-safe. - Service& get_service() noexcept; - - /// \brief Perform an asynchronous wait operation. - /// - /// Initiate an asynchronous wait operation. The completion handler becomes - /// ready to execute when the expiration time is reached, or an error occurs - /// (cancellation counts as an error here). The expiration time is the time - /// of initiation plus the specified delay. The error code passed to the - /// completion handler will **never** indicate success, unless the - /// expiration time was reached. - /// - /// The completion handler is always executed by the event loop thread, - /// i.e., by a thread that is executing Service::run(). Conversely, the - /// completion handler is guaranteed to not be called while no thread is - /// executing Service::run(). The execution of the completion handler is - /// always deferred to the event loop, meaning that it never happens as a - /// synchronous side effect of the execution of async_wait(), even when - /// async_wait() is executed by the event loop thread. The completion - /// handler is guaranteed to be called eventually, as long as there is time - /// enough for the operation to complete or fail, and a thread is executing - /// Service::run() for long enough. - /// - /// The operation can be canceled by calling cancel(), and will be - /// automatically canceled if the timer is destroyed. If the operation is - /// canceled, it will fail with `ErrorCodes::OperationAborted`. The operation - /// remains cancelable up until the point in time where the completion - /// handler starts to execute. This means that if cancel() is called before - /// the completion handler starts to execute, then the completion handler is - /// guaranteed to have `ErrorCodes::OperationAborted` passed to it. This is true - /// regardless of whether cancel() is called explicitly or implicitly, such - /// as when the timer is destroyed. - /// - /// The specified handler will be executed by an expression on the form - /// `handler(status)` where `status` is a Status object. If the handler object - /// is movable, it will never be copied. Otherwise, it will be copied as - /// necessary. - /// - /// It is an error to start a new asynchronous wait operation while an - /// another one is in progress. An asynchronous wait operation is in - /// progress until its completion handler starts executing. - template - void async_wait(std::chrono::duration delay, H&& handler); - - /// \brief Cancel an asynchronous wait operation. - /// - /// If an asynchronous wait operation, that is associated with this deadline - /// timer, is in progress, cause it to fail with - /// `error::operation_aborted`. An asynchronous wait operation is in - /// progress until its completion handler starts executing. - /// - /// Completion handlers of canceled operations will become immediately ready - /// to execute, but will never be executed directly as part of the execution - /// of cancel(). - /// - /// Cancellation happens automatically when the timer object is destroyed. - void cancel() noexcept; - -private: - template - class WaitOper; - - using clock = Service::clock; - - Service::Impl& m_service_impl; - Service::OwnersOperPtr m_wait_oper; - - void initiate_oper(Service::LendersWaitOperPtr); -}; - - -class ReadAheadBuffer { -public: - ReadAheadBuffer(); - - /// Discard any buffered data. - void clear() noexcept; - -private: - using Want = Service::Want; - - char* m_begin = nullptr; - char* m_end = nullptr; - static constexpr std::size_t s_size = 1024; - const std::unique_ptr m_buffer; - - bool empty() const noexcept; - bool read(char*& begin, char* end, int delim, std::error_code&) noexcept; - template - void refill_sync(S& stream, std::error_code&) noexcept; - template - bool refill_async(S& stream, std::error_code&, Want&) noexcept; - - template - friend class Service::BasicStreamOps; -}; - - -enum class ResolveErrors { - /// Host not found (authoritative). - host_not_found = 1, - - /// Host not found (non-authoritative). - host_not_found_try_again = 2, - - /// The query is valid but does not have associated address data. - no_data = 3, - - /// A non-recoverable error occurred. - no_recovery = 4, - - /// The service is not supported for the given socket type. - service_not_found = 5, - - /// The socket type is not supported. - socket_type_not_supported = 6, -}; - -/// The error category associated with ResolveErrors. The name of this category is -/// `realm.sync.network.resolve`. -const std::error_category& resolve_error_category() noexcept; - -std::error_code make_error_code(ResolveErrors err); - -} // namespace realm::sync::network - -namespace std { - -template <> -class is_error_code_enum { -public: - static const bool value = true; -}; - -} // namespace std - -namespace realm::sync::network { - -// Implementation - -// ---------------- StreamProtocol ---------------- - -inline StreamProtocol StreamProtocol::ip_v4() -{ - StreamProtocol prot; - prot.m_family = AF_INET; - return prot; -} - -inline StreamProtocol StreamProtocol::ip_v6() -{ - StreamProtocol prot; - prot.m_family = AF_INET6; - return prot; -} - -inline bool StreamProtocol::is_ip_v4() const -{ - return m_family == AF_INET; -} - -inline bool StreamProtocol::is_ip_v6() const -{ - return m_family == AF_INET6; -} - -inline int StreamProtocol::family() const -{ - return m_family; -} - -inline int StreamProtocol::protocol() const -{ - return m_protocol; -} - -inline StreamProtocol::StreamProtocol() - : m_family{AF_UNSPEC} - , // Allow both IPv4 and IPv6 - m_socktype{SOCK_STREAM} - , // Or SOCK_DGRAM for UDP - m_protocol{0} // Any protocol -{ -} - -// ---------------- Address ---------------- - -inline bool Address::is_ip_v4() const -{ - return !m_is_ip_v6; -} - -inline bool Address::is_ip_v6() const -{ - return m_is_ip_v6; -} - -template -inline std::basic_ostream& operator<<(std::basic_ostream& out, const Address& addr) -{ - // FIXME: Not taking `addr.m_ip_v6_scope_id` into account. What does ASIO - // do? - union buffer_union { - char ip_v4[INET_ADDRSTRLEN]; - char ip_v6[INET6_ADDRSTRLEN]; - }; - char buffer[sizeof(buffer_union)]; - int af = addr.m_is_ip_v6 ? AF_INET6 : AF_INET; -#ifdef _WIN32 - void* src = const_cast(reinterpret_cast(&addr.m_union)); -#else - const void* src = &addr.m_union; -#endif - const char* ret = ::inet_ntop(af, src, buffer, sizeof buffer); - if (ret == 0) { - std::error_code ec = util::make_basic_system_error_code(errno); - throw std::system_error(ec); - } - out << ret; - return out; -} - -inline Address::Address() -{ - m_union.m_ip_v4 = ip_v4_type(); -} - -inline Address make_address(const char* c_str) -{ - std::error_code ec; - Address addr = make_address(c_str, ec); - if (ec) - throw std::system_error(ec); - return addr; -} - -inline Address make_address(const std::string& str) -{ - std::error_code ec; - Address addr = make_address(str, ec); - if (ec) - throw std::system_error(ec); - return addr; -} - -inline Address make_address(const std::string& str, std::error_code& ec) noexcept -{ - return make_address(str.c_str(), ec); -} - -// ---------------- Endpoint ---------------- - -inline StreamProtocol Endpoint::protocol() const -{ - return m_protocol; -} - -inline Address Endpoint::address() const -{ - Address addr; - if (m_protocol.is_ip_v4()) { - addr.m_union.m_ip_v4 = m_sockaddr_union.m_ip_v4.sin_addr; - } - else { - addr.m_union.m_ip_v6 = m_sockaddr_union.m_ip_v6.sin6_addr; - addr.m_ip_v6_scope_id = m_sockaddr_union.m_ip_v6.sin6_scope_id; - addr.m_is_ip_v6 = true; - } - return addr; -} - -inline Endpoint::port_type Endpoint::port() const -{ - return ntohs(m_protocol.is_ip_v4() ? m_sockaddr_union.m_ip_v4.sin_port : m_sockaddr_union.m_ip_v6.sin6_port); -} - -inline Endpoint::data_type* Endpoint::data() -{ - return &m_sockaddr_union.m_base; -} - -inline const Endpoint::data_type* Endpoint::data() const -{ - return &m_sockaddr_union.m_base; -} - -inline Endpoint::Endpoint() - : Endpoint{StreamProtocol::ip_v4(), 0} -{ -} - -inline Endpoint::Endpoint(const StreamProtocol& protocol, port_type port) - : m_protocol{protocol} -{ - int family = m_protocol.family(); - if (family == AF_INET) { - m_sockaddr_union.m_ip_v4 = sockaddr_ip_v4_type(); // Clear - m_sockaddr_union.m_ip_v4.sin_family = AF_INET; - m_sockaddr_union.m_ip_v4.sin_port = htons(port); - m_sockaddr_union.m_ip_v4.sin_addr.s_addr = INADDR_ANY; - } - else if (family == AF_INET6) { - m_sockaddr_union.m_ip_v6 = sockaddr_ip_v6_type(); // Clear - m_sockaddr_union.m_ip_v6.sin6_family = AF_INET6; - m_sockaddr_union.m_ip_v6.sin6_port = htons(port); - } - else { - m_sockaddr_union.m_ip_v4 = sockaddr_ip_v4_type(); // Clear - m_sockaddr_union.m_ip_v4.sin_family = AF_UNSPEC; - m_sockaddr_union.m_ip_v4.sin_port = htons(port); - m_sockaddr_union.m_ip_v4.sin_addr.s_addr = INADDR_ANY; - } -} - -inline Endpoint::Endpoint(const Address& addr, port_type port) -{ - if (addr.m_is_ip_v6) { - m_protocol = StreamProtocol::ip_v6(); - m_sockaddr_union.m_ip_v6.sin6_family = AF_INET6; - m_sockaddr_union.m_ip_v6.sin6_port = htons(port); - m_sockaddr_union.m_ip_v6.sin6_flowinfo = 0; - m_sockaddr_union.m_ip_v6.sin6_addr = addr.m_union.m_ip_v6; - m_sockaddr_union.m_ip_v6.sin6_scope_id = addr.m_ip_v6_scope_id; - } - else { - m_protocol = StreamProtocol::ip_v4(); - m_sockaddr_union.m_ip_v4.sin_family = AF_INET; - m_sockaddr_union.m_ip_v4.sin_port = htons(port); - m_sockaddr_union.m_ip_v4.sin_addr = addr.m_union.m_ip_v4; - } -} - -inline Endpoint::List::iterator Endpoint::List::begin() const noexcept -{ - return m_endpoints.data(); -} - -inline Endpoint::List::iterator Endpoint::List::end() const noexcept -{ - return m_endpoints.data() + m_endpoints.size(); -} - -inline std::size_t Endpoint::List::size() const noexcept -{ - return m_endpoints.size(); -} - -inline bool Endpoint::List::empty() const noexcept -{ - return m_endpoints.size() == 0; -} - -// ---------------- Service::OperQueue ---------------- - -template -inline bool Service::OperQueue::empty() const noexcept -{ - return !m_back; -} - -template -inline void Service::OperQueue::push_back(LendersOperPtr op) noexcept -{ - REALM_ASSERT(!op->m_next); - if (m_back) { - op->m_next = m_back->m_next; - m_back->m_next = op.get(); - } - else { - op->m_next = op.get(); - } - m_back = op.release(); -} - -template -template -inline void Service::OperQueue::push_back(OperQueue& q) noexcept -{ - if (!q.m_back) - return; - if (m_back) - std::swap(m_back->m_next, q.m_back->m_next); - m_back = q.m_back; - q.m_back = nullptr; -} - -template -inline auto Service::OperQueue::pop_front() noexcept -> LendersOperPtr -{ - Oper* op = nullptr; - if (m_back) { - op = static_cast(m_back->m_next); - if (op != m_back) { - m_back->m_next = op->m_next; - } - else { - m_back = nullptr; - } - op->m_next = nullptr; - } - return LendersOperPtr(op); -} - -template -inline void Service::OperQueue::clear() noexcept -{ - if (m_back) { - LendersOperPtr op(m_back); - while (op->m_next != m_back) - op.reset(static_cast(op->m_next)); - m_back = nullptr; - } -} - -template -inline Service::OperQueue::OperQueue(OperQueue&& q) noexcept - : m_back{q.m_back} -{ - q.m_back = nullptr; -} - -// ---------------- Service::Descriptor ---------------- - -inline Service::Descriptor::Descriptor(Impl& s) noexcept - : service_impl{s} -{ -} - -inline Service::Descriptor::~Descriptor() noexcept -{ - if (is_open()) - close(); -} - -inline void Service::Descriptor::assign(native_handle_type fd, bool in_blocking_mode) noexcept -{ - REALM_ASSERT(!is_open()); - m_fd = fd; - m_in_blocking_mode = in_blocking_mode; -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - m_read_ready = false; - m_write_ready = false; - m_imminent_end_of_input = false; - m_is_registered = false; -#endif -} - -inline void Service::Descriptor::close() noexcept -{ - REALM_ASSERT(is_open()); -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - if (m_is_registered) - deregister_for_async(); - m_is_registered = false; -#endif - do_close(); -} - -inline auto Service::Descriptor::release() noexcept -> native_handle_type -{ - REALM_ASSERT(is_open()); -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - if (m_is_registered) - deregister_for_async(); - m_is_registered = false; -#endif - return do_release(); -} - -inline bool Service::Descriptor::is_open() const noexcept -{ - return (m_fd != -1); -} - -inline auto Service::Descriptor::native_handle() const noexcept -> native_handle_type -{ - return m_fd; -} - -inline bool Service::Descriptor::in_blocking_mode() const noexcept -{ - return m_in_blocking_mode; -} - -template -inline void Service::Descriptor::initiate_oper(std::unique_ptr op, Args&&... args) -{ - Service::Want want = op->initiate(std::forward(args)...); // Throws - add_initiated_oper(std::move(op), want); // Throws -} - -inline void Service::Descriptor::ensure_blocking_mode() -{ - // Assuming that descriptors are either used mostly in blocking mode, or - // mostly in nonblocking mode. - if (REALM_UNLIKELY(!m_in_blocking_mode)) { - bool value = false; - set_nonblock_flag(value); // Throws - m_in_blocking_mode = true; - } -} - -inline void Service::Descriptor::ensure_nonblocking_mode() -{ - // Assuming that descriptors are either used mostly in blocking mode, or - // mostly in nonblocking mode. - if (REALM_UNLIKELY(m_in_blocking_mode)) { - bool value = true; - set_nonblock_flag(value); // Throws - m_in_blocking_mode = false; - } -} - -inline bool Service::Descriptor::assume_read_would_block() const noexcept -{ -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - return !m_in_blocking_mode && !m_read_ready; -#else - return false; -#endif -} - -inline bool Service::Descriptor::assume_write_would_block() const noexcept -{ -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - return !m_in_blocking_mode && !m_write_ready; -#else - return false; -#endif -} - -inline void Service::Descriptor::set_read_ready(bool value) noexcept -{ -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - m_read_ready = value; -#else - // No-op - static_cast(value); -#endif -} - -inline void Service::Descriptor::set_write_ready(bool value) noexcept -{ -#if REALM_NETWORK_USE_EPOLL || REALM_HAVE_KQUEUE - m_write_ready = value; -#else - // No-op - static_cast(value); -#endif -} - -// ---------------- Service ---------------- - -class Service::AsyncOper { -public: - bool in_use() const noexcept; - bool is_complete() const noexcept; - bool is_canceled() const noexcept; - void cancel() noexcept; - /// Every object of type \ref AsyncOper must be destroyed either by a call - /// to this function or to recycle(). This function recycles the operation - /// object (commits suicide), even if it throws. - virtual void recycle_and_execute() = 0; - /// Every object of type \ref AsyncOper must be destroyed either by a call - /// to recycle_and_execute() or to this function. This function destroys the - /// object (commits suicide). - virtual void recycle() noexcept = 0; - /// Must be called when the owner dies, and the object is in use (not an - /// instance of UnusedOper). - virtual void orphan() noexcept = 0; - -protected: - AsyncOper(std::size_t size, bool in_use) noexcept; - virtual ~AsyncOper() noexcept {} - void set_is_complete(bool value) noexcept; - template - void do_recycle_and_execute(bool orphaned, H& handler, Args&&...); - void do_recycle(bool orphaned) noexcept; - -private: - std::size_t m_size; // Allocated number of bytes - bool m_in_use = false; - // Set to true when the operation completes successfully or fails. If the - // operation is canceled before this happens, it will never be set to - // true. Always false when not in use - bool m_complete = false; - // Set to true when the operation is canceled. Always false when not in use. - bool m_canceled = false; - AsyncOper* m_next = nullptr; // Always null when not in use - template - void do_recycle_and_execute_helper(bool orphaned, bool& was_recycled, H handler, Args...); - friend class Service; -}; - -class Service::ResolveOperBase : public AsyncOper { -public: - ResolveOperBase(std::size_t size, Resolver& resolver, Resolver::Query query) noexcept - : AsyncOper{size, true} - , m_resolver{&resolver} - , m_query{std::move(query)} - { - } - void complete() noexcept - { - set_is_complete(true); - } - void recycle() noexcept override final - { - bool orphaned = !m_resolver; - REALM_ASSERT(orphaned); - // Note: do_recycle() commits suicide. - do_recycle(orphaned); - } - void orphan() noexcept override final - { - m_resolver = nullptr; - } - -protected: - Resolver* m_resolver; - Resolver::Query m_query; - Endpoint::List m_endpoints; - std::error_code m_error_code; - friend class Service; -}; - -class Service::WaitOperBase : public AsyncOper { -public: - WaitOperBase(std::size_t size, DeadlineTimer& timer, clock::time_point expiration_time) noexcept - : AsyncOper{size, true} - , // Second argument is `in_use` - m_timer{&timer} - , m_expiration_time{expiration_time} - { - } - void complete() noexcept - { - set_is_complete(true); - } - void recycle() noexcept override final - { - bool orphaned = !m_timer; - REALM_ASSERT(orphaned); - // Note: do_recycle() commits suicide. - do_recycle(orphaned); - } - void orphan() noexcept override final - { - m_timer = nullptr; - } - -protected: - DeadlineTimer* m_timer; - clock::time_point m_expiration_time; - friend class Service; -}; - -class Service::TriggerExecOperBase : public AsyncOper, public util::AtomicRefCountBase { -public: - TriggerExecOperBase(Impl& service) noexcept - : AsyncOper{0, false} - , // First arg is `size` (unused), second arg is `in_use` - m_service{&service} - { - } - void recycle() noexcept override final - { - REALM_ASSERT(in_use()); - REALM_ASSERT(!m_service); - // Note: Potential suicide when `self` goes out of scope - util::bind_ptr self{this, util::bind_ptr_base::adopt_tag{}}; - } - void orphan() noexcept override final - { - REALM_ASSERT(m_service); - m_service = nullptr; - } - void trigger() noexcept - { - REALM_ASSERT(m_service); - Service::trigger_exec(*m_service, *this); - } - -protected: - Impl* m_service; -}; - -class Service::PostOperBase : public AsyncOper { -public: - PostOperBase(std::size_t size, Impl& service) noexcept - : AsyncOper{size, true} - , // Second argument is `in_use` - m_service{service} - { - } - void recycle() noexcept override final - { - // Service::recycle_post_oper() destroys this operation object - Service::recycle_post_oper(m_service, this); - } - void orphan() noexcept override final - { - REALM_ASSERT(false); // Never called - } - -protected: - Impl& m_service; -}; - -template -class Service::PostOper : public PostOperBase { - static_assert(std::is_nothrow_move_constructible_v); - -public: - PostOper(std::size_t size, Impl& service, H&& handler) - : PostOperBase{size, service} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - // Recycle the operation object before the handler is exceuted, such - // that the memory is available for a new post operation that might be - // initiated during the execution of the handler. - H handler = std::move(m_handler); - // Service::recycle_post_oper() destroys this operation object - Service::recycle_post_oper(m_service, this); - handler(Status::OK()); // Throws - } - -private: - H m_handler; -}; - -class Service::IoOper : public AsyncOper { -public: - IoOper(std::size_t size) noexcept - : AsyncOper{size, true} // Second argument is `in_use` - { - } - virtual Descriptor& descriptor() noexcept = 0; - /// Advance this operation and figure out out whether it needs to read from, - /// or write to the underlying descriptor to advance further. This function - /// must return Want::read if the operation needs to read, or Want::write if - /// the operation needs to write to advance further. If the operation - /// completes (due to success or failure), this function must return - /// Want::nothing. - virtual Want advance() noexcept = 0; -}; - -class Service::UnusedOper : public AsyncOper { -public: - UnusedOper(std::size_t size) noexcept - : AsyncOper{size, false} // Second argument is `in_use` - { - } - void recycle_and_execute() override final - { - // Must never be called - REALM_ASSERT(false); - } - void recycle() noexcept override final - { - // Must never be called - REALM_ASSERT(false); - } - void orphan() noexcept override final - { - // Must never be called - REALM_ASSERT(false); - } -}; - -// `S` must be a stream class with the following member functions: -// -// Socket& lowest_layer() noexcept; -// -// void do_init_read_async(std::error_code& ec, Want& want) noexcept; -// void do_init_write_async(std::error_code& ec, Want& want) noexcept; -// -// std::size_t do_read_some_sync(char* buffer, std::size_t size, -// std::error_code& ec) noexcept; -// std::size_t do_write_some_sync(const char* data, std::size_t size, -// std::error_code& ec) noexcept; -// std::size_t do_read_some_async(char* buffer, std::size_t size, -// std::error_code& ec, Want& want) noexcept; -// std::size_t do_write_some_async(const char* data, std::size_t size, -// std::error_code& ec, Want& want) noexcept; -// -// If an error occurs during any of these 6 functions, the `ec` argument must be -// set accordingly. Otherwise the `ec` argument must be set to -// `std::error_code()`. -// -// The do_init_*_async() functions must update the `want` argument to indicate -// how the operation must be initiated: -// -// Want::read Wait for read readiness, then call do_*_some_async(). -// Want::write Wait for write readiness, then call do_*_some_async(). -// Want::nothing Call do_*_some_async() immediately without waiting for -// read or write readiness. -// -// If end-of-input occurs while reading, do_read_some_*() must fail, set `ec` to -// MiscExtErrors::end_of_input, and return zero. -// -// If an error occurs during reading or writing, do_*_some_sync() must set `ec` -// accordingly (to something other than `std::system_error()`) and return -// zero. Otherwise they must set `ec` to `std::system_error()` and return the -// number of bytes read or written, which **must** be at least 1. If the -// underlying socket is in nonblocking mode, and no bytes could be immediately -// read or written, these functions must fail with -// `error::resource_unavailable_try_again`. -// -// If an error occurs during reading or writing, do_*_some_async() must set `ec` -// accordingly (to something other than `std::system_error()`), `want` to -// `Want::nothing`, and return zero. Otherwise they must set `ec` to -// `std::system_error()` and return the number of bytes read or written, which -// must be zero if no bytes could be immediately read or written. Note, in this -// case it is not an error if the underlying socket is in nonblocking mode, and -// no bytes could be immediately read or written. When these functions succeed, -// but return zero because no bytes could be immediately read or written, they -// must set `want` to something other than `Want::nothing`. -// -// If no error occurs, do_*_some_async() must set `want` to indicate how the -// operation should proceed if additional data needs to be read or written, or -// if no bytes were transferred: -// -// Want::read Wait for read readiness, then call do_*_some_async() again. -// Want::write Wait for write readiness, then call do_*_some_async() again. -// Want::nothing Call do_*_some_async() again without waiting for read or -// write readiness. -// -// NOTE: If, for example, do_read_some_async() sets `want` to `Want::write`, it -// means that the stream needs to write data to the underlying TCP socket before -// it is able to deliver any additional data to the caller. While such a -// situation will never occur on a raw TCP socket, it can occur on an SSL stream -// (Secure Socket Layer). -// -// When do_*_some_async() returns `n`, at least one of the following conditions -// must be true: -// -// n > 0 Bytes were transferred. -// ec != std::error_code() An error occured. -// want != Want::nothing Wait for read/write readiness. -// -// This is of critical importance, as it is the only way we can avoid falling -// into a busy loop of repeated invocations of do_*_some_async(). -// -// NOTE: do_*_some_async() are allowed to set `want` to `Want::read` or -// `Want::write`, even when they succesfully transfer a nonzero number of bytes. -template -class Service::BasicStreamOps { -public: - class StreamOper; - class ReadOperBase; - class WriteOperBase; - class BufferedReadOperBase; - template - class ReadOper; - template - class WriteOper; - template - class BufferedReadOper; - - using LendersReadOperPtr = std::unique_ptr; - using LendersWriteOperPtr = std::unique_ptr; - using LendersBufferedReadOperPtr = std::unique_ptr; - - // Synchronous read - static std::size_t read(S& stream, char* buffer, std::size_t size, std::error_code& ec) - { - REALM_ASSERT(!stream.lowest_layer().m_read_oper || !stream.lowest_layer().m_read_oper->in_use()); - stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws - char* begin = buffer; - char* end = buffer + size; - char* curr = begin; - for (;;) { - if (curr == end) { - ec = std::error_code(); // Success - break; - } - char* buffer_2 = curr; - std::size_t size_2 = std::size_t(end - curr); - std::size_t n = stream.do_read_some_sync(buffer_2, size_2, ec); - if (REALM_UNLIKELY(ec)) - break; - REALM_ASSERT(n > 0); - REALM_ASSERT(n <= size_2); - curr += n; - } - std::size_t n = std::size_t(curr - begin); - return n; - } - - // Synchronous write - static std::size_t write(S& stream, const char* data, std::size_t size, std::error_code& ec) - { - REALM_ASSERT(!stream.lowest_layer().m_write_oper || !stream.lowest_layer().m_write_oper->in_use()); - stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws - const char* begin = data; - const char* end = data + size; - const char* curr = begin; - for (;;) { - if (curr == end) { - ec = std::error_code(); // Success - break; - } - const char* data_2 = curr; - std::size_t size_2 = std::size_t(end - curr); - std::size_t n = stream.do_write_some_sync(data_2, size_2, ec); - if (REALM_UNLIKELY(ec)) - break; - REALM_ASSERT(n > 0); - REALM_ASSERT(n <= size_2); - curr += n; - } - std::size_t n = std::size_t(curr - begin); - return n; - } - - // Synchronous read - static std::size_t buffered_read(S& stream, char* buffer, std::size_t size, int delim, ReadAheadBuffer& rab, - std::error_code& ec) - { - REALM_ASSERT(!stream.lowest_layer().m_read_oper || !stream.lowest_layer().m_read_oper->in_use()); - stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws - char* begin = buffer; - char* end = buffer + size; - char* curr = begin; - for (;;) { - bool complete = rab.read(curr, end, delim, ec); - if (complete) - break; - - rab.refill_sync(stream, ec); - if (REALM_UNLIKELY(ec)) - break; - } - std::size_t n = (curr - begin); - return n; - } - - // Synchronous read - static std::size_t read_some(S& stream, char* buffer, std::size_t size, std::error_code& ec) - { - REALM_ASSERT(!stream.lowest_layer().m_read_oper || !stream.lowest_layer().m_read_oper->in_use()); - stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws - return stream.do_read_some_sync(buffer, size, ec); - } - - // Synchronous write - static std::size_t write_some(S& stream, const char* data, std::size_t size, std::error_code& ec) - { - REALM_ASSERT(!stream.lowest_layer().m_write_oper || !stream.lowest_layer().m_write_oper->in_use()); - stream.lowest_layer().m_desc.ensure_blocking_mode(); // Throws - return stream.do_write_some_sync(data, size, ec); - } - - template - static void async_read(S& stream, char* buffer, std::size_t size, bool is_read_some, H&& handler) - { - char* begin = buffer; - char* end = buffer + size; - LendersReadOperPtr op = Service::alloc>(stream.lowest_layer().m_read_oper, stream, is_read_some, - begin, end, std::move(handler)); // Throws - stream.lowest_layer().m_desc.initiate_oper(std::move(op)); // Throws - } - - template - static void async_write(S& stream, const char* data, std::size_t size, bool is_write_some, H&& handler) - { - const char* begin = data; - const char* end = data + size; - LendersWriteOperPtr op = Service::alloc>( - stream.lowest_layer().m_write_oper, stream, is_write_some, begin, end, std::move(handler)); // Throws - stream.lowest_layer().m_desc.initiate_oper(std::move(op)); // Throws - } - - template - static void async_buffered_read(S& stream, char* buffer, std::size_t size, int delim, ReadAheadBuffer& rab, - H&& handler) - { - char* begin = buffer; - char* end = buffer + size; - LendersBufferedReadOperPtr op = - Service::alloc>(stream.lowest_layer().m_read_oper, stream, begin, end, delim, rab, - std::move(handler)); // Throws - stream.lowest_layer().m_desc.initiate_oper(std::move(op)); // Throws - } -}; - -template -class Service::BasicStreamOps::StreamOper : public IoOper { -public: - StreamOper(std::size_t size, S& stream) noexcept - : IoOper{size} - , m_stream{&stream} - { - } - void recycle() noexcept override final - { - bool orphaned = !m_stream; - REALM_ASSERT(orphaned); - // Note: do_recycle() commits suicide. - do_recycle(orphaned); - } - void orphan() noexcept override final - { - m_stream = nullptr; - } - Descriptor& descriptor() noexcept override final - { - return m_stream->lowest_layer().m_desc; - } - -protected: - S* m_stream; - std::error_code m_error_code; -}; - -template -class Service::BasicStreamOps::ReadOperBase : public StreamOper { -public: - ReadOperBase(std::size_t size, S& stream, bool is_read_some, char* begin, char* end) noexcept - : StreamOper{size, stream} - , m_is_read_some{is_read_some} - , m_begin{begin} - , m_end{end} - { - } - Want initiate() - { - auto& s = *this; - REALM_ASSERT(this == s.m_stream->lowest_layer().m_read_oper.get()); - REALM_ASSERT(!s.is_complete()); - REALM_ASSERT(s.m_curr <= s.m_end); - Want want = Want::nothing; - if (REALM_UNLIKELY(s.m_curr == s.m_end)) { - s.set_is_complete(true); // Success - } - else { - s.m_stream->lowest_layer().m_desc.ensure_nonblocking_mode(); // Throws - s.m_stream->do_init_read_async(s.m_error_code, want); - if (want == Want::nothing) { - if (REALM_UNLIKELY(s.m_error_code)) { - s.set_is_complete(true); // Failure - } - else { - want = advance(); - } - } - } - return want; - } - Want advance() noexcept override final - { - auto& s = *this; - REALM_ASSERT(!s.is_complete()); - REALM_ASSERT(!s.is_canceled()); - REALM_ASSERT(!s.m_error_code); - REALM_ASSERT(s.m_curr < s.m_end); - REALM_ASSERT(!s.m_is_read_some || s.m_curr == m_begin); - for (;;) { - // Read into callers buffer - char* buffer = s.m_curr; - std::size_t size = std::size_t(s.m_end - s.m_curr); - Want want = Want::nothing; - std::size_t n = s.m_stream->do_read_some_async(buffer, size, s.m_error_code, want); - REALM_ASSERT(n > 0 || s.m_error_code || want != Want::nothing); // No busy loop, please - // Any errors reported by do_read_some_async() (other than end_of_input) should always return 0 - bool got_nothing = (n == 0); - if (got_nothing) { - if (REALM_UNLIKELY(s.m_error_code)) { - s.set_is_complete(true); // Failure - return Want::nothing; - } - // Got nothing, but want something - return want; - } - REALM_ASSERT(!s.m_error_code); - // Check for completion - REALM_ASSERT(n <= size); - s.m_curr += n; - if (s.m_is_read_some || s.m_curr == s.m_end) { - s.set_is_complete(true); // Success - return Want::nothing; - } - if (want != Want::nothing) - return want; - REALM_ASSERT(n < size); - } - } - -protected: - const bool m_is_read_some; - char* const m_begin; // May be dangling after cancellation - char* const m_end; // May be dangling after cancellation - char* m_curr = m_begin; // May be dangling after cancellation -}; - -template -class Service::BasicStreamOps::WriteOperBase : public StreamOper { -public: - WriteOperBase(std::size_t size, S& stream, bool is_write_some, const char* begin, const char* end) noexcept - : StreamOper{size, stream} - , m_is_write_some{is_write_some} - , m_begin{begin} - , m_end{end} - { - } - Want initiate() - { - auto& s = *this; - REALM_ASSERT(this == s.m_stream->lowest_layer().m_write_oper.get()); - REALM_ASSERT(!s.is_complete()); - REALM_ASSERT(s.m_curr <= s.m_end); - Want want = Want::nothing; - if (REALM_UNLIKELY(s.m_curr == s.m_end)) { - s.set_is_complete(true); // Success - } - else { - s.m_stream->lowest_layer().m_desc.ensure_nonblocking_mode(); // Throws - s.m_stream->do_init_write_async(s.m_error_code, want); - if (want == Want::nothing) { - if (REALM_UNLIKELY(s.m_error_code)) { - s.set_is_complete(true); // Failure - } - else { - want = advance(); - } - } - } - return want; - } - Want advance() noexcept override final - { - auto& s = *this; - REALM_ASSERT(!s.is_complete()); - REALM_ASSERT(!s.is_canceled()); - REALM_ASSERT(!s.m_error_code); - REALM_ASSERT(s.m_curr < s.m_end); - REALM_ASSERT(!s.m_is_write_some || s.m_curr == s.m_begin); - for (;;) { - // Write from callers buffer - const char* data = s.m_curr; - std::size_t size = std::size_t(s.m_end - s.m_curr); - Want want = Want::nothing; - std::size_t n = s.m_stream->do_write_some_async(data, size, s.m_error_code, want); - REALM_ASSERT(n > 0 || s.m_error_code || want != Want::nothing); // No busy loop, please - bool wrote_nothing = (n == 0); - if (wrote_nothing) { - if (REALM_UNLIKELY(s.m_error_code)) { - s.set_is_complete(true); // Failure - return Want::nothing; - } - // Wrote nothing, but want something written - return want; - } - REALM_ASSERT(!s.m_error_code); - // Check for completion - REALM_ASSERT(n <= size); - s.m_curr += n; - if (s.m_is_write_some || s.m_curr == s.m_end) { - s.set_is_complete(true); // Success - return Want::nothing; - } - if (want != Want::nothing) - return want; - REALM_ASSERT(n < size); - } - } - -protected: - const bool m_is_write_some; - const char* const m_begin; // May be dangling after cancellation - const char* const m_end; // May be dangling after cancellation - const char* m_curr = m_begin; // May be dangling after cancellation -}; - -template -class Service::BasicStreamOps::BufferedReadOperBase : public StreamOper { -public: - BufferedReadOperBase(std::size_t size, S& stream, char* begin, char* end, int delim, - ReadAheadBuffer& rab) noexcept - : StreamOper{size, stream} - , m_read_ahead_buffer{rab} - , m_begin{begin} - , m_end{end} - , m_delim{delim} - { - } - Want initiate() - { - auto& s = *this; - REALM_ASSERT(this == s.m_stream->lowest_layer().m_read_oper.get()); - REALM_ASSERT(!s.is_complete()); - Want want = Want::nothing; - bool complete = s.m_read_ahead_buffer.read(s.m_curr, s.m_end, s.m_delim, s.m_error_code); - if (complete) { - s.set_is_complete(true); // Success or failure - } - else { - s.m_stream->lowest_layer().m_desc.ensure_nonblocking_mode(); // Throws - s.m_stream->do_init_read_async(s.m_error_code, want); - if (want == Want::nothing) { - if (REALM_UNLIKELY(s.m_error_code)) { - s.set_is_complete(true); // Failure - } - else { - want = advance(); - } - } - } - return want; - } - Want advance() noexcept override final - { - auto& s = *this; - REALM_ASSERT(!s.is_complete()); - REALM_ASSERT(!s.is_canceled()); - REALM_ASSERT(!s.m_error_code); - REALM_ASSERT(s.m_read_ahead_buffer.empty()); - REALM_ASSERT(s.m_curr < s.m_end); - for (;;) { - // Fill read-ahead buffer from stream (is empty now) - Want want = Want::nothing; - bool nonempty = s.m_read_ahead_buffer.refill_async(*s.m_stream, s.m_error_code, want); - REALM_ASSERT(nonempty || s.m_error_code || want != Want::nothing); // No busy loop, please - bool got_nothing = !nonempty; - if (got_nothing) { - if (REALM_UNLIKELY(s.m_error_code)) { - s.set_is_complete(true); // Failure - return Want::nothing; - } - // Got nothing, but want something - return want; - } - // Transfer buffered data to callers buffer - bool complete = s.m_read_ahead_buffer.read(s.m_curr, s.m_end, s.m_delim, s.m_error_code); - if (complete || s.m_error_code == util::MiscExtErrors::end_of_input) { - s.set_is_complete(true); // Success or failure (delim_not_found or end_of_input) - return Want::nothing; - } - if (want != Want::nothing) - return want; - } - } - -protected: - ReadAheadBuffer& m_read_ahead_buffer; // May be dangling after cancellation - char* const m_begin; // May be dangling after cancellation - char* const m_end; // May be dangling after cancellation - char* m_curr = m_begin; // May be dangling after cancellation - const int m_delim; -}; - -template -template -class Service::BasicStreamOps::ReadOper : public ReadOperBase { -public: - ReadOper(std::size_t size, S& stream, bool is_read_some, char* begin, char* end, H&& handler) - : ReadOperBase{size, stream, is_read_some, begin, end} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - auto& s = *this; - REALM_ASSERT(s.is_complete() || s.is_canceled()); - REALM_ASSERT(s.is_complete() == - (s.m_error_code || s.m_curr == s.m_end || (s.m_is_read_some && s.m_curr != s.m_begin))); - REALM_ASSERT(s.m_curr >= s.m_begin); - bool orphaned = !s.m_stream; - std::error_code ec = s.m_error_code; - if (s.is_canceled()) - ec = util::error::operation_aborted; - std::size_t num_bytes_transferred = std::size_t(s.m_curr - s.m_begin); - // Note: do_recycle_and_execute() commits suicide. - s.template do_recycle_and_execute(orphaned, s.m_handler, ec, - num_bytes_transferred); // Throws - } - -private: - H m_handler; -}; - -template -template -class Service::BasicStreamOps::WriteOper : public WriteOperBase { -public: - WriteOper(std::size_t size, S& stream, bool is_write_some, const char* begin, const char* end, H&& handler) - : WriteOperBase{size, stream, is_write_some, begin, end} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - auto& s = *this; - REALM_ASSERT(s.is_complete() || s.is_canceled()); - REALM_ASSERT(s.is_complete() == - (s.m_error_code || s.m_curr == s.m_end || (s.m_is_write_some && s.m_curr != s.m_begin))); - REALM_ASSERT(s.m_curr >= s.m_begin); - bool orphaned = !s.m_stream; - std::error_code ec = s.m_error_code; - if (s.is_canceled()) - ec = util::error::operation_aborted; - std::size_t num_bytes_transferred = std::size_t(s.m_curr - s.m_begin); - // Note: do_recycle_and_execute() commits suicide. - s.template do_recycle_and_execute(orphaned, s.m_handler, ec, - num_bytes_transferred); // Throws - } - -private: - H m_handler; -}; - -template -template -class Service::BasicStreamOps::BufferedReadOper : public BufferedReadOperBase { -public: - BufferedReadOper(std::size_t size, S& stream, char* begin, char* end, int delim, ReadAheadBuffer& rab, - H&& handler) - : BufferedReadOperBase{size, stream, begin, end, delim, rab} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - auto& s = *this; - REALM_ASSERT(s.is_complete() || (s.is_canceled() && !s.m_error_code)); - REALM_ASSERT(s.is_canceled() || s.m_error_code || - (s.m_delim != std::char_traits::eof() - ? s.m_curr > s.m_begin && s.m_curr[-1] == std::char_traits::to_char_type(s.m_delim) - : s.m_curr == s.m_end)); - REALM_ASSERT(s.m_curr >= s.m_begin); - bool orphaned = !s.m_stream; - std::error_code ec = s.m_error_code; - if (s.is_canceled()) - ec = util::error::operation_aborted; - std::size_t num_bytes_transferred = std::size_t(s.m_curr - s.m_begin); - // Note: do_recycle_and_execute() commits suicide. - s.template do_recycle_and_execute(orphaned, s.m_handler, ec, - num_bytes_transferred); // Throws - } - -private: - H m_handler; -}; - -template -inline void Service::post(H handler) -{ - do_post(&Service::post_oper_constr, sizeof(PostOper), &handler); -} - -inline void Service::OwnersOperDeleter::operator()(AsyncOper* op) const noexcept -{ - if (op->in_use()) { - op->orphan(); - } - else { - void* addr = op; - op->~AsyncOper(); - delete[] static_cast(addr); - } -} - -inline void Service::LendersOperDeleter::operator()(AsyncOper* op) const noexcept -{ - op->recycle(); // Suicide -} - -template -std::unique_ptr Service::alloc(OwnersOperPtr& owners_ptr, Args&&... args) -{ - void* addr = owners_ptr.get(); - std::size_t size; - if (REALM_LIKELY(addr)) { - REALM_ASSERT(!owners_ptr->in_use()); - size = owners_ptr->m_size; - // We can use static dispatch in the destructor call here, since an - // object, that is not in use, is always an instance of UnusedOper. - REALM_ASSERT(dynamic_cast(owners_ptr.get())); - static_cast(owners_ptr.get())->UnusedOper::~UnusedOper(); - if (REALM_UNLIKELY(size < sizeof(Oper))) { - owners_ptr.release(); - delete[] static_cast(addr); - goto no_object; - } - } - else { - no_object: - addr = new char[sizeof(Oper)]; // Throws - size = sizeof(Oper); - owners_ptr.reset(static_cast(addr)); - } - std::unique_ptr lenders_ptr; - try { - lenders_ptr.reset(new (addr) Oper(size, std::forward(args)...)); // Throws - } - catch (...) { - new (addr) UnusedOper(size); // Does not throw - throw; - } - return lenders_ptr; -} - -template -inline Service::PostOperBase* Service::post_oper_constr(void* addr, std::size_t size, Impl& service, void* cookie) -{ - H& handler = *static_cast(cookie); - return new (addr) PostOper(size, service, std::move(handler)); // Throws -} - -inline bool Service::AsyncOper::in_use() const noexcept -{ - return m_in_use; -} - -inline bool Service::AsyncOper::is_complete() const noexcept -{ - return m_complete; -} - -inline void Service::AsyncOper::cancel() noexcept -{ - REALM_ASSERT(m_in_use); - REALM_ASSERT(!m_canceled); - m_canceled = true; -} - -inline Service::AsyncOper::AsyncOper(std::size_t size, bool is_in_use) noexcept - : m_size{size} - , m_in_use{is_in_use} -{ -} - -inline bool Service::AsyncOper::is_canceled() const noexcept -{ - return m_canceled; -} - -inline void Service::AsyncOper::set_is_complete(bool value) noexcept -{ - REALM_ASSERT(!m_complete); - REALM_ASSERT(!value || m_in_use); - m_complete = value; -} - -template -inline void Service::AsyncOper::do_recycle_and_execute(bool orphaned, H& handler, Args&&... args) -{ - // Recycle the operation object before the handler is exceuted, such that - // the memory is available for a new post operation that might be initiated - // during the execution of the handler. - bool was_recycled = false; - - // ScopeExit to ensure the AsyncOper object was reclaimed/deleted - auto at_exit = util::ScopeExit([this, &was_recycled, &orphaned]() noexcept { - if (!was_recycled) { - do_recycle(orphaned); - } - }); - - // We need to copy or move all arguments to be passed to the handler, - // such that there is no risk of references to the recycled operation - // object being passed to the handler (the passed arguments may be - // references to members of the recycled operation object). The easiest - // way to achive this, is by forwarding the reference arguments (passed - // to this function) to a helper function whose arguments have - // nonreference type (`Args...` rather than `Args&&...`). - // - // Note that the copying and moving of arguments may throw, and it is - // important that the operation is still recycled even if that - // happens. For that reason, copying and moving of arguments must not - // happen until we are in a scope (this scope) that catches and deals - // correctly with such exceptions. - do_recycle_and_execute_helper(orphaned, was_recycled, std::move(handler), - std::forward(args)...); // Throws - - // Removed catch to prevent truncating the stack trace on exception -} - -template -inline void Service::AsyncOper::do_recycle_and_execute_helper(bool orphaned, bool& was_recycled, H handler, - Args... args) -{ - do_recycle(orphaned); - was_recycled = true; - handler(std::move(args)...); // Throws -} - -inline void Service::AsyncOper::do_recycle(bool orphaned) noexcept -{ - REALM_ASSERT(in_use()); - void* addr = this; - std::size_t size = m_size; - this->~AsyncOper(); // Suicide - if (orphaned) { - delete[] static_cast(addr); - } - else { - new (addr) UnusedOper(size); - } -} - -// ---------------- Resolver ---------------- - -template -class Resolver::ResolveOper : public Service::ResolveOperBase { -public: - ResolveOper(std::size_t size, Resolver& r, Query q, H&& handler) - : ResolveOperBase{size, r, std::move(q)} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - REALM_ASSERT(is_complete() || (is_canceled() && !m_error_code)); - REALM_ASSERT(is_canceled() || m_error_code || !m_endpoints.empty()); - bool orphaned = !m_resolver; - std::error_code ec = m_error_code; - if (is_canceled()) - ec = util::error::operation_aborted; - // Note: do_recycle_and_execute() commits suicide. - do_recycle_and_execute(orphaned, m_handler, ec, std::move(m_endpoints)); // Throws - } - -private: - H m_handler; -}; - -inline Resolver::Resolver(Service& service) - : m_service_impl{*service.m_impl} -{ -} - -inline Resolver::~Resolver() noexcept -{ - cancel(); -} - -inline Endpoint::List Resolver::resolve(const Query& q) -{ - std::error_code ec; - Endpoint::List list = resolve(q, ec); - if (REALM_UNLIKELY(ec)) - throw std::system_error(ec); - return list; -} - -template -void Resolver::async_resolve(Query query, H&& handler) -{ - Service::LendersResolveOperPtr op = Service::alloc>(m_resolve_oper, *this, std::move(query), - std::move(handler)); // Throws - initiate_oper(std::move(op)); // Throws -} - -inline Resolver::Query::Query(std::string service_port, int init_flags) - : m_flags{init_flags} - , m_service{service_port} -{ -} - -inline Resolver::Query::Query(const StreamProtocol& prot, std::string service_port, int init_flags) - : m_flags{init_flags} - , m_protocol{prot} - , m_service{service_port} -{ -} - -inline Resolver::Query::Query(std::string host_name, std::string service_port, int init_flags) - : m_flags{init_flags} - , m_host{host_name} - , m_service{service_port} -{ -} - -inline Resolver::Query::Query(const StreamProtocol& prot, std::string host_name, std::string service_port, - int init_flags) - : m_flags{init_flags} - , m_protocol{prot} - , m_host{host_name} - , m_service{service_port} -{ -} - -inline Resolver::Query::~Query() noexcept {} - -inline int Resolver::Query::flags() const -{ - return m_flags; -} - -inline StreamProtocol Resolver::Query::protocol() const -{ - return m_protocol; -} - -inline std::string Resolver::Query::host() const -{ - return m_host; -} - -inline std::string Resolver::Query::service() const -{ - return m_service; -} - -// ---------------- SocketBase ---------------- - -inline SocketBase::SocketBase(Service& service) - : m_desc{*service.m_impl} -{ -} - -inline SocketBase::~SocketBase() noexcept -{ - close(); -} - -inline bool SocketBase::is_open() const noexcept -{ - return m_desc.is_open(); -} - -inline auto SocketBase::native_handle() const noexcept -> native_handle_type -{ - return m_desc.native_handle(); -} - -inline void SocketBase::open(const StreamProtocol& prot) -{ - std::error_code ec; - if (open(prot, ec)) - throw std::system_error(ec); -} - -inline void SocketBase::close() noexcept -{ - if (!is_open()) - return; - cancel(); - m_desc.close(); -} - -template -inline void SocketBase::get_option(O& opt) const -{ - std::error_code ec; - if (get_option(opt, ec)) - throw std::system_error(ec); -} - -template -inline std::error_code SocketBase::get_option(O& opt, std::error_code& ec) const -{ - opt.get(*this, ec); - return ec; -} - -template -inline void SocketBase::set_option(const O& opt) -{ - std::error_code ec; - if (set_option(opt, ec)) - throw std::system_error(ec); -} - -template -inline std::error_code SocketBase::set_option(const O& opt, std::error_code& ec) -{ - opt.set(*this, ec); - return ec; -} - -inline void SocketBase::bind(const Endpoint& ep) -{ - std::error_code ec; - if (bind(ep, ec)) - throw std::system_error(ec); -} - -inline Endpoint SocketBase::local_endpoint() const -{ - std::error_code ec; - Endpoint ep = local_endpoint(ec); - if (ec) - throw std::system_error(ec); - return ep; -} - -inline auto SocketBase::release_native_handle() noexcept -> native_handle_type -{ - if (is_open()) { - cancel(); - return m_desc.release(); - } - return m_desc.native_handle(); -} - -inline const StreamProtocol& SocketBase::get_protocol() const noexcept -{ - return m_protocol; -} - -template -inline SocketBase::Option::Option(T init_value) - : m_value{init_value} -{ -} - -template -inline T SocketBase::Option::value() const -{ - return m_value; -} - -template -inline void SocketBase::Option::get(const SocketBase& sock, std::error_code& ec) -{ - union { - U value; - char strut[sizeof(U) + 1]; - }; - std::size_t value_size = sizeof strut; - sock.get_option(opt_enum(opt), &value, value_size, ec); - if (!ec) { - REALM_ASSERT(value_size == sizeof value); - m_value = T(value); - } -} - -template -inline void SocketBase::Option::set(SocketBase& sock, std::error_code& ec) const -{ - U value_to_set = U(m_value); - sock.set_option(opt_enum(opt), &value_to_set, sizeof value_to_set, ec); -} - -// ---------------- Socket ---------------- - -class Socket::ConnectOperBase : public Service::IoOper { -public: - ConnectOperBase(std::size_t size, Socket& sock) noexcept - : IoOper{size} - , m_socket{&sock} - { - } - Want initiate(const Endpoint& ep) - { - REALM_ASSERT(this == m_socket->m_write_oper.get()); - if (m_socket->initiate_async_connect(ep, m_error_code)) { // Throws - set_is_complete(true); // Failure, or immediate completion - return Want::nothing; - } - return Want::write; - } - Want advance() noexcept override final - { - REALM_ASSERT(!is_complete()); - REALM_ASSERT(!is_canceled()); - REALM_ASSERT(!m_error_code); - m_socket->finalize_async_connect(m_error_code); - set_is_complete(true); - return Want::nothing; - } - void recycle() noexcept override final - { - bool orphaned = !m_socket; - REALM_ASSERT(orphaned); - // Note: do_recycle() commits suicide. - do_recycle(orphaned); - } - void orphan() noexcept override final - { - m_socket = nullptr; - } - Service::Descriptor& descriptor() noexcept override final - { - return m_socket->m_desc; - } - -protected: - Socket* m_socket; - std::error_code m_error_code; -}; - -template -class Socket::ConnectOper : public ConnectOperBase { -public: - ConnectOper(std::size_t size, Socket& sock, H&& handler) - : ConnectOperBase{size, sock} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - REALM_ASSERT(is_complete() || (is_canceled() && !m_error_code)); - bool orphaned = !m_socket; - std::error_code ec = m_error_code; - if (is_canceled()) - ec = util::error::operation_aborted; - // Note: do_recycle_and_execute() commits suicide. - do_recycle_and_execute(orphaned, m_handler, ec); // Throws - } - -private: - H m_handler; -}; - -inline Socket::Socket(Service& service) - : SocketBase{service} -{ -} - -inline Socket::Socket(Service& service, const StreamProtocol& prot, native_handle_type native_socket) - : SocketBase{service} -{ - assign(prot, native_socket); // Throws -} - -inline Socket::~Socket() noexcept {} - -inline void Socket::connect(const Endpoint& ep) -{ - std::error_code ec; - if (connect(ep, ec)) // Throws - throw std::system_error(ec); -} - -inline std::size_t Socket::read(char* buffer, std::size_t size) -{ - std::error_code ec; - read(buffer, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return size; -} - -inline std::size_t Socket::read(char* buffer, std::size_t size, std::error_code& ec) -{ - return StreamOps::read(*this, buffer, size, ec); // Throws -} - -inline std::size_t Socket::read(char* buffer, std::size_t size, ReadAheadBuffer& rab) -{ - std::error_code ec; - read(buffer, size, rab, ec); // Throws - if (ec) - throw std::system_error(ec); - return size; -} - -inline std::size_t Socket::read(char* buffer, std::size_t size, ReadAheadBuffer& rab, std::error_code& ec) -{ - int delim = std::char_traits::eof(); - return StreamOps::buffered_read(*this, buffer, size, delim, rab, ec); // Throws -} - -inline std::size_t Socket::read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer& rab) -{ - std::error_code ec; - std::size_t n = read_until(buffer, size, delim, rab, ec); // Throws - if (ec) - throw std::system_error(ec); - return n; -} - -inline std::size_t Socket::read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer& rab, - std::error_code& ec) -{ - int delim_2 = std::char_traits::to_int_type(delim); - return StreamOps::buffered_read(*this, buffer, size, delim_2, rab, ec); // Throws -} - -inline std::size_t Socket::write(const char* data, std::size_t size) -{ - std::error_code ec; - write(data, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return size; -} - -inline std::size_t Socket::write(const char* data, std::size_t size, std::error_code& ec) -{ - return StreamOps::write(*this, data, size, ec); // Throws -} - -inline std::size_t Socket::read_some(char* buffer, std::size_t size) -{ - std::error_code ec; - std::size_t n = read_some(buffer, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return n; -} - -inline std::size_t Socket::read_some(char* buffer, std::size_t size, std::error_code& ec) -{ - return StreamOps::read_some(*this, buffer, size, ec); // Throws -} - -inline std::size_t Socket::write_some(const char* data, std::size_t size) -{ - std::error_code ec; - std::size_t n = write_some(data, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return n; -} - -inline std::size_t Socket::write_some(const char* data, std::size_t size, std::error_code& ec) -{ - return StreamOps::write_some(*this, data, size, ec); // Throws -} - -template -inline void Socket::async_connect(const Endpoint& ep, H&& handler) -{ - LendersConnectOperPtr op = Service::alloc>(m_write_oper, *this, std::move(handler)); // Throws - m_desc.initiate_oper(std::move(op), ep); // Throws -} - -template -inline void Socket::async_read(char* buffer, std::size_t size, H&& handler) -{ - bool is_read_some = false; - StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws -} - -template -inline void Socket::async_read(char* buffer, std::size_t size, ReadAheadBuffer& rab, H&& handler) -{ - int delim = std::char_traits::eof(); - StreamOps::async_buffered_read(*this, buffer, size, delim, rab, std::move(handler)); // Throws -} - -template -inline void Socket::async_read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer& rab, H&& handler) -{ - int delim_2 = std::char_traits::to_int_type(delim); - StreamOps::async_buffered_read(*this, buffer, size, delim_2, rab, std::move(handler)); // Throws -} - -template -inline void Socket::async_write(const char* data, std::size_t size, H&& handler) -{ - bool is_write_some = false; - StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws -} - -template -inline void Socket::async_read_some(char* buffer, std::size_t size, H&& handler) -{ - bool is_read_some = true; - StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws -} - -template -inline void Socket::async_write_some(const char* data, std::size_t size, H&& handler) -{ - bool is_write_some = true; - StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws -} - -inline void Socket::shutdown(shutdown_type what) -{ - std::error_code ec; - if (shutdown(what, ec)) // Throws - throw std::system_error(ec); -} - -inline void Socket::assign(const StreamProtocol& prot, native_handle_type native_socket) -{ - std::error_code ec; - if (assign(prot, native_socket, ec)) // Throws - throw std::system_error(ec); -} - -inline std::error_code Socket::assign(const StreamProtocol& prot, native_handle_type native_socket, - std::error_code& ec) -{ - return do_assign(prot, native_socket, ec); // Throws -} - -inline Socket& Socket::lowest_layer() noexcept -{ - return *this; -} - -inline void Socket::do_init_read_async(std::error_code&, Want& want) noexcept -{ - want = Want::read; // Wait for read readiness before proceeding -} - -inline void Socket::do_init_write_async(std::error_code&, Want& want) noexcept -{ - want = Want::write; // Wait for write readiness before proceeding -} - -inline std::size_t Socket::do_read_some_sync(char* buffer, std::size_t size, std::error_code& ec) noexcept -{ - return m_desc.read_some(buffer, size, ec); -} - -inline std::size_t Socket::do_write_some_sync(const char* data, std::size_t size, std::error_code& ec) noexcept -{ - return m_desc.write_some(data, size, ec); -} - -inline std::size_t Socket::do_read_some_async(char* buffer, std::size_t size, std::error_code& ec, - Want& want) noexcept -{ - std::error_code ec_2; - std::size_t n = m_desc.read_some(buffer, size, ec_2); - bool success = (!ec_2 || ec_2 == util::error::resource_unavailable_try_again); - if (REALM_UNLIKELY(!success)) { - ec = ec_2; - want = Want::nothing; // Failure - return 0; - } - ec = std::error_code(); - want = Want::read; // Success - return n; -} - -inline std::size_t Socket::do_write_some_async(const char* data, std::size_t size, std::error_code& ec, - Want& want) noexcept -{ - std::error_code ec_2; - std::size_t n = m_desc.write_some(data, size, ec_2); - bool success = (!ec_2 || ec_2 == util::error::resource_unavailable_try_again); - if (REALM_UNLIKELY(!success)) { - ec = ec_2; - want = Want::nothing; // Failure - return 0; - } - ec = std::error_code(); - want = Want::write; // Success - return n; -} - -// ---------------- Acceptor ---------------- - -class Acceptor::AcceptOperBase : public Service::IoOper { -public: - AcceptOperBase(std::size_t size, Acceptor& a, Socket& s, Endpoint* e) - : IoOper{size} - , m_acceptor{&a} - , m_socket{s} - , m_endpoint{e} - { - } - Want initiate() - { - REALM_ASSERT(this == m_acceptor->m_read_oper.get()); - REALM_ASSERT(!is_complete()); - m_acceptor->m_desc.ensure_nonblocking_mode(); // Throws - return Want::read; - } - Want advance() noexcept override final - { - REALM_ASSERT(!is_complete()); - REALM_ASSERT(!is_canceled()); - REALM_ASSERT(!m_error_code); - REALM_ASSERT(!m_socket.is_open()); - Want want = m_acceptor->do_accept_async(m_socket, m_endpoint, m_error_code); - if (want == Want::nothing) - set_is_complete(true); // Success or failure - return want; - } - void recycle() noexcept override final - { - bool orphaned = !m_acceptor; - REALM_ASSERT(orphaned); - // Note: do_recycle() commits suicide. - do_recycle(orphaned); - } - void orphan() noexcept override final - { - m_acceptor = nullptr; - } - Service::Descriptor& descriptor() noexcept override final - { - return m_acceptor->m_desc; - } - -protected: - Acceptor* m_acceptor; - Socket& m_socket; // May be dangling after cancellation - Endpoint* const m_endpoint; // May be dangling after cancellation - std::error_code m_error_code; -}; - -template -class Acceptor::AcceptOper : public AcceptOperBase { -public: - AcceptOper(std::size_t size, Acceptor& a, Socket& s, Endpoint* e, H&& handler) - : AcceptOperBase{size, a, s, e} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - REALM_ASSERT(is_complete() || (is_canceled() && !m_error_code)); - REALM_ASSERT(is_canceled() || m_error_code || m_socket.is_open()); - bool orphaned = !m_acceptor; - std::error_code ec = m_error_code; - if (is_canceled()) - ec = util::error::operation_aborted; - // Note: do_recycle_and_execute() commits suicide. - do_recycle_and_execute(orphaned, m_handler, ec); // Throws - } - -private: - H m_handler; -}; - -inline Acceptor::Acceptor(Service& service) - : SocketBase{service} -{ -} - -inline Acceptor::~Acceptor() noexcept {} - -inline void Acceptor::listen(int backlog) -{ - std::error_code ec; - if (listen(backlog, ec)) // Throws - throw std::system_error(ec); -} - -inline void Acceptor::accept(Socket& sock) -{ - std::error_code ec; - if (accept(sock, ec)) // Throws - throw std::system_error(ec); -} - -inline void Acceptor::accept(Socket& sock, Endpoint& ep) -{ - std::error_code ec; - if (accept(sock, ep, ec)) // Throws - throw std::system_error(ec); -} - -inline std::error_code Acceptor::accept(Socket& sock, std::error_code& ec) -{ - Endpoint* ep = nullptr; - return accept(sock, ep, ec); // Throws -} - -inline std::error_code Acceptor::accept(Socket& sock, Endpoint& ep, std::error_code& ec) -{ - return accept(sock, &ep, ec); // Throws -} - -template -inline void Acceptor::async_accept(Socket& sock, H&& handler) -{ - Endpoint* ep = nullptr; - async_accept(sock, ep, std::move(handler)); // Throws -} - -template -inline void Acceptor::async_accept(Socket& sock, Endpoint& ep, H&& handler) -{ - async_accept(sock, &ep, std::move(handler)); // Throws -} - -inline std::error_code Acceptor::accept(Socket& socket, Endpoint* ep, std::error_code& ec) -{ - REALM_ASSERT(!m_read_oper || !m_read_oper->in_use()); - if (REALM_UNLIKELY(socket.is_open())) - throw util::runtime_error("Socket is already open"); - m_desc.ensure_blocking_mode(); // Throws - m_desc.accept(socket.m_desc, m_protocol, ep, ec); - return ec; -} - -inline Acceptor::Want Acceptor::do_accept_async(Socket& socket, Endpoint* ep, std::error_code& ec) noexcept -{ - std::error_code ec_2; - m_desc.accept(socket.m_desc, m_protocol, ep, ec_2); - if (ec_2 == util::error::resource_unavailable_try_again) - return Want::read; - ec = ec_2; - return Want::nothing; -} - -template -inline void Acceptor::async_accept(Socket& sock, Endpoint* ep, H&& handler) -{ - if (REALM_UNLIKELY(sock.is_open())) - throw util::runtime_error("Socket is already open"); - LendersAcceptOperPtr op = Service::alloc>(m_read_oper, *this, sock, ep, - std::move(handler)); // Throws - m_desc.initiate_oper(std::move(op)); // Throws -} - -// ---------------- DeadlineTimer ---------------- - -template -class DeadlineTimer::WaitOper : public Service::WaitOperBase { -public: - WaitOper(std::size_t size, DeadlineTimer& timer, clock::time_point expiration_time, H&& handler) - : Service::WaitOperBase{size, timer, expiration_time} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - bool orphaned = !m_timer; - Status status = Status::OK(); - if (is_canceled()) - status = Status{ErrorCodes::OperationAborted, "Timer canceled"}; - // Note: do_recycle_and_execute() commits suicide. - do_recycle_and_execute(orphaned, m_handler, status); // Throws - } - -private: - H m_handler; -}; - -inline DeadlineTimer::DeadlineTimer(Service& service) - : m_service_impl{*service.m_impl} -{ -} - -inline DeadlineTimer::~DeadlineTimer() noexcept -{ - cancel(); -} - -template -inline void DeadlineTimer::async_wait(std::chrono::duration delay, H&& handler) -{ - clock::time_point now = clock::now(); - // FIXME: This method of detecting overflow does not work. Comparison - // between distinct duration types is not overflow safe. Overflow easily - // happens in the implied conversion of arguments to the common duration - // type (std::common_type<>). - auto max_add = clock::time_point::max() - now; - if (delay > max_add) - throw util::overflow_error("Expiration time overflow"); - clock::time_point expiration_time = now + delay; - initiate_oper(Service::alloc>(m_wait_oper, *this, expiration_time, - std::move(handler))); // Throws -} - -// ---------------- ReadAheadBuffer ---------------- - -inline ReadAheadBuffer::ReadAheadBuffer() - : m_buffer{new char[s_size]} // Throws -{ -} - -inline void ReadAheadBuffer::clear() noexcept -{ - m_begin = nullptr; - m_end = nullptr; -} - -inline bool ReadAheadBuffer::empty() const noexcept -{ - return (m_begin == m_end); -} - -template -inline void ReadAheadBuffer::refill_sync(S& stream, std::error_code& ec) noexcept -{ - char* buffer = m_buffer.get(); - std::size_t size = s_size; - static_assert(noexcept(stream.do_read_some_sync(buffer, size, ec)), ""); - std::size_t n = stream.do_read_some_sync(buffer, size, ec); - if (REALM_UNLIKELY(n == 0)) - return; - REALM_ASSERT(!ec); - REALM_ASSERT(n <= size); - m_begin = m_buffer.get(); - m_end = m_begin + n; -} - -template -inline bool ReadAheadBuffer::refill_async(S& stream, std::error_code& ec, Want& want) noexcept -{ - char* buffer = m_buffer.get(); - std::size_t size = s_size; - static_assert(noexcept(stream.do_read_some_async(buffer, size, ec, want)), ""); - std::size_t n = stream.do_read_some_async(buffer, size, ec, want); - // Any errors reported by do_read_some_async() (other than end_of_input) should always return 0 - if (n == 0) - return false; - REALM_ASSERT(!ec || ec == util::MiscExtErrors::end_of_input); - REALM_ASSERT(n <= size); - m_begin = m_buffer.get(); - m_end = m_begin + n; - return true; -} - -} // namespace realm::sync::network diff --git a/src/realm/sync/network/network_ssl.cpp b/src/realm/sync/network/network_ssl.cpp deleted file mode 100644 index d06d4a5c269..00000000000 --- a/src/realm/sync/network/network_ssl.cpp +++ /dev/null @@ -1,1578 +0,0 @@ -#include -#include - -#include -#include -#include -#include - -#if REALM_HAVE_OPENSSL -#include -#include -#ifdef _WIN32 -using osslX509_NAME = X509_NAME; // alias this before including wincrypt.h because it gets clobbered -#include -#include -#else -#include -#endif -#elif REALM_HAVE_SECURE_TRANSPORT -#include -#include -#endif - -using namespace realm; -using namespace realm::util; -using namespace realm::sync::network; -using namespace realm::sync::network::ssl; - - -namespace { - -#if REALM_INCLUDE_CERTS - -const char* root_certs[] = { -#include -}; - -void populate_cert_store_with_included_certs(X509_STORE* store, std::error_code& ec) -{ - std::size_t num_certs = sizeof(root_certs) / sizeof(root_certs[0]); - - for (std::size_t i = 0; i < num_certs; ++i) { - ERR_clear_error(); - BIO* bio = BIO_new_mem_buf(const_cast(root_certs[i]), -1); - if (REALM_UNLIKELY(!bio)) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } - - ERR_clear_error(); - X509* cert = PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr); - BIO_free(bio); - if (REALM_UNLIKELY(!cert)) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } - - ERR_clear_error(); - int ret = X509_STORE_add_cert(store, cert); - X509_free(cert); - if (REALM_UNLIKELY(ret != 1)) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - } - } -} - -#endif // REALM_INCLUDE_CERTS - -#if REALM_HAVE_OPENSSL && _WIN32 - -/// Allow OpenSSL to look up certificates in the Windows Trusted Root Certification Authority list by implementing the -/// X509_LOOKUP interface. -class CapiLookup { -public: - CapiLookup() - { - // Try to open the store in all of these locations sequentially. Many of them might not exist, and the - // CERT_STORE_OPEN_EXISTING_FLAG flag - // will cause CertOpenStore to return null in which case we just move on to the next. - // The order is important - we go from the most likely to the least likely to optimize lookup. - static std::initializer_list store_locations{CERT_SYSTEM_STORE_CURRENT_USER, - CERT_SYSTEM_STORE_LOCAL_MACHINE, - CERT_SYSTEM_STORE_CURRENT_SERVICE, - CERT_SYSTEM_STORE_SERVICES, - CERT_SYSTEM_STORE_USERS, - CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, - CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, - CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE}; - - for (DWORD location : store_locations) { - constexpr DWORD flags = - CERT_STORE_READONLY_FLAG | CERT_STORE_SHARE_CONTEXT_FLAG | CERT_STORE_OPEN_EXISTING_FLAG; - HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, NULL, flags | location, L"ROOT"); - if (store) - m_stores.push_back(store); - } - } - - ~CapiLookup() - { - for (auto store : m_stores) { - CertCloseStore(store, 0); - } - } - - int get_by_subject(X509_LOOKUP* ctx, X509_LOOKUP_TYPE type, const osslX509_NAME* name, X509_OBJECT* ret) - { - if (type != X509_LU_X509) - return 0; - - // Convert the OpenSSL X509_NAME structure into its ASN.1 representation and construct a CAPI search parameter - CERT_NAME_BLOB capi_name = {0}; - capi_name.cbData = i2d_X509_NAME(name, &capi_name.pbData); - int result = 0; - - for (auto store : m_stores) { - PCCERT_CONTEXT cert = - CertFindCertificateInStore(store, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_NAME, &capi_name, NULL); - if (!cert) - continue; - - // Convert the ASN.1 representation of the CAPI certificate into an OpenSSL certificate and add it to the - // OpenSSL store - const unsigned char* encoded_cert_data = cert->pbCertEncoded; - X509* ossl_cert = d2i_X509(NULL, &encoded_cert_data, cert->cbCertEncoded); - result = X509_STORE_add_cert(X509_LOOKUP_get_store(ctx), ossl_cert); - X509_free(ossl_cert); - break; - } - - OPENSSL_free(capi_name.pbData); - - // if we previously added a certificate to the store we need to look it up again from the store and return - // that - if (result) - result = get_cached_object(ctx, type, name, ret); - return result; - } - -private: - int get_cached_object(X509_LOOKUP* ctx, X509_LOOKUP_TYPE type, const osslX509_NAME* name, X509_OBJECT* ret) - { - REALM_ASSERT_RELEASE(type == X509_LU_X509); - - // Loop through the objects already in the store to find the one we just added in get_by_subject. - // retrieve_by_subject returns a cert with refcount 1 but set1_X509 increases it. - // That's why we need to free it after before returning, otherwise it will leak. - - STACK_OF(X509_OBJECT)* objects = X509_STORE_get0_objects(X509_LOOKUP_get_store(ctx)); - X509_OBJECT* tmp = X509_OBJECT_retrieve_by_subject(objects, type, name); - if (!tmp) - return 0; - - X509* cert = X509_OBJECT_get0_X509(tmp); - int result = X509_OBJECT_set1_X509(ret, cert); - X509_free(cert); - - return result; - } - - std::vector m_stores; -}; - -void add_windows_certificate_store_lookup(X509_STORE* store) -{ - X509_LOOKUP_METHOD* capi_lookup = X509_LOOKUP_meth_new("capi"); - - X509_LOOKUP_meth_set_new_item(capi_lookup, [](X509_LOOKUP* ctx) { - auto* data = new CapiLookup(); - return X509_LOOKUP_set_method_data(ctx, data); - }); - X509_LOOKUP_meth_set_free(capi_lookup, [](X509_LOOKUP* ctx) { - auto* data = reinterpret_cast(X509_LOOKUP_get_method_data(ctx)); - delete data; - }); - X509_LOOKUP_meth_set_get_by_subject(capi_lookup, [](auto ctx, auto type, auto name, auto ret) { - auto* data = reinterpret_cast(X509_LOOKUP_get_method_data(ctx)); - return data->get_by_subject(ctx, type, name, ret); - }); - - X509_STORE_add_lookup(store, capi_lookup); -} - -#endif // REALM_HAVE_OPENSSL && _WIN32 - -#if REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)) - -// These must be made to execute before main() is called, i.e., before there is -// any chance of threads being spawned. -struct OpensslInit { - std::unique_ptr mutexes; - OpensslInit(); - ~OpensslInit(); -}; - -OpensslInit g_openssl_init; - - -void openssl_locking_func(int mode, int i, const char*, int) -{ - if (mode & CRYPTO_LOCK) { - g_openssl_init.mutexes[i].lock(); - } - else { - g_openssl_init.mutexes[i].unlock(); - } -} - - -OpensslInit::OpensslInit() -{ - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - std::size_t n = CRYPTO_num_locks(); - mutexes.reset(new std::mutex[n]); // Throws - CRYPTO_set_locking_callback(&openssl_locking_func); - /* - #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L) - null_compression_methods_ = sk_SSL_COMP_new_null(); - #endif - */ -} - - -OpensslInit::~OpensslInit() -{ - /* - #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L) - sk_SSL_COMP_free(null_compression_methods_); - #endif - */ - CRYPTO_set_locking_callback(0); - ERR_free_strings(); -#if OPENSSL_VERSION_NUMBER < 0x10000000L - ERR_remove_state(0); -#else - ERR_remove_thread_state(0); -#endif - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - CONF_modules_unload(1); -} - -#endif // REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)) - -} // unnamed namespace - - -namespace realm { -namespace sync { -namespace network { -namespace ssl { - -ErrorCategory error_category; - - -const char* ErrorCategory::name() const noexcept -{ - return "realm.sync.network.ssl"; -} - - -std::string ErrorCategory::message(int value) const -{ - switch (Errors(value)) { - case Errors::tls_handshake_failed: - return "SSL certificate rejected"; // Throws - } - REALM_ASSERT(false); - return {}; -} - - -bool ErrorCategory::equivalent(const std::error_code& ec, int condition) const noexcept -{ - switch (Errors(condition)) { - case Errors::tls_handshake_failed: -#if REALM_HAVE_OPENSSL - return ec.category() == openssl_error_category; -#elif REALM_HAVE_SECURE_TRANSPORT - return ec.category() == secure_transport_error_category; -#else - static_cast(ec); - return false; -#endif - } - return false; -} - -} // namespace ssl - - -OpensslErrorCategory openssl_error_category; - - -const char* OpensslErrorCategory::name() const noexcept -{ - return "openssl"; -} - - -std::string OpensslErrorCategory::message(int value) const -{ - const char* message = "Unknown error"; -#if REALM_HAVE_OPENSSL - if (const char* s = ERR_reason_error_string(value)) - message = s; -#endif - return util::format("OpenSSL error: %1 (%2)", message, value); // Throws -} - - -SecureTransportErrorCategory secure_transport_error_category; - - -const char* SecureTransportErrorCategory::name() const noexcept -{ - return "securetransport"; -} - - -std::string SecureTransportErrorCategory::message(int value) const -{ - const char* message = "Unknown error"; -#if REALM_HAVE_SECURE_TRANSPORT - std::unique_ptr buffer; - auto status = OSStatus(value); - void* reserved = nullptr; - if (auto cf_message = adoptCF(SecCopyErrorMessageString(status, reserved))) - message = cfstring_to_cstring(cf_message.get(), buffer); -#endif // REALM_HAVE_SECURE_TRANSPORT - - return util::format("SecureTransport error: %1 (%2)", message, value); // Throws -} - - -namespace ssl { - -const char* ProtocolNotSupported::what() const noexcept -{ - return "SSL/TLS protocol not supported"; -} - - -std::error_code Stream::handshake(std::error_code& ec) -{ - REALM_ASSERT(!m_tcp_socket.m_read_oper || !m_tcp_socket.m_read_oper->in_use()); - REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use()); - m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws - Want want = Want::nothing; - ssl_handshake(ec, want); - REALM_ASSERT(want == Want::nothing); - return ec; -} - - -std::error_code Stream::shutdown(std::error_code& ec) -{ - REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use()); - m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws - Want want = Want::nothing; - ssl_shutdown(ec, want); - REALM_ASSERT(want == Want::nothing); - return ec; -} - - -#if REALM_HAVE_OPENSSL - -void Context::ssl_init() -{ - ERR_clear_error(); - - // Despite the name, SSLv23_method isn't specific to SSLv2 and SSLv3. - // It negotiates with the peer to pick the newest enabled protocol version. - const SSL_METHOD* method = SSLv23_method(); - - SSL_CTX* ssl_ctx = SSL_CTX_new(method); - if (REALM_UNLIKELY(!ssl_ctx)) { - std::error_code ec(int(ERR_get_error()), openssl_error_category); - throw std::system_error(ec); - } - - // Disable use of older protocol versions (SSLv2 and SSLv3). - // Disable SSL compression by default, as compression is unavailable - // with Apple's Secure Transport API. - long options = 0; - options |= SSL_OP_NO_SSLv2; - options |= SSL_OP_NO_SSLv3; - options |= SSL_OP_NO_COMPRESSION; - SSL_CTX_set_options(ssl_ctx, options); - - m_ssl_ctx = ssl_ctx; -} - - -void Context::ssl_destroy() noexcept -{ - /* - if (handle_->default_passwd_callback_userdata) { - detail::password_callback_base* callback = - static_cast(handle_->default_passwd_callback_userdata); delete callback; - handle_->default_passwd_callback_userdata = nullptr; - } - - if (SSL_CTX_get_app_data(handle_)) { - detail::verify_callback_base* callback = - static_cast(SSL_CTX_get_app_data(handle_)); delete callback; - SSL_CTX_set_app_data(handle_, nullptr); - } - */ - SSL_CTX_free(m_ssl_ctx); -} - - -void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec) -{ - ERR_clear_error(); - int ret = SSL_CTX_use_certificate_chain_file(m_ssl_ctx, path.c_str()); - if (REALM_UNLIKELY(ret != 1)) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } - ec = std::error_code(); -} - - -void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec) -{ - ERR_clear_error(); - int type = SSL_FILETYPE_PEM; - int ret = SSL_CTX_use_PrivateKey_file(m_ssl_ctx, path.c_str(), type); - if (REALM_UNLIKELY(ret != 1)) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } - ec = std::error_code(); -} - - -void Context::ssl_use_default_verify(std::error_code& ec) -{ -#if REALM_USE_SYSTEM_OPENSSL_PATHS - ERR_clear_error(); - int ret = SSL_CTX_set_default_verify_paths(m_ssl_ctx); - if (ret != 1) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } -#endif -#ifdef _WIN32 - add_windows_certificate_store_lookup(SSL_CTX_get_cert_store(m_ssl_ctx)); -#endif - ec = std::error_code(); -} - - -void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec) -{ - ERR_clear_error(); - int ret = SSL_CTX_load_verify_locations(m_ssl_ctx, path.c_str(), nullptr); - if (ret != 1) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } - - ec = std::error_code(); -} - -#if REALM_INCLUDE_CERTS -void Context::ssl_use_included_certificate_roots(std::error_code& ec) -{ - X509_STORE* store = SSL_CTX_get_cert_store(m_ssl_ctx); - populate_cert_store_with_included_certs(store, ec); -} -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL) -class Stream::BioMethod { -public: - BIO_METHOD* bio_method; - - BioMethod() - { - const char* name = "realm::util::Stream::BioMethod"; - bio_method = BIO_meth_new(BIO_get_new_index(), name); - if (!bio_method) - throw util::bad_alloc(); - - BIO_meth_set_write(bio_method, &Stream::bio_write); - BIO_meth_set_read(bio_method, &Stream::bio_read); - BIO_meth_set_puts(bio_method, &Stream::bio_puts); - BIO_meth_set_gets(bio_method, nullptr); - BIO_meth_set_ctrl(bio_method, &Stream::bio_ctrl); - BIO_meth_set_create(bio_method, &Stream::bio_create); - BIO_meth_set_destroy(bio_method, &Stream::bio_destroy); - BIO_meth_set_callback_ctrl(bio_method, nullptr); - } - - ~BioMethod() - { - BIO_meth_free(bio_method); - } -}; -#else -class Stream::BioMethod { -public: - BIO_METHOD* bio_method; - - BioMethod() - { - bio_method = new BIO_METHOD{ - BIO_TYPE_SOCKET, // int type - nullptr, // const char* name - &Stream::bio_write, // int (*bwrite)(BIO*, const char*, int) - &Stream::bio_read, // int (*bread)(BIO*, char*, int) - &Stream::bio_puts, // int (*bputs)(BIO*, const char*) - nullptr, // int (*bgets)(BIO*, char*, int) - &Stream::bio_ctrl, // long (*ctrl)(BIO*, int, long, void*) - &Stream::bio_create, // int (*create)(BIO*) - &Stream::bio_destroy, // int (*destroy)(BIO*) - nullptr // long (*callback_ctrl)(BIO*, int, bio_info_cb*) - }; - } - - ~BioMethod() - { - delete bio_method; - } -}; -#endif - - -Stream::BioMethod Stream::s_bio_method; - - -#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER) - -namespace { - -// check_common_name() checks that \param server_cert constains host_name -// as Common Name. The function is used by verify_callback() for -// OpenSSL versions before 1.0.2. -bool check_common_name(X509* server_cert, const std::string& host_name) -{ - // Find the position of the Common Name field in the Subject field of the certificate - int common_name_loc = -1; - common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(server_cert), NID_commonName, -1); - if (common_name_loc < 0) - return false; - - // Extract the Common Name field - X509_NAME_ENTRY* common_name_entry; - common_name_entry = X509_NAME_get_entry(X509_get_subject_name(server_cert), common_name_loc); - if (!common_name_entry) - return false; - - // Convert the Common Namefield to a C string - ASN1_STRING* common_name_asn1; - common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); - if (!common_name_asn1) - return false; - - char* common_name_str = reinterpret_cast(ASN1_STRING_data(common_name_asn1)); - - // Make sure there isn't an embedded NUL character in the Common Name - if (static_cast(ASN1_STRING_length(common_name_asn1)) != std::strlen(common_name_str)) - return false; - - bool names_equal = (host_name == common_name_str); - return names_equal; -} - -// check_common_name() checks that \param server_cert constains host_name -// in the Subject Alternative Name DNS section. The function is used by verify_callback() -// for OpenSSL versions before 1.0.2. -bool check_san(X509* server_cert, const std::string& host_name) -{ - STACK_OF(GENERAL_NAME) * san_names; - - // Try to extract the names within the SAN extension from the certificate - san_names = - static_cast(X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); - if (!san_names) - return false; - - int san_names_nb = sk_GENERAL_NAME_num(san_names); - - bool found = false; - - // Check each name within the extension - for (int i = 0; i < san_names_nb; ++i) { - const GENERAL_NAME* current_name = sk_GENERAL_NAME_value(san_names, i); - - if (current_name->type == GEN_DNS) { - // Current name is a DNS name - char* dns_name = static_cast(ASN1_STRING_data(current_name->d.dNSName)); - - // Make sure there isn't an embedded NUL character in the DNS name - if (static_cast(ASN1_STRING_length(current_name->d.dNSName)) != std::strlen(dns_name)) - break; - - if (host_name == dns_name) { - found = true; - break; - } - } - } - - sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); - - return found; -} - -} // namespace - -int Stream::verify_callback_using_hostname(int preverify_ok, X509_STORE_CTX* ctx) noexcept -{ - if (preverify_ok != 1) - return preverify_ok; - - X509* server_cert = X509_STORE_CTX_get_current_cert(ctx); - - int err = X509_STORE_CTX_get_error(ctx); - if (err != X509_V_OK) - return 0; - - int depth = X509_STORE_CTX_get_error_depth(ctx); - - // We only inspect the certificate at depth = 0. - if (depth > 0) - return preverify_ok; - - // Retrieve the pointer to the SSL object for this connection. - SSL* ssl = static_cast(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - - // The stream object is stored as data in the SSL object. - Stream* stream = static_cast(SSL_get_ex_data(ssl, 0)); - - const std::string& host_name = stream->m_host_name; - - if (check_common_name(server_cert, host_name)) - return 1; - - if (check_san(server_cert, host_name)) - return 1; - - return 0; -} - -#endif - - -void Stream::ssl_set_verify_mode(VerifyMode mode, std::error_code& ec) -{ - int mode_2 = 0; - switch (mode) { - case VerifyMode::none: - break; - case VerifyMode::peer: - mode_2 = SSL_VERIFY_PEER; - break; - } - - int rc = SSL_set_ex_data(m_ssl, 0, this); - if (rc == 0) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } - -#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER) - SSL_set_verify(m_ssl, mode_2, &Stream::verify_callback_using_hostname); -#else - // verify_callback is nullptr. - SSL_set_verify(m_ssl, mode_2, nullptr); -#endif - ec = std::error_code(); -} - - -void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec) -{ - // Enable Server Name Indication (SNI) extension -#if OPENSSL_VERSION_NUMBER >= 0x10101000L - { -#ifndef _WIN32 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif - auto ret = SSL_set_tlsext_host_name(m_ssl, host_name.c_str()); -#ifndef _WIN32 -#pragma GCC diagnostic pop -#endif - if (ret == 0) { - ec = std::error_code(int(ERR_get_error()), openssl_error_category); - return; - } - } -#else - static_cast(host_name); - static_cast(ec); -#endif - - // Enable host name check during certificate validation -#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER) - { - X509_VERIFY_PARAM* param = SSL_get0_param(m_ssl); - X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - auto ret = X509_VERIFY_PARAM_set1_host(param, host_name.c_str(), host_name.size()); - if (ret == 0) { - int sys_error = int(ERR_get_error()); - // BoringSSL can return 0 here without actually pushing on the error stack - REALM_ASSERT_DEBUG(sys_error != 0); - ec = std::error_code(sys_error, openssl_error_category); - return; - } - } -#else - static_cast(host_name); - static_cast(ec); -#endif -} - -void Stream::ssl_use_verify_callback(const std::function& callback, std::error_code&) -{ - m_ssl_verify_callback = &callback; - - SSL_set_verify(m_ssl, SSL_VERIFY_PEER, &Stream::verify_callback_using_delegate); -} - -#ifndef _WIN32 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif - -int Stream::verify_callback_using_delegate(int preverify_ok, X509_STORE_CTX* ctx) noexcept -{ - X509* server_cert = X509_STORE_CTX_get_current_cert(ctx); - - int depth = X509_STORE_CTX_get_error_depth(ctx); - - BIO* bio = BIO_new(BIO_s_mem()); - if (!bio) { - // certificate rejected if a memory error occurs. - return 0; - } - - int ret = PEM_write_bio_X509(bio, server_cert); - if (!ret) { - BIO_free(bio); - return 0; - } - - BUF_MEM* buffer; - BIO_get_mem_ptr(bio, &buffer); - - const char* pem_data = buffer->data; - std::size_t pem_size = buffer->length; - - SSL* ssl = static_cast(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - Stream* stream = static_cast(SSL_get_ex_data(ssl, 0)); - const std::string& host_name = stream->m_host_name; - port_type server_port = stream->m_server_port; - - REALM_ASSERT(stream->m_ssl_verify_callback); - const std::function& callback = *stream->m_ssl_verify_callback; - - // FIXME: Oops, the callback may throw, but verify_callback_using_delegate() - // is not allowed to throw. It does not seem to be reasonable to deny the - // callback the opportunity of throwing. The right solution seems to be to - // carry an exception across the OpenSSL C-layer using the exception object - // transportation mechanism offered by C++. - bool valid = callback(host_name, server_port, pem_data, pem_size, preverify_ok, depth); // Throws - - BIO_free(bio); - return int(valid); -} - -#ifndef _WIN32 -#pragma GCC diagnostic pop -#endif - -void Stream::ssl_init() -{ - SSL_CTX* ssl_ctx = m_ssl_context.m_ssl_ctx; - SSL* ssl = SSL_new(ssl_ctx); - if (REALM_UNLIKELY(!ssl)) { - std::error_code ec(int(ERR_get_error()), openssl_error_category); - throw std::system_error(ec); - } - SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); -#if defined(SSL_MODE_RELEASE_BUFFERS) - SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS); -#endif - - BIO* bio = BIO_new(s_bio_method.bio_method); - - if (REALM_UNLIKELY(!bio)) { - SSL_free(ssl); - std::error_code ec(int(ERR_get_error()), openssl_error_category); - throw std::system_error(ec); - } - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) - BIO_set_data(bio, this); -#else - bio->ptr = this; -#endif - - SSL_set_bio(ssl, bio, bio); - m_ssl = ssl; -} - - -void Stream::ssl_destroy() noexcept -{ - SSL_free(m_ssl); -} - - -int Stream::bio_write(BIO* bio, const char* data, int size) noexcept -{ -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) - Stream& stream = *static_cast(BIO_get_data(bio)); -#else - Stream& stream = *static_cast(bio->ptr); -#endif - Service::Descriptor& desc = stream.m_tcp_socket.m_desc; - std::error_code ec; - std::size_t n = desc.write_some(data, std::size_t(size), ec); - - BIO_clear_retry_flags(bio); - if (ec) { - if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) { - stream.m_bio_error_code = ec; - return -1; - } - BIO_set_retry_write(bio); - return -1; - } - return int(n); -} - - -int Stream::bio_read(BIO* bio, char* buffer, int size) noexcept -{ -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) - Stream& stream = *static_cast(BIO_get_data(bio)); -#else - Stream& stream = *static_cast(bio->ptr); -#endif - Service::Descriptor& desc = stream.m_tcp_socket.m_desc; - std::error_code ec; - std::size_t n = desc.read_some(buffer, std::size_t(size), ec); - - BIO_clear_retry_flags(bio); - if (ec) { - if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) { - // This behaviour agrees with `crypto/bio/bss_sock.c` of OpenSSL. - return 0; - } - if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) { - stream.m_bio_error_code = ec; - return -1; - } - BIO_set_retry_read(bio); - return -1; - } - return int(n); -} - - -int Stream::bio_puts(BIO* bio, const char* c_str) noexcept -{ - std::size_t size = std::strlen(c_str); - return bio_write(bio, c_str, int(size)); -} - - -long Stream::bio_ctrl(BIO*, int cmd, long, void*) noexcept -{ - switch (cmd) { - case BIO_CTRL_EOF: - return 0; - case BIO_CTRL_PUSH: - case BIO_CTRL_POP: - // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL. - return 0; - case BIO_CTRL_FLUSH: - // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL. - return 1; -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - case BIO_CTRL_GET_KTLS_SEND: - case BIO_CTRL_GET_KTLS_RECV: - return 0; -#endif - default: - REALM_ASSERT_EX(false, "Got BIO_ctrl with unknown command %d", cmd); - } - return 0; -} - - -int Stream::bio_create(BIO* bio) noexcept -{ -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) - BIO_set_init(bio, 1); - BIO_set_data(bio, nullptr); - BIO_clear_flags(bio, 0); - BIO_set_shutdown(bio, 0); -#else - // In alignment with `crypto/bio/bss_sock.c` of OpenSSL. - bio->init = 1; - bio->num = 0; - bio->ptr = nullptr; - bio->flags = 0; -#endif - return 1; -} - - -int Stream::bio_destroy(BIO*) noexcept -{ - return 1; -} - - -#elif REALM_HAVE_SECURE_TRANSPORT - -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // FIXME: Should this be removed at some point? - -void Context::ssl_init() {} - -void Context::ssl_destroy() noexcept -{ -#if REALM_HAVE_KEYCHAIN_APIS - if (m_keychain) { - m_keychain.reset(); - unlink(m_keychain_path.data()); - m_keychain_path = {}; - } -#endif -} - -// Load certificates and/or keys from the specified PEM file. If keychain is non-null, the items will be -// imported into that keychain. -util::CFPtr Context::load_pem_file(const std::string& path, SecKeychainRef keychain, std::error_code& ec) -{ - using util::adoptCF; - using util::CFPtr; - - std::ifstream file(path); - if (!file) { - // Rely on the open attempt having set errno to a sensible value as ifstream's - // own error reporting gives terribly generic error messages. - ec = make_basic_system_error_code(errno); - return util::CFPtr(); - } - std::vector contents{std::istreambuf_iterator(file), std::istreambuf_iterator()}; - - auto contentsCF = adoptCF(CFDataCreateWithBytesNoCopy(nullptr, reinterpret_cast(contents.data()), - contents.size(), kCFAllocatorNull)); - - // If we don't need to import it into a keychain, try to interpret the data - // as a certificate directly. This only works for DER files, so we fall back - // to SecItemImport() on platforms which support that if this fails. - if (keychain == nullptr) { - if (auto certificate = adoptCF(SecCertificateCreateWithData(NULL, contentsCF.get()))) { - auto ref = certificate.get(); - return adoptCF(CFArrayCreate(nullptr, const_cast(reinterpret_cast(&ref)), 1, - &kCFTypeArrayCallBacks)); - } - - // SecCertificateCreateWithData doesn't tell us why it failed, so just - // report the error code that SecItemImport uses when given something - // that's not a certificate - ec = std::error_code(errSecUnknownFormat, secure_transport_error_category); - } - - CFArrayRef items = nullptr; - -#if REALM_HAVE_KEYCHAIN_APIS - SecItemImportExportKeyParameters params{}; - params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; - - CFPtr pathCF = adoptCF(CFStringCreateWithBytes(nullptr, reinterpret_cast(path.data()), - path.size(), kCFStringEncodingUTF8, false)); - - SecExternalFormat format = kSecFormatUnknown; - SecExternalItemType itemType = kSecItemTypeUnknown; - if (OSStatus status = - SecItemImport(contentsCF.get(), pathCF.get(), &format, &itemType, 0, ¶ms, keychain, &items)) { - ec = std::error_code(status, secure_transport_error_category); - return util::CFPtr(); - } - ec = {}; -#endif - - return adoptCF(items); -} - -#if REALM_HAVE_KEYCHAIN_APIS - -static std::string temporary_directory() -{ - auto ensure_trailing_slash = [](auto str) { - return str.back() == '/' ? str : str + '/'; - }; - - std::string path; - path.resize(PATH_MAX); - std::size_t result = confstr(_CS_DARWIN_USER_TEMP_DIR, &path[0], path.size()); - if (result && result <= path.size()) { - path.resize(result - 1); - return ensure_trailing_slash(std::move(path)); - } - - // We failed to retrieve temporary directory from confstr. Fall back to the TMPDIR - // environment variable if we're not running with elevated privileges, and then to /tmp. - if (!issetugid()) { - path = getenv("TMPDIR"); - if (path.size()) { - return ensure_trailing_slash(std::move(path)); - } - } - return "/tmp/"; -} - - -std::error_code Context::open_temporary_keychain_if_needed() -{ - if (m_keychain) { - return std::error_code(); - } - - std::string path = temporary_directory() + "realm-sync-ssl-XXXXXXXX.keychain"; - int fd = mkstemps(&path[0], std::strlen(".keychain")); - if (fd < 0) { - return make_basic_system_error_code(errno); - } - - // Close and remove the file so that we can create a keychain in its place. - close(fd); - unlink(path.data()); - - SecKeychainRef keychain = nullptr; - std::string password = ""; - if (OSStatus status = - SecKeychainCreate(path.data(), UInt32(password.size()), password.data(), false, nullptr, &keychain)) - return std::error_code(status, secure_transport_error_category); - - m_keychain = adoptCF(keychain); - m_keychain_path = std::move(path); - - return std::error_code(); -} - - -// Creates an identity from the certificate and private key. The private key must exist in m_keychain. -std::error_code Context::update_identity_if_needed() -{ - // If we've not yet loaded both the certificate and private key there's nothing to do. - if (!m_certificate || !m_private_key) { - return std::error_code(); - } - - SecIdentityRef identity = nullptr; - if (OSStatus status = SecIdentityCreateWithCertificate(m_keychain.get(), m_certificate.get(), &identity)) { - return std::error_code(status, secure_transport_error_category); - } - - m_identity = util::adoptCF(identity); - return std::error_code(); -} - -#endif // REALM_HAVE_KEYCHAIN_APIS - -void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec) -{ -#if !REALM_HAVE_KEYCHAIN_APIS - static_cast(path); - ec = make_basic_system_error_code(ENOTSUP); -#else - auto items = load_pem_file(path, nullptr, ec); - if (!items) { - REALM_ASSERT(ec); - return; - } - - if (CFArrayGetCount(items.get()) < 1) { - ec = std::error_code(errSecItemNotFound, secure_transport_error_category); - return; - } - - CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0); - if (CFGetTypeID(item) != SecCertificateGetTypeID()) { - ec = std::error_code(errSecItemNotFound, secure_transport_error_category); - return; - } - - m_certificate = util::retainCF(reinterpret_cast(const_cast(item))); - - // The returned array contains the server certificate followed by the remainder of the certificates in the chain. - // Remove the server certificate to leave us with an array containing only the remainder of the certificate chain. - auto certificate_chain = util::adoptCF(CFArrayCreateMutableCopy(nullptr, 0, items.get())); - CFArrayRemoveValueAtIndex(certificate_chain.get(), 0); - m_certificate_chain = util::adoptCF(reinterpret_cast(certificate_chain.release())); - - ec = update_identity_if_needed(); -#endif -} - - -void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec) -{ -#if !REALM_HAVE_KEYCHAIN_APIS - static_cast(path); - ec = make_basic_system_error_code(ENOTSUP); -#else - ec = open_temporary_keychain_if_needed(); - if (ec) { - return; - } - - auto items = load_pem_file(path, m_keychain.get(), ec); - if (!items) { - return; - } - - if (CFArrayGetCount(items.get()) != 1) { - ec = std::error_code(errSecItemNotFound, secure_transport_error_category); - return; - } - - CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0); - if (CFGetTypeID(item) != SecKeyGetTypeID()) { - ec = std::error_code(errSecItemNotFound, secure_transport_error_category); - return; - } - - m_private_key = util::retainCF(reinterpret_cast(const_cast(item))); - ec = update_identity_if_needed(); -#endif -} - -void Context::ssl_use_default_verify(std::error_code&) {} - -void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec) -{ -#if REALM_HAVE_KEYCHAIN_APIS - m_trust_anchors = load_pem_file(path, m_keychain.get(), ec); -#else - m_trust_anchors = load_pem_file(path, nullptr, ec); -#endif - - if (m_trust_anchors && CFArrayGetCount(m_trust_anchors.get())) { - const void* leaf_certificate = CFArrayGetValueAtIndex(m_trust_anchors.get(), 0); - m_pinned_certificate = - adoptCF(SecCertificateCopyData(static_cast(const_cast(leaf_certificate)))); - } - else { - m_pinned_certificate.reset(); - } -} - -void Stream::ssl_init() -{ - SSLProtocolSide side = m_handshake_type == HandshakeType::client ? kSSLClientSide : kSSLServerSide; - m_ssl = util::adoptCF(SSLCreateContext(nullptr, side, kSSLStreamType)); - if (OSStatus status = SSLSetIOFuncs(m_ssl.get(), Stream::tcp_read, Stream::tcp_write)) { - std::error_code ec(status, secure_transport_error_category); - throw std::system_error(ec); - } - if (OSStatus status = SSLSetConnection(m_ssl.get(), this)) { - std::error_code ec(status, secure_transport_error_category); - throw std::system_error(ec); - } - - // Require TLSv1 or greater. - if (OSStatus status = SSLSetProtocolVersionMin(m_ssl.get(), kTLSProtocol1)) { - std::error_code ec(status, secure_transport_error_category); - throw std::system_error(ec); - } - - // Break after certificate exchange to allow for customizing the verification process. - SSLSessionOption option = m_handshake_type == HandshakeType::client ? kSSLSessionOptionBreakOnServerAuth - : kSSLSessionOptionBreakOnClientAuth; - if (OSStatus status = SSLSetSessionOption(m_ssl.get(), option, true)) { - std::error_code ec(status, secure_transport_error_category); - throw std::system_error(ec); - } - -#if REALM_HAVE_KEYCHAIN_APIS - if (m_ssl_context.m_identity && m_ssl_context.m_certificate_chain) { - // SSLSetCertificate expects an array containing the identity followed by the identity's certificate chain. - auto certificates = util::adoptCF(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks)); - CFArrayInsertValueAtIndex(certificates.get(), 0, m_ssl_context.m_identity.get()); - - CFArrayRef certificate_chain = m_ssl_context.m_certificate_chain.get(); - CFArrayAppendArray(certificates.get(), certificate_chain, CFRangeMake(0, CFArrayGetCount(certificate_chain))); - - if (OSStatus status = SSLSetCertificate(m_ssl.get(), certificates.get())) { - std::error_code ec(status, secure_transport_error_category); - throw std::system_error(ec); - } - } -#endif -} - - -void Stream::ssl_destroy() noexcept -{ - m_ssl.reset(); -} - - -void Stream::ssl_set_verify_mode(VerifyMode verify_mode, std::error_code& ec) -{ - m_verify_mode = verify_mode; - ec = std::error_code(); -} - - -void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec) -{ - if (OSStatus status = SSLSetPeerDomainName(m_ssl.get(), host_name.data(), host_name.size())) - ec = std::error_code(status, secure_transport_error_category); -} - -void Stream::ssl_use_verify_callback(const std::function&, std::error_code&) {} - -void Stream::ssl_handshake(std::error_code& ec, Want& want) noexcept -{ - auto perform = [this]() noexcept { - return do_ssl_handshake(); - }; - ssl_perform(std::move(perform), ec, want); -} - -std::pair Stream::do_ssl_handshake() noexcept -{ - OSStatus result = SSLHandshake(m_ssl.get()); - if (result != errSSLPeerAuthCompleted) { - return {result, 0}; - } - - if (OSStatus status = verify_peer()) { - // When performing peer verification internally, verification failure results in SecureTransport - // sending a fatal alert to the peer, closing the connection. Sadly SecureTransport has no way - // to explicitly send a fatal alert when trust evaluation is handled externally. The best we can - // do is close the connection gracefully. - SSLClose(m_ssl.get()); - return {status, 0}; - } - - // Verification succeeded. Resume the handshake. - return do_ssl_handshake(); -} - - -OSStatus Stream::verify_peer() noexcept -{ - switch (m_verify_mode) { - case VerifyMode::none: - // Peer verification is disabled. - return noErr; - - case VerifyMode::peer: { - SecTrustRef peerTrustRef = nullptr; - if (OSStatus status = SSLCopyPeerTrust(m_ssl.get(), &peerTrustRef)) { - return status; - } - - auto peerTrust = util::adoptCF(peerTrustRef); - - if (m_ssl_context.m_trust_anchors) { - if (OSStatus status = - SecTrustSetAnchorCertificates(peerTrust.get(), m_ssl_context.m_trust_anchors.get())) { - return status; - } - if (OSStatus status = SecTrustSetAnchorCertificatesOnly(peerTrust.get(), true)) { - return status; - } - } - - // FIXME: SecTrustEvaluate can block if evaluation needs to fetch missing intermediate - // certificates or to check revocation using OCSP. Consider disabling these network - // fetches or doing async trust evaluation instead. -#if __has_builtin(__builtin_available) - if (__builtin_available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)) { - CFErrorRef cfErrorRef; - if (!SecTrustEvaluateWithError(peerTrust.get(), &cfErrorRef)) { - auto cfError = util::adoptCF(cfErrorRef); - if (logger && logger->would_log(Logger::Level::debug)) { - auto errorStr = util::adoptCF(CFErrorCopyDescription(cfErrorRef)); - std::unique_ptr buffer; - logger->debug("SSL peer verification failed: %1", - cfstring_to_cstring(errorStr.get(), buffer)); - } - return errSSLXCertChainInvalid; - } - } - else -#endif - { - SecTrustResultType trustResult; - if (OSStatus status = SecTrustEvaluate(peerTrust.get(), &trustResult)) { - return status; - } - - // A "proceed" result means the cert is explicitly trusted, e.g. "Always Trust" was selected. - // "Unspecified" means the cert has no explicit trust settings, but is implicitly OK since it - // chains back to a trusted root. Any other result means the cert is not trusted. - if (trustResult == kSecTrustResultRecoverableTrustFailure) { - // Not trusted. - return errSSLXCertChainInvalid; - } - if (trustResult != kSecTrustResultProceed && trustResult != kSecTrustResultUnspecified) { - return errSSLBadCert; - } - } - - if (!m_ssl_context.m_pinned_certificate) { - // Certificate is trusted! - return noErr; - } - - // Verify that the certificate is one of our pinned certificates - // Loop backwards as the pinned certificate will normally be the last one - for (CFIndex i = SecTrustGetCertificateCount(peerTrust.get()); i > 0; --i) { - SecCertificateRef certificate = SecTrustGetCertificateAtIndex(peerTrust.get(), i - 1); - auto certificate_data = adoptCF(SecCertificateCopyData(certificate)); - if (CFEqual(certificate_data.get(), m_ssl_context.m_pinned_certificate.get())) { - return noErr; - } - } - - // Although the cerificate is valid, it's not the one we've pinned so reject it. - return errSSLXCertChainInvalid; - } - } -} - - -std::size_t Stream::ssl_read(char* buffer, std::size_t size, std::error_code& ec, Want& want) noexcept -{ - auto perform = [this, buffer, size]() noexcept { - return do_ssl_read(buffer, size); - }; - std::size_t n = ssl_perform(std::move(perform), ec, want); - if (want == Want::nothing && n == 0 && !ec) { - // End of input on TCP socket - SSLSessionState state; - if (SSLGetSessionState(m_ssl.get(), &state) == noErr && state == kSSLClosed) { - ec = MiscExtErrors::end_of_input; - } - else { - ec = MiscExtErrors::premature_end_of_input; - } - } - return n; -} - -std::pair Stream::do_ssl_read(char* buffer, std::size_t size) noexcept -{ - std::size_t processed = 0; - OSStatus result = SSLRead(m_ssl.get(), buffer, size, &processed); - return {result, processed}; -} - - -std::size_t Stream::ssl_write(const char* data, std::size_t size, std::error_code& ec, Want& want) noexcept -{ - auto perform = [this, data, size]() noexcept { - return do_ssl_write(data, size); - }; - std::size_t n = ssl_perform(std::move(perform), ec, want); - if (want == Want::nothing && n == 0 && !ec) { - // End of input on TCP socket - ec = MiscExtErrors::premature_end_of_input; - } - return n; -} - -std::pair Stream::do_ssl_write(const char* data, std::size_t size) noexcept -{ - m_last_error = {}; - - REALM_ASSERT(size >= m_num_partially_written_bytes); - data += m_num_partially_written_bytes; - size -= m_num_partially_written_bytes; - - std::size_t processed = 0; - OSStatus result = SSLWrite(m_ssl.get(), data, size, &processed); - - if (result != noErr) { - // Map errors that indicate the connection is closed to broken_pipe, for - // consistency with OpenSSL. - if (REALM_LIKELY(result == errSSLWouldBlock)) { - m_num_partially_written_bytes += processed; - } - else if (result == errSSLClosedGraceful || result == errSSLClosedAbort || result == errSSLClosedNoNotify) { - result = errSecIO; - m_last_error = error::broken_pipe; - } - processed = 0; - } - else { - processed += m_num_partially_written_bytes; - m_num_partially_written_bytes = 0; - } - - return {result, processed}; -} - - -bool Stream::ssl_shutdown(std::error_code& ec, Want& want) noexcept -{ - auto perform = [this]() noexcept { - return do_ssl_shutdown(); - }; - std::size_t n = ssl_perform(std::move(perform), ec, want); - REALM_ASSERT(n == 0 || n == 1); - return (n > 0); -} - -std::pair Stream::do_ssl_shutdown() noexcept -{ - SSLSessionState previousState; - if (OSStatus result = SSLGetSessionState(m_ssl.get(), &previousState)) { - return {result, false}; - } - if (OSStatus result = SSLClose(m_ssl.get())) { - return {result, false}; - } - - // SSLClose returns noErr if it encountered an I/O error. We can still - // detect such errors if they originated from our underlying tcp_read / - // tcp_write functions as we'll have set m_last_error in such cases. This - // allows us to reconstruct the I/O error and communicate it to our caller. - if (m_last_error) { - return {errSecIO, false}; - } - return {noErr, previousState == kSSLClosed}; -} - - -OSStatus Stream::tcp_read(SSLConnectionRef connection, void* data, std::size_t* length) noexcept -{ - return static_cast(const_cast(connection))->tcp_read(data, length); -} - -OSStatus Stream::tcp_read(void* data, std::size_t* length) noexcept -{ - Service::Descriptor& desc = m_tcp_socket.m_desc; - std::error_code ec; - std::size_t bytes_read = desc.read_some(reinterpret_cast(data), *length, ec); - - m_last_operation = BlockingOperation::read; - - // A successful but short read should be treated the same as EAGAIN. - if (!ec && bytes_read < *length) { - ec = error::resource_unavailable_try_again; - } - - *length = bytes_read; - m_last_error = ec; - - if (ec) { - if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) { - return noErr; - } - if (ec == error::resource_unavailable_try_again) { - return errSSLWouldBlock; - } - return errSecIO; - } - return noErr; -} - -OSStatus Stream::tcp_write(SSLConnectionRef connection, const void* data, std::size_t* length) noexcept -{ - return static_cast(const_cast(connection))->tcp_write(data, length); -} - -OSStatus Stream::tcp_write(const void* data, std::size_t* length) noexcept -{ - Service::Descriptor& desc = m_tcp_socket.m_desc; - std::error_code ec; - std::size_t bytes_written = desc.write_some(reinterpret_cast(data), *length, ec); - - m_last_operation = BlockingOperation::write; - - // A successful but short write should be treated the same as EAGAIN. - if (!ec && bytes_written < *length) { - ec = error::resource_unavailable_try_again; - } - - *length = bytes_written; - m_last_error = ec; - - if (ec) { - if (ec == error::resource_unavailable_try_again) { - return errSSLWouldBlock; - } - return errSecIO; - } - return noErr; -} - - -#else // !REALM_HAVE_OPENSSL && !REALM_HAVE_SECURE_TRANSPORT - - -void Context::ssl_init() -{ - throw ProtocolNotSupported(); -} - - -void Context::ssl_destroy() noexcept {} - - -void Stream::ssl_init() {} - - -void Stream::ssl_destroy() noexcept {} - - -void Context::ssl_use_certificate_chain_file(const std::string&, std::error_code&) {} - - -void Context::ssl_use_private_key_file(const std::string&, std::error_code&) {} - - -void Context::ssl_use_default_verify(std::error_code&) {} - - -void Context::ssl_use_verify_file(const std::string&, std::error_code&) {} - - -void Stream::ssl_set_verify_mode(VerifyMode, std::error_code&) {} - - -void Stream::ssl_set_host_name(const std::string&, std::error_code&) {} - - -void Stream::ssl_use_verify_callback(const std::function&, std::error_code&) {} - - -void Stream::ssl_handshake(std::error_code&, Want&) noexcept {} - - -std::size_t Stream::ssl_read(char*, std::size_t, std::error_code&, Want&) noexcept -{ - return 0; -} - - -std::size_t Stream::ssl_write(const char*, std::size_t, std::error_code&, Want&) noexcept -{ - return 0; -} - - -bool Stream::ssl_shutdown(std::error_code&, Want&) noexcept -{ - return false; -} - -#endif // ! REALM_HAVE_OPENSSL - - -} // namespace ssl -} // namespace network -} // namespace sync -} // namespace realm diff --git a/src/realm/sync/network/network_ssl.hpp b/src/realm/sync/network/network_ssl.hpp deleted file mode 100644 index 07b60794764..00000000000 --- a/src/realm/sync/network/network_ssl.hpp +++ /dev/null @@ -1,1409 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#if REALM_HAVE_OPENSSL -#include -#include -#elif REALM_HAVE_SECURE_TRANSPORT -#include -#include -#include - -#define REALM_HAVE_KEYCHAIN_APIS (TARGET_OS_MAC && !TARGET_OS_IPHONE) - -#endif - -// FIXME: Add necessary support for customizing the SSL server and client -// configurations. - -// FIXME: Currently, the synchronous SSL operations (handshake, read, write, -// shutdown) do not automatically retry if the underlying SSL function returns -// with SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. This normally never -// happens, but it can happen according to the man pages, but in the case of -// SSL_write(), only when a renegotiation has to take place. It is likely that -// the solution is to to wrap the SSL calls inside a loop, such that they keep -// retrying until they succeed, however, such a simple scheme will fail if the -// synchronous operations were to be used with an underlying TCP socket in -// nonblocking mode. Currently, the underlying TCP socket is always in blocking -// mode when performing synchronous operations, but that may continue to be the -// case in the future. - - -namespace realm::sync::network::ssl { - -enum class Errors { - tls_handshake_failed = 1, -}; - -class ErrorCategory : public std::error_category { -public: - const char* name() const noexcept override final; - std::string message(int) const override final; - bool equivalent(const std::error_code&, int) const noexcept override final; -}; - -/// The error category associated with \ref Errors. The name of this category is -/// `realm.sync.network.ssl`. -extern ErrorCategory error_category; - -inline std::error_code make_error_code(Errors err) -{ - return std::error_code(int(err), error_category); -} - -inline std::error_condition make_error_condition(Errors err) -{ - return std::error_condition(int(err), error_category); -} - -} // namespace realm::sync::network::ssl - -namespace std { - -template <> -class is_error_condition_enum { -public: - static const bool value = true; -}; - -} // namespace std - -namespace realm::sync::network { - -class OpensslErrorCategory : public std::error_category { -public: - const char* name() const noexcept override final; - std::string message(int) const override final; -}; - -/// The error category associated with error codes produced by the third-party -/// library, OpenSSL. The name of this category is `openssl`. -extern OpensslErrorCategory openssl_error_category; - -class SecureTransportErrorCategory : public std::error_category { -public: - const char* name() const noexcept override final; - std::string message(int) const override final; -}; - -/// The error category associated with error codes produced by Apple's -/// SecureTransport library. The name of this category is `securetransport`. -extern SecureTransportErrorCategory secure_transport_error_category; - -namespace ssl { - -class ProtocolNotSupported; - - -/// `VerifyMode::none` corresponds to OpenSSL's `SSL_VERIFY_NONE`, and -/// `VerifyMode::peer` to `SSL_VERIFY_PEER`. -enum class VerifyMode { none, peer }; - - -class Context { -public: - Context(); - ~Context() noexcept; - - /// File must be in PEM format. Corresponds to OpenSSL's - /// `SSL_CTX_use_certificate_chain_file()`. - void use_certificate_chain_file(const std::string& path); - - /// File must be in PEM format. Corresponds to OpenSSL's - /// `SSL_CTX_use_PrivateKey_file()`. - void use_private_key_file(const std::string& path); - - /// Calling use_default_verify() will make a client use the device - /// default certificates for server verification. For OpenSSL, - /// use_default_verify() corresponds to - /// SSL_CTX_set_default_verify_paths(SSL_CTX*); - /// - /// On Windows this also adds a lookup to the system Trusted Root Certification Authorities list. - void use_default_verify(); - - /// The verify file is a PEM file containing trust certificates that the - /// client will use to verify the server certificate. If use_verify_file() - /// is not called, the default device trust store will be used. - /// use_verify_file() corresponds roughly to OpenSSL's - /// SSL_CTX_load_verify_locations(). - void use_verify_file(const std::string& path); - -#if REALM_INCLUDE_CERTS - /// Load the bundled certificates from noinst/root_certs.hpp - void use_included_certificate_roots(); -#endif - -private: - void ssl_init(); - void ssl_destroy() noexcept; - void ssl_use_certificate_chain_file(const std::string& path, std::error_code&); - void ssl_use_private_key_file(const std::string& path, std::error_code&); - void ssl_use_default_verify(std::error_code&); - void ssl_use_verify_file(const std::string& path, std::error_code&); - void ssl_use_included_certificate_roots(std::error_code&); - -#if REALM_HAVE_OPENSSL - SSL_CTX* m_ssl_ctx = nullptr; - -#elif REALM_HAVE_SECURE_TRANSPORT - -#if REALM_HAVE_KEYCHAIN_APIS - std::error_code open_temporary_keychain_if_needed(); - std::error_code update_identity_if_needed(); - - util::CFPtr m_keychain; - std::string m_keychain_path; - - util::CFPtr m_certificate; - util::CFPtr m_private_key; - util::CFPtr m_identity; - - util::CFPtr m_certificate_chain; - -#else - using SecKeychainRef = std::nullptr_t; - -#endif // REALM_HAVE_KEYCHAIN_APIS - static util::CFPtr load_pem_file(const std::string& path, SecKeychainRef, std::error_code&); - - util::CFPtr m_trust_anchors; - util::CFPtr m_pinned_certificate; - -#endif - - friend class Stream; -}; - - -/// Switching between synchronous and asynchronous operations is allowed, but -/// only in a nonoverlapping fashion. That is, a synchronous operation is not -/// allowed to run concurrently with an asynchronous one on the same -/// stream. Note that an asynchronous operation is considered to be running -/// until its completion handler starts executing. -class Stream { -public: - using port_type = network::Endpoint::port_type; - using SSLVerifyCallback = bool(const std::string& server_address, port_type server_port, const char* pem_data, - size_t pem_size, int preverify_ok, int depth); - - enum HandshakeType { client, server }; - - util::Logger* logger = nullptr; - - Stream(Socket&, Context&, HandshakeType); - ~Stream() noexcept; - - /// \brief set_logger() set a logger for the stream class. If - /// set_logger() is not called, no logging will take place by - /// the Stream class. - void set_logger(util::Logger*); - - /// \brief Set the certificate verification mode for this SSL stream. - /// - /// Corresponds to OpenSSL's `SSL_set_verify()` with null passed as - /// `verify_callback`. - /// - /// Clients should always set it to `VerifyMode::peer`, such that the client - /// verifies the servers certificate. Servers should only set it to - /// `VerifyMode::peer` if they want to request a certificate from the - /// client. When testing with self-signed certificates, it is necessary to - /// set it to `VerifyMode::none` for clients too. - /// - /// It is an error if this function is called after the handshake operation - /// is initiated. - /// - /// The default verify mode is `VerifyMode::none`. - void set_verify_mode(VerifyMode); - - /// \brief Check the certificate against a host_name. - /// - /// On the client side, this enables the Server Name Indication (TLS/SNI) - /// extension if supported by the underlying platform. For OpenSSL, it is - /// enabled starting from version 1.1.1. - /// - /// Additionally, this turns on a host name check as part of certificate - /// verification, if certificate verification is enabled - /// (set_verify_mode()). - /// - /// NOTE: With Secure Transport on macos, host name check will be turned on - /// regardless of whether certificate verification is enabled (see - /// https://github.com/curl/curl/pull/1240#issuecomment-285281512). - void set_host_name(std::string host_name); - - /// get_server_port() and set_server_port() are getter and setter for - /// the server port. They are only used by the verify callback function - /// below. - void set_server_port(port_type server_port); - - /// If use_verify_callback() is called, the SSL certificate chain of - /// the server is presented to callback, one certificate at a time. - /// The SSL connection is accepted if and only if callback returns true - /// for all certificates. - /// The signature of \param callback is - /// - /// bool(const std::string& server_address, - /// port_type server_port, - /// const char* pem_data, - /// size_t pem_size, - /// int preverify_ok, - /// int depth); - // - /// server address and server_port is the address and port of the server - /// that a SSL connection is being established to. - /// pem_data is the certificate of length pem_size in - /// the PEM format. preverify_ok is OpenSSL's preverification of the - /// certificate. preverify_ok is either 0, or 1. If preverify_ok is 1, - /// OpenSSL has accepted the certificate and it will generally be safe - /// to trust that certificate. depth represents the position of the - /// certificate in the certificate chain sent by the server. depth = 0 - /// represents the actual server certificate that should contain the - /// host name(server address) of the server. The highest depth is the - /// root certificate. - /// The callback function will receive the certificates starting from - /// the root certificate and moving down the chain until it reaches the - /// server's own certificate with a host name. The depth of the last - /// certificate is 0. The depth of the first certificate is chain - /// length - 1. - /// - /// The return value of the callback function decides whether the - /// client accepts the certificate. If the return value is false, the - /// processing of the certificate chain is interrupted and the SSL - /// connection is rejected. If the return value is true, the verification - /// process continues. If the callback function returns true for all - /// presented certificates including the depth == 0 certificate, the - /// SSL connection is accepted. - /// - /// A recommended way of using the callback function is to return true - /// if preverify_ok = 1 and depth > 0, - /// always check the host name if depth = 0, - /// and use an independent verification step if preverify_ok = 0. - /// - /// Another possible way of using the callback is to collect all the - /// certificates until depth = 0, and present the entire chain for - /// independent verification. - void use_verify_callback(const std::function& callback); - - /// @{ - /// - /// Read and write operations behave the same way as they do on \ref - /// network::Socket, except that after cancellation of asynchronous - /// operations (`lowest_layer().cancel()`), the stream may be left in a bad - /// state (see below). - /// - /// The handshake operation must complete successfully before any read, - /// write, or shutdown operations are performed. - /// - /// The shutdown operation sends the shutdown alert to the peer, and - /// returns/completes as soon as the alert message has been written to the - /// underlying socket. It is an error if the shutdown operation is initiated - /// while there are read or write operations in progress. No read or write - /// operations are allowed to be initiated after the shutdown operation has - /// been initiated. When the shutdown operation has completed, it is safe to - /// close the underlying socket (`lowest_layer().close()`). - /// - /// If a write operation is executing while, or is initiated after a close - /// notify alert is received from the remote peer, the write operation will - /// fail with error::broken_pipe. - /// - /// Callback functions for async read and write operations must take two - /// arguments, an std::error_code(), and an integer of a type std::size_t - /// indicating the number of transferred bytes (other types are allowed as - /// long as implicit conversion can take place). - /// - /// Callback functions for async handshake and shutdown operations must take - /// a single argument of type std::error_code() (other types are allowed as - /// long as implicit conversion can take place). - /// - /// Resumption of stream operation after cancellation of asynchronous - /// operations is not supported (does not work). Since the shutdown - /// operation involves network communication, that operation is also not - /// allowed after cancellation. The only thing that is allowed, is to - /// destroy the stream object. Other stream objects are not affected. - - void handshake(); - std::error_code handshake(std::error_code&); - - std::size_t read(char* buffer, std::size_t size); - std::size_t read(char* buffer, std::size_t size, std::error_code& ec); - std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&); - std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&, std::error_code& ec); - std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&); - std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&, std::error_code& ec); - - std::size_t write(const char* data, std::size_t size); - std::size_t write(const char* data, std::size_t size, std::error_code& ec); - - std::size_t read_some(char* buffer, std::size_t size); - std::size_t read_some(char* buffer, std::size_t size, std::error_code&); - - std::size_t write_some(const char* data, std::size_t size); - std::size_t write_some(const char* data, std::size_t size, std::error_code&); - - void shutdown(); - std::error_code shutdown(std::error_code&); - - template - void async_handshake(H handler); - - template - void async_read(char* buffer, std::size_t size, H handler); - template - void async_read(char* buffer, std::size_t size, ReadAheadBuffer&, H handler); - template - void async_read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&, H handler); - - template - void async_write(const char* data, std::size_t size, H handler); - - template - void async_read_some(char* buffer, std::size_t size, H handler); - - template - void async_write_some(const char* data, std::size_t size, H handler); - - template - void async_shutdown(H handler); - - /// @} - - /// Returns a reference to the underlying socket. - Socket& lowest_layer() noexcept; - -private: - using Want = Service::Want; - using StreamOps = Service::BasicStreamOps; - - class HandshakeOperBase; - template - class HandshakeOper; - class ShutdownOperBase; - template - class ShutdownOper; - - using LendersHandshakeOperPtr = std::unique_ptr; - using LendersShutdownOperPtr = std::unique_ptr; - - Socket& m_tcp_socket; - Context& m_ssl_context; - const HandshakeType m_handshake_type; - - // The host name that the certificate should be checked against. - // The host name is called server address in the certificate verify - // callback function. - std::string m_host_name; - - // The port of the server which is used in the certificate verify - // callback function. - port_type m_server_port; - - // The callback for certificate verification and an - // opaque argument that will be supplied to the callback. - const std::function* m_ssl_verify_callback = nullptr; - - bool m_valid_certificate_in_chain = false; - - - // See Service::BasicStreamOps for details on these these 6 functions. - void do_init_read_async(std::error_code&, Want&) noexcept; - void do_init_write_async(std::error_code&, Want&) noexcept; - std::size_t do_read_some_sync(char* buffer, std::size_t size, std::error_code&) noexcept; - std::size_t do_write_some_sync(const char* data, std::size_t size, std::error_code&) noexcept; - std::size_t do_read_some_async(char* buffer, std::size_t size, std::error_code&, Want&) noexcept; - std::size_t do_write_some_async(const char* data, std::size_t size, std::error_code&, Want&) noexcept; - - // The meaning of the arguments and return values of ssl_read() and - // ssl_write() are identical to do_read_some_async() and - // do_write_some_async() respectively, except that when the return value is - // nonzero, `want` is always `Want::nothing`, meaning that after bytes have - // been transferred, ssl_read() and ssl_write() must be called again to - // figure out whether it is necessary to wait for read or write readiness. - // - // The first invocation of ssl_shutdown() must send the shutdown alert to - // the peer. In blocking mode it must wait until the alert has been sent. In - // nonblocking mode, it must keep setting `want` to something other than - // `Want::nothing` until the alert has been sent. When the shutdown alert - // has been sent, it is safe to shut down the sending side of the underlying - // socket. On failure, ssl_shutdown() must set `ec` to something different - // than `std::error_code()` and return false. On success, it must set `ec` - // to `std::error_code()`, and return true if a shutdown alert from the peer - // has already been received, otherwise it must return false. When it sets - // `want` to something other than `Want::nothing`, it must set `ec` to - // `std::error_code()` and return false. - // - // The second invocation of ssl_shutdown() (after the first invocation - // completed) must wait for reception on the peers shutdown alert. - // - // Note: The semantics around the second invocation of shutdown is currently - // unused by the higher level API, because of a requirement of compatibility - // with Apple's Secure Transport API. - void ssl_init(); - void ssl_destroy() noexcept; - void ssl_set_verify_mode(VerifyMode, std::error_code&); - void ssl_set_host_name(const std::string&, std::error_code&); - void ssl_use_verify_callback(const std::function&, std::error_code&); - - void ssl_handshake(std::error_code&, Want& want) noexcept; - bool ssl_shutdown(std::error_code& ec, Want& want) noexcept; - std::size_t ssl_read(char* buffer, std::size_t size, std::error_code&, Want& want) noexcept; - std::size_t ssl_write(const char* data, std::size_t size, std::error_code&, Want& want) noexcept; - -#if REALM_HAVE_OPENSSL - class BioMethod; - static BioMethod s_bio_method; - SSL* m_ssl = nullptr; - std::error_code m_bio_error_code; - - int m_ssl_index = -1; - - template - std::size_t ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept; - - int do_ssl_accept() noexcept; - int do_ssl_connect() noexcept; - int do_ssl_shutdown() noexcept; - int do_ssl_read(char* buffer, std::size_t size) noexcept; - int do_ssl_write(const char* data, std::size_t size) noexcept; - - static int bio_write(BIO*, const char*, int) noexcept; - static int bio_read(BIO*, char*, int) noexcept; - static int bio_puts(BIO*, const char*) noexcept; - static long bio_ctrl(BIO*, int, long, void*) noexcept; - static int bio_create(BIO*) noexcept; - static int bio_destroy(BIO*) noexcept; - - // verify_callback_using_hostname is used as an argument to OpenSSL's SSL_set_verify function. - // verify_callback_using_hostname verifies that the certificate is valid and contains - // m_host_name as a Common Name or Subject Alternative Name. - static int verify_callback_using_hostname(int preverify_ok, X509_STORE_CTX* ctx) noexcept; - - // verify_callback_using_delegate() is also used as an argument to OpenSSL's set_verify_function. - // verify_callback_using_delegate() calls out to the user supplied verify callback. - static int verify_callback_using_delegate(int preverify_ok, X509_STORE_CTX* ctx) noexcept; -#elif REALM_HAVE_SECURE_TRANSPORT - util::CFPtr m_ssl; - VerifyMode m_verify_mode = VerifyMode::none; - - enum class BlockingOperation { - read, - write, - }; - util::Optional m_last_operation; - - // Details of the underlying I/O error that lead to errSecIO being returned - // from a SecureTransport function. - std::error_code m_last_error; - - // The number of bytes accepted by SSWrite() but not yet confirmed to be - // written to the underlying socket. - std::size_t m_num_partially_written_bytes = 0; - - template - std::size_t ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept; - - std::pair do_ssl_handshake() noexcept; - std::pair do_ssl_shutdown() noexcept; - std::pair do_ssl_read(char* buffer, std::size_t size) noexcept; - std::pair do_ssl_write(const char* data, std::size_t size) noexcept; - - static OSStatus tcp_read(SSLConnectionRef, void*, std::size_t* length) noexcept; - static OSStatus tcp_write(SSLConnectionRef, const void*, std::size_t* length) noexcept; - - OSStatus tcp_read(void*, std::size_t* length) noexcept; - OSStatus tcp_write(const void*, std::size_t* length) noexcept; - - OSStatus verify_peer() noexcept; -#endif - - friend class Service::BasicStreamOps; - friend class network::ReadAheadBuffer; -#if REALM_HAVE_SECURE_TRANSPORT - friend struct MockSSLError; // for access to Service::Want -#endif - -#if REALM_HAVE_SECURE_TRANSPORT -public: - // Structure for mocking the error returned by Oper called by ssl_perform() - // By default, this is a one-shot error that will be cleared after it is read, - // unless clear_after_access is set to false. - struct MockSSLError { - using Operation = Stream::BlockingOperation; - - explicit MockSSLError(Operation op, int ssl_error, int bytes_processed, bool clear_after_access = true) - : operation{op} - , ssl_error{ssl_error} - , sys_error{0} - , bytes_processed{bytes_processed} - , clear_after_access{clear_after_access} - { - } - - explicit MockSSLError(Operation op, int ssl_error, int sys_error, int bytes_processed, - bool clear_after_access = true) - : operation{op} - , ssl_error{ssl_error} - , sys_error{sys_error} - , bytes_processed{bytes_processed} - , clear_after_access{clear_after_access} - { - } - - Operation operation; - int ssl_error; - int sys_error; - int bytes_processed; - bool clear_after_access; - }; - - /// Mock the error value returned by ssl_perform() - currently only used by Apple Secure Transport - void set_mock_ssl_perform_error(std::unique_ptr&& error = nullptr); /// - -private: - std::unique_ptr m_mock_ssl_perform_error; -#endif -}; - - -// Implementation - -class ProtocolNotSupported : public std::exception { -public: - const char* what() const noexcept override final; -}; - -inline Context::Context() -{ - ssl_init(); // Throws -} - -inline Context::~Context() noexcept -{ - ssl_destroy(); -} - -inline void Context::use_certificate_chain_file(const std::string& path) -{ - std::error_code ec; - ssl_use_certificate_chain_file(path, ec); // Throws - if (ec) - throw std::system_error(ec); -} - -inline void Context::use_private_key_file(const std::string& path) -{ - std::error_code ec; - ssl_use_private_key_file(path, ec); // Throws - if (ec) - throw std::system_error(ec); -} - -inline void Context::use_default_verify() -{ - std::error_code ec; - ssl_use_default_verify(ec); - if (ec) - throw std::system_error(ec); -} - -inline void Context::use_verify_file(const std::string& path) -{ - std::error_code ec; - ssl_use_verify_file(path, ec); - if (ec) { - throw std::system_error(ec); - } -} - -#if REALM_INCLUDE_CERTS -inline void Context::use_included_certificate_roots() -{ - std::error_code ec; - ssl_use_included_certificate_roots(ec); - if (ec) { - throw std::system_error(ec); - } -} -#endif - -class Stream::HandshakeOperBase : public Service::IoOper { -public: - HandshakeOperBase(std::size_t size, Stream& stream) - : IoOper{size} - , m_stream{&stream} - { - } - Want initiate() - { - REALM_ASSERT(this == m_stream->m_tcp_socket.m_read_oper.get()); - REALM_ASSERT(!is_complete()); - m_stream->m_tcp_socket.m_desc.ensure_nonblocking_mode(); // Throws - return advance(); - } - Want advance() noexcept override final - { - REALM_ASSERT(!is_complete()); - REALM_ASSERT(!is_canceled()); - REALM_ASSERT(!m_error_code); - Want want = Want::nothing; - m_stream->ssl_handshake(m_error_code, want); - set_is_complete(want == Want::nothing); - return want; - } - void recycle() noexcept override final - { - bool orphaned = !m_stream; - REALM_ASSERT(orphaned); - // Note: do_recycle() commits suicide. - do_recycle(orphaned); - } - void orphan() noexcept override final - { - m_stream = nullptr; - } - Service::Descriptor& descriptor() noexcept override final - { - return m_stream->lowest_layer().m_desc; - } - -protected: - Stream* m_stream; - std::error_code m_error_code; -}; - -template -class Stream::HandshakeOper : public HandshakeOperBase { -public: - HandshakeOper(std::size_t size, Stream& stream, H handler) - : HandshakeOperBase{size, stream} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - REALM_ASSERT(is_complete() || is_canceled()); - bool orphaned = !m_stream; - std::error_code ec = m_error_code; - if (is_canceled()) - ec = util::error::operation_aborted; - // Note: do_recycle_and_execute() commits suicide. - do_recycle_and_execute(orphaned, m_handler, ec); // Throws - } - -private: - H m_handler; -}; - -class Stream::ShutdownOperBase : public Service::IoOper { -public: - ShutdownOperBase(std::size_t size, Stream& stream) - : IoOper{size} - , m_stream{&stream} - { - } - Want initiate() - { - REALM_ASSERT(this == m_stream->m_tcp_socket.m_write_oper.get()); - REALM_ASSERT(!is_complete()); - m_stream->m_tcp_socket.m_desc.ensure_nonblocking_mode(); // Throws - return advance(); - } - Want advance() noexcept override final - { - REALM_ASSERT(!is_complete()); - REALM_ASSERT(!is_canceled()); - REALM_ASSERT(!m_error_code); - Want want = Want::nothing; - m_stream->ssl_shutdown(m_error_code, want); - if (want == Want::nothing) - set_is_complete(true); - return want; - } - void recycle() noexcept override final - { - bool orphaned = !m_stream; - REALM_ASSERT(orphaned); - // Note: do_recycle() commits suicide. - do_recycle(orphaned); - } - void orphan() noexcept override final - { - m_stream = nullptr; - } - Service::Descriptor& descriptor() noexcept override final - { - return m_stream->lowest_layer().m_desc; - } - -protected: - Stream* m_stream; - std::error_code m_error_code; -}; - -template -class Stream::ShutdownOper : public ShutdownOperBase { -public: - ShutdownOper(std::size_t size, Stream& stream, H handler) - : ShutdownOperBase{size, stream} - , m_handler{std::move(handler)} - { - } - void recycle_and_execute() override final - { - REALM_ASSERT(is_complete() || is_canceled()); - bool orphaned = !m_stream; - std::error_code ec = m_error_code; - if (is_canceled()) - ec = util::error::operation_aborted; - // Note: do_recycle_and_execute() commits suicide. - do_recycle_and_execute(orphaned, m_handler, ec); // Throws - } - -private: - H m_handler; -}; - -inline Stream::Stream(Socket& socket, Context& context, HandshakeType type) - : m_tcp_socket{socket} - , m_ssl_context{context} - , m_handshake_type{type} -{ - ssl_init(); // Throws -} - -inline Stream::~Stream() noexcept -{ - m_tcp_socket.cancel(); - ssl_destroy(); -} - -inline void Stream::set_logger(util::Logger* logger_ptr) -{ - logger = logger_ptr; -} - -inline void Stream::set_verify_mode(VerifyMode mode) -{ - std::error_code ec; - ssl_set_verify_mode(mode, ec); // Throws - if (ec) - throw std::system_error(ec); -} - -inline void Stream::set_host_name(std::string host_name) -{ - m_host_name = std::move(host_name); - std::error_code ec; - ssl_set_host_name(m_host_name, ec); - if (ec) - throw std::system_error(ec); -} - -inline void Stream::set_server_port(port_type server_port) -{ - m_server_port = server_port; -} - -inline void Stream::use_verify_callback(const std::function& callback) -{ - std::error_code ec; - ssl_use_verify_callback(callback, ec); // Throws - if (ec) - throw std::system_error(ec); -} - -inline void Stream::handshake() -{ - std::error_code ec; - if (handshake(ec)) // Throws - throw std::system_error(ec); -} - -inline std::size_t Stream::read(char* buffer, std::size_t size) -{ - std::error_code ec; - read(buffer, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return size; -} - -inline std::size_t Stream::read(char* buffer, std::size_t size, std::error_code& ec) -{ - return StreamOps::read(*this, buffer, size, ec); // Throws -} - -inline std::size_t Stream::read(char* buffer, std::size_t size, ReadAheadBuffer& rab) -{ - std::error_code ec; - read(buffer, size, rab, ec); // Throws - if (ec) - throw std::system_error(ec); - return size; -} - -inline std::size_t Stream::read(char* buffer, std::size_t size, ReadAheadBuffer& rab, std::error_code& ec) -{ - int delim = std::char_traits::eof(); - return StreamOps::buffered_read(*this, buffer, size, delim, rab, ec); // Throws -} - -inline std::size_t Stream::read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer& rab) -{ - std::error_code ec; - std::size_t n = read_until(buffer, size, delim, rab, ec); // Throws - if (ec) - throw std::system_error(ec); - return n; -} - -inline std::size_t Stream::read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer& rab, - std::error_code& ec) -{ - int delim_2 = std::char_traits::to_int_type(delim); - return StreamOps::buffered_read(*this, buffer, size, delim_2, rab, ec); // Throws -} - -inline std::size_t Stream::write(const char* data, std::size_t size) -{ - std::error_code ec; - write(data, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return size; -} - -inline std::size_t Stream::write(const char* data, std::size_t size, std::error_code& ec) -{ - return StreamOps::write(*this, data, size, ec); // Throws -} - -inline std::size_t Stream::read_some(char* buffer, std::size_t size) -{ - std::error_code ec; - std::size_t n = read_some(buffer, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return n; -} - -inline std::size_t Stream::read_some(char* buffer, std::size_t size, std::error_code& ec) -{ - return StreamOps::read_some(*this, buffer, size, ec); // Throws -} - -inline std::size_t Stream::write_some(const char* data, std::size_t size) -{ - std::error_code ec; - std::size_t n = write_some(data, size, ec); // Throws - if (ec) - throw std::system_error(ec); - return n; -} - -inline std::size_t Stream::write_some(const char* data, std::size_t size, std::error_code& ec) -{ - return StreamOps::write_some(*this, data, size, ec); // Throws -} - -inline void Stream::shutdown() -{ - std::error_code ec; - if (shutdown(ec)) // Throws - throw std::system_error(ec); -} - -template -inline void Stream::async_handshake(H handler) -{ - LendersHandshakeOperPtr op = Service::alloc>(m_tcp_socket.m_read_oper, *this, - std::move(handler)); // Throws - m_tcp_socket.m_desc.initiate_oper(std::move(op)); // Throws -} - -template -inline void Stream::async_read(char* buffer, std::size_t size, H handler) -{ - bool is_read_some = false; - StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws -} - -template -inline void Stream::async_read(char* buffer, std::size_t size, ReadAheadBuffer& rab, H handler) -{ - int delim = std::char_traits::eof(); - StreamOps::async_buffered_read(*this, buffer, size, delim, rab, std::move(handler)); // Throws -} - -template -inline void Stream::async_read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer& rab, H handler) -{ - int delim_2 = std::char_traits::to_int_type(delim); - StreamOps::async_buffered_read(*this, buffer, size, delim_2, rab, std::move(handler)); // Throws -} - -template -inline void Stream::async_write(const char* data, std::size_t size, H handler) -{ - bool is_write_some = false; - StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws -} - -template -inline void Stream::async_read_some(char* buffer, std::size_t size, H handler) -{ - bool is_read_some = true; - StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws -} - -template -inline void Stream::async_write_some(const char* data, std::size_t size, H handler) -{ - bool is_write_some = true; - StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws -} - -template -inline void Stream::async_shutdown(H handler) -{ - LendersShutdownOperPtr op = Service::alloc>(m_tcp_socket.m_write_oper, *this, - std::move(handler)); // Throws - m_tcp_socket.m_desc.initiate_oper(std::move(op)); // Throws -} - -inline void Stream::do_init_read_async(std::error_code&, Want& want) noexcept -{ - want = Want::nothing; // Proceed immediately unless there is an error -} - -inline void Stream::do_init_write_async(std::error_code&, Want& want) noexcept -{ - want = Want::nothing; // Proceed immediately unless there is an error -} - -inline std::size_t Stream::do_read_some_sync(char* buffer, std::size_t size, std::error_code& ec) noexcept -{ - Want want = Want::nothing; - std::size_t n = do_read_some_async(buffer, size, ec, want); - if (n == 0 && want != Want::nothing) - ec = util::error::resource_unavailable_try_again; - return n; -} - -inline std::size_t Stream::do_write_some_sync(const char* data, std::size_t size, std::error_code& ec) noexcept -{ - Want want = Want::nothing; - std::size_t n = do_write_some_async(data, size, ec, want); - if (n == 0 && want != Want::nothing) - ec = util::error::resource_unavailable_try_again; - return n; -} - -inline std::size_t Stream::do_read_some_async(char* buffer, std::size_t size, std::error_code& ec, - Want& want) noexcept -{ - return ssl_read(buffer, size, ec, want); -} - -inline std::size_t Stream::do_write_some_async(const char* data, std::size_t size, std::error_code& ec, - Want& want) noexcept -{ - return ssl_write(data, size, ec, want); -} - -inline Socket& Stream::lowest_layer() noexcept -{ - return m_tcp_socket; -} - -#if REALM_HAVE_OPENSSL - -inline void Stream::ssl_handshake(std::error_code& ec, Want& want) noexcept -{ - auto perform = [this]() noexcept { - switch (m_handshake_type) { - case client: - return do_ssl_connect(); - case server: - return do_ssl_accept(); - } - REALM_ASSERT(false); - return 0; - }; - std::size_t n = ssl_perform(std::move(perform), ec, want); - REALM_ASSERT(n == 0 || n == 1); - if (want == Want::nothing && n == 0 && !ec) { - // End of input on TCP socket - ec = util::MiscExtErrors::premature_end_of_input; - } -} - -inline std::size_t Stream::ssl_read(char* buffer, std::size_t size, std::error_code& ec, Want& want) noexcept -{ - auto perform = [this, buffer, size]() noexcept { - return do_ssl_read(buffer, size); - }; - std::size_t n = ssl_perform(std::move(perform), ec, want); - if (want == Want::nothing && n == 0 && !ec) { - // End of input on TCP socket - if (SSL_get_shutdown(m_ssl) & SSL_RECEIVED_SHUTDOWN) { - ec = util::MiscExtErrors::end_of_input; - } - else { - ec = util::MiscExtErrors::premature_end_of_input; - } - } - return n; -} - -inline std::size_t Stream::ssl_write(const char* data, std::size_t size, std::error_code& ec, Want& want) noexcept -{ - // While OpenSSL is able to continue writing after we have received the - // close notify alert fro the remote peer, Apple's Secure Transport API is - // not, so to achieve common behaviour, we make sure that any such attempt - // will result in an `error::broken_pipe` error. - if ((SSL_get_shutdown(m_ssl) & SSL_RECEIVED_SHUTDOWN) != 0) { - ec = util::error::broken_pipe; - want = Want::nothing; - return 0; - } - auto perform = [this, data, size]() noexcept { - return do_ssl_write(data, size); - }; - std::size_t n = ssl_perform(std::move(perform), ec, want); - if (want == Want::nothing && n == 0 && !ec) { - // End of input on TCP socket - ec = util::MiscExtErrors::premature_end_of_input; - } - return n; -} - -inline bool Stream::ssl_shutdown(std::error_code& ec, Want& want) noexcept -{ - auto perform = [this]() noexcept { - return do_ssl_shutdown(); - }; - std::size_t n = ssl_perform(std::move(perform), ec, want); - REALM_ASSERT(n == 0 || n == 1); - if (want == Want::nothing && n == 0 && !ec) { - // The first invocation of SSL_shutdown() does not signal completion - // until the shutdown alert has been sent to the peer, or an error - // occurred (does not wait for acknowledgment). - // - // The second invocation (after a completed first invocation) does not - // signal completion until the peers shutdown alert has been received, - // or an error occurred. - // - // It is believed that: - // - // If this is the first time SSL_shutdown() is called, and - // `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` evaluates to nonzero, then a - // zero return value means "partial success" (shutdown alert was sent, - // but the peers shutdown alert was not yet received), and 1 means "full - // success" (peers shutdown alert has already been received). - // - // If this is the first time SSL_shutdown() is called, and - // `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` valuates to zero, then a - // zero return value means "premature end of input", and 1 is supposedly - // not a possibility. - // - // If this is the second time SSL_shutdown() is called (after the first - // call has returned zero), then a zero return value means "premature - // end of input", and 1 means "full success" (peers shutdown alert has - // now been received). - if ((SSL_get_shutdown(m_ssl) & SSL_SENT_SHUTDOWN) == 0) - ec = util::MiscExtErrors::premature_end_of_input; - } - return (n > 0); -} - -// Provides a homogeneous, and mostly quirks-free interface across the OpenSSL -// operations (handshake, read, write, shutdown). -// -// First of all, if the operation remains incomplete (neither successfully -// completed, nor failed), ssl_perform() will set `ec` to `std::system_error()`, -// `want` to something other than `Want::nothing`, and return zero. Note that -// read and write operations are partial in the sense that they do not need to -// read or write everything before completing successfully. They only need to -// read or write at least one byte to complete successfully. -// -// Such a situation will normally only happen when the underlying TCP socket is -// in nonblocking mode, and the read/write requirements of the operation could -// not be immediately accommodated. However, as is noted in the SSL_write() man -// page, it can also happen in blocking mode (at least while writing). -// -// If an error occurred, ssl_perform() will set `ec` to something other than -// `std::system_error()`, `want` to `Want::nothing`, and return 0. -// -// If no error occurred, and the operation completed (`!ec && want == -// Want::nothing`), then the return value indicates the outcome of the -// operation. -// -// In general, a nonzero value means "full" success, and a zero value means -// "partial" success, however, a zero result can also generally mean "premature -// end of input" / "unclean protocol termination". -// -// Assuming there is no premature end of input, then for reads and writes, the -// returned value is the number of transferred bytes. Zero for read on end of -// input. Never zero for write. For handshake it is always 1. For shutdown it is -// 1 if the peer shutdown alert was already received, otherwise it is zero. -// -// ssl_read() should use `SSL_get_shutdown() & SSL_RECEIVED_SHUTDOWN` to -// distinguish between the two possible meanings of zero. -// -// ssl_shutdown() should use `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` to -// distinguish between the two possible meanings of zero. -template -std::size_t Stream::ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept -{ - ERR_clear_error(); - m_bio_error_code = std::error_code(); // Success - int ret = oper(); - int ssl_error = SSL_get_error(m_ssl, ret); - int sys_error = int(ERR_peek_last_error()); - - // Guaranteed by the documentation of SSL_get_error() - REALM_ASSERT((ret > 0) == (ssl_error == SSL_ERROR_NONE)); - - REALM_ASSERT(!m_bio_error_code || ssl_error == SSL_ERROR_SYSCALL); - - // Judging from various comments in the man pages, and from experience with - // the API, it seems that, - // - // ret=0, ssl_error=SSL_ERROR_SYSCALL, sys_error=0 - // - // is supposed to be an indicator of "premature end of input" / "unclean - // protocol termination", while - // - // ret=0, ssl_error=SSL_ERROR_ZERO_RETURN - // - // is supposed to be an indicator of the following success conditions: - // - // - Mature end of input / clean protocol termination. - // - // - Successful transmission of the shutdown alert, but no prior reception - // of shutdown alert from peer. - // - // Unfortunately, as is also remarked in various places in the man pages, - // those two success conditions may actually result in `ret=0, - // ssl_error=SSL_ERROR_SYSCALL, sys_error=0` too, and it seems that they - // almost always do. - // - // This means that we cannot properly discriminate between these conditions - // in ssl_perform(), and will have to defer to the caller to interpret the - // situation. Since thay cannot be properly told apart, we report all - // `ret=0, ssl_error=SSL_ERROR_SYSCALL, sys_error=0` and `ret=0, - // ssl_error=SSL_ERROR_ZERO_RETURN` cases as the latter. - switch (ssl_error) { - case SSL_ERROR_NONE: - ec = std::error_code(); // Success - want = Want::nothing; - return std::size_t(ret); // ret > 0 - case SSL_ERROR_ZERO_RETURN: - ec = std::error_code(); // Success - want = Want::nothing; - return 0; - case SSL_ERROR_WANT_READ: - ec = std::error_code(); // Success - want = Want::read; - return 0; - case SSL_ERROR_WANT_WRITE: - ec = std::error_code(); // Success - want = Want::write; - return 0; - case SSL_ERROR_SYSCALL: - if (REALM_UNLIKELY(sys_error != 0)) { - ec = util::make_basic_system_error_code(sys_error); - } - else if (REALM_UNLIKELY(m_bio_error_code)) { - ec = m_bio_error_code; - } - else if (ret == 0) { - // ret = 0, ssl_eror = SSL_ERROR_SYSCALL, sys_error = 0 - // - // See remarks above! - ec = std::error_code(); // Success - } - else { - // ret = -1, ssl_eror = SSL_ERROR_SYSCALL, sys_error = 0 - // - // This situation arises in OpenSSL version >= 1.1. - // It has been observed in the SSL_connect call if the - // other endpoint terminates the connection during - // SSL_connect. The OpenSSL documentation states - // that ret = -1 implies an underlying BIO error and - // that errno should be consulted. However, - // errno = 0(Undefined error) in the observed case. - // At the moment. we will report - // MiscExtErrors::premature_end_of_input. - // If we see this error case occurring in other situations in - // the future, we will have to update this case. - ec = util::MiscExtErrors::premature_end_of_input; - } - want = Want::nothing; - return 0; - case SSL_ERROR_SSL: - ec = std::error_code(sys_error, openssl_error_category); - want = Want::nothing; - return 0; - default: - break; - } - // We are not supposed to ever get here - REALM_ASSERT(false); - return 0; -} - -inline int Stream::do_ssl_accept() noexcept -{ - int ret = SSL_accept(m_ssl); - return ret; -} - -inline int Stream::do_ssl_connect() noexcept -{ - int ret = SSL_connect(m_ssl); - return ret; -} - -inline int Stream::do_ssl_read(char* buffer, std::size_t size) noexcept -{ - int size_2 = int(size); - if (size > unsigned(std::numeric_limits::max())) - size_2 = std::size_t(std::numeric_limits::max()); - int ret = SSL_read(m_ssl, buffer, size_2); - return ret; -} - -inline int Stream::do_ssl_write(const char* data, std::size_t size) noexcept -{ - int size_2 = int(size); - if (size > unsigned(std::numeric_limits::max())) - size_2 = std::size_t(std::numeric_limits::max()); - int ret = SSL_write(m_ssl, data, size_2); - return ret; -} - -inline int Stream::do_ssl_shutdown() noexcept -{ - int ret = SSL_shutdown(m_ssl); - return ret; -} - -#elif REALM_HAVE_SECURE_TRANSPORT - -inline void Stream::set_mock_ssl_perform_error(std::unique_ptr&& error) -{ - m_mock_ssl_perform_error = std::move(error); -} - -// Provides a homogeneous, and mostly quirks-free interface across the SecureTransport -// operations (handshake, read, write, shutdown). -// -// First of all, if the operation remains incomplete (neither successfully -// completed, nor failed), ssl_perform() will set `ec` to `std::system_error()`, -// `want` to something other than `Want::nothing`, and return zero. -// -// If an error occurred, ssl_perform() will set `ec` to something other than -// `std::system_error()`, `want` to `Want::nothing`, and return 0. If the error -// is end_of_input, it is possible that the value returned is non-zero. -// -// If no error occurred, and the operation completed (`!ec && want == -// Want::nothing`), then the return value indicates the outcome of the -// operation. -// -// In general, a nonzero value means "full" success, and a zero value means -// "partial" success, however, a zero result can also generally mean "premature -// end of input" / "unclean protocol termination". -// -// Assuming there is no premature end of input, then for reads and writes, the -// returned value is the number of transferred bytes. Zero for read on end of -// input. Never zero for write. For handshake it is always 1. For shutdown it is -// 1 if the peer shutdown alert was already received, otherwise it is zero. -template -std::size_t Stream::ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept -{ - OSStatus result; - std::size_t n; - - // Use caution with MockSSLError, since errSSLWouldBlock will potentially perform - // another read that may block - if (REALM_UNLIKELY(m_mock_ssl_perform_error)) { - result = static_cast(m_mock_ssl_perform_error->ssl_error); - n = static_cast(m_mock_ssl_perform_error->bytes_processed); - if (m_mock_ssl_perform_error->clear_after_access) - m_mock_ssl_perform_error.reset(); - } - else { - // Call the operation if there is no mock error set - std::tie(result, n) = oper(); - } - - Want blocking_want = [this]() { - if (!m_last_operation) - return Want::nothing; - switch (*m_last_operation) { - case BlockingOperation::read: - return Want::read; - case BlockingOperation::write: - return Want::write; - }; - }(); - m_last_operation.reset(); - - if (result == noErr) { - ec = std::error_code(); - want = Want::nothing; - return n; - } - - // Don't return an error, but keep reading if more data is needed - if (result == errSSLWouldBlock) { - REALM_ASSERT(blocking_want != Want::nothing); - ec = std::error_code(); - want = blocking_want; - return n; - } - - if (result == errSSLClosedGraceful) { - ec = util::MiscExtErrors::end_of_input; - want = Want::nothing; - return n; - } - - // Always return 0 if an error (other than end_of_input) occurs - if (result == errSSLClosedAbort || result == errSSLClosedNoNotify) { - ec = util::MiscExtErrors::premature_end_of_input; - want = Want::nothing; - return 0; - } - - if (result == errSecIO) { - // A generic I/O error means something went wrong at a lower level. Use the error - // code we smuggled out of our lower-level functions to provide a more specific error. - REALM_ASSERT(m_last_error); - ec = m_last_error; - want = Want::nothing; - return 0; - } - - ec = std::error_code(result, secure_transport_error_category); - want = Want::nothing; - return 0; -} -#endif // REALM_HAVE_OPENSSL / REALM_HAVE_SECURE_TRANSPORT - -} // namespace ssl -} // namespace realm::sync::network diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp deleted file mode 100644 index 4a2668b0708..00000000000 --- a/src/realm/sync/network/websocket.cpp +++ /dev/null @@ -1,1335 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -using namespace realm; -using namespace realm::sync; -using HttpError = websocket::HttpError; -using WebSocketError = websocket::WebSocketError; - - -namespace { - -// case_insensitive_equal is used to compare, ignoring case, certain HTTP -// header values with their expected values. -bool case_insensitive_equal(StringData str1, StringData str2) -{ - if (str1.size() != str2.size()) - return false; - - for (size_t i = 0; i < str1.size(); ++i) - if (std::tolower(str1[i], std::locale::classic()) != std::tolower(str2[i], std::locale::classic())) - return false; - - return true; -} - -// The WebSocket version is always 13 according to the standard. -const StringData sec_websocket_version = "13"; - -// The magic string is specified in the WebSocket protocol. It is used in the handshake. -const StringData websocket_magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - -// The Sec-WebSocket-Key is a header in the client HTTP request. -// It is the base64 encoding of 16 random bytes. -std::string make_random_sec_websocket_key(std::mt19937_64& random) -{ - char random_bytes[16]; - std::uniform_int_distribution<> dis(std::numeric_limits::min(), std::numeric_limits::max()); - for (int i = 0; i < 16; ++i) { - random_bytes[i] = dis(random); - } - - char out_buffer[24]; - size_t encoded_size = util::base64_encode(random_bytes, out_buffer); - REALM_ASSERT(encoded_size == 24); - - return std::string{out_buffer, 24}; -} - -// Sec-WebSocket-Accept is a header in the server's HTTP response. -// It is calculated from the Sec-WebSocket-Key in the client's HHTP request -// as the base64 encoding of the sha1 of the concatenation of the -// Sec-Websocket-Key and the magic string. -std::string make_sec_websocket_accept(StringData sec_websocket_key) -{ - std::string sha1_input; - sha1_input.reserve(sec_websocket_key.size() + websocket_magic_string.size()); - sha1_input.append(sec_websocket_key.data(), sec_websocket_key.size()); - sha1_input.append(websocket_magic_string.data(), websocket_magic_string.size()); - - char sha1_output[20]; - util::sha1(sha1_input.data(), sha1_input.length(), reinterpret_cast(sha1_output)); - - char base64_output[28]; - size_t base64_output_size = util::base64_encode(sha1_output, base64_output); - REALM_ASSERT(base64_output_size == 28); - - return std::string(base64_output, 28); -} - -// find_http_header_value() returns the value of the header \param header in the -// HTTP message \param message if present. If the header is absent, the function returns -// None. -util::Optional find_http_header_value(const HTTPHeaders& headers, StringData header) -{ - auto it = headers.find(header); - - if (it != headers.end()) { - return StringData(it->second); - } - return none; -} - -// validate_websocket_upgrade() returns true if the HTTP \a headers -// contain the line -// Upgrade: websocket -bool validate_websocket_upgrade(const HTTPHeaders& headers) -{ - util::Optional header_value_upgrade = find_http_header_value(headers, "Upgrade"); - - return (header_value_upgrade && case_insensitive_equal(*header_value_upgrade, "websocket")); -} - -// validate_websocket_connection() returns true if the HTTP \a headers -// contains the line: -// Connection: Upgrade -bool validate_websocket_connection(const HTTPHeaders& headers) -{ - util::Optional header_value_connection = find_http_header_value(headers, "Connection"); - - return (header_value_connection && case_insensitive_equal(*header_value_connection, "Upgrade")); -} - -// validate_sec_websocket_version() returns true if the -// \param http_message contains a header with the -// correct sec_websocket_version. -bool validate_sec_websocket_version(const HTTPHeaders& headers) -{ - return find_http_header_value(headers, "Sec-WebSocket-Version") == sec_websocket_version; -} - -// find_sec_websocket_key() returns true if the -// \param headers contains a Sec-Websocket-Key -// header, false otherwise. If the header is found, the -// argument sec_websocket_key is set to its value. -bool find_sec_websocket_key(const HTTPHeaders& headers, std::string& sec_websocket_key) -{ - util::Optional header_value = find_http_header_value(headers, "Sec-WebSocket-Key"); - - if (!header_value) - return false; - - sec_websocket_key = *header_value; - - return true; -} - -util::Optional do_make_http_response(const HTTPRequest& request, - const std::string& sec_websocket_protocol, std::error_code& ec) -{ - std::string sec_websocket_key; - - if (!validate_websocket_upgrade(request.headers)) { - ec = HttpError::bad_request_header_upgrade; - return util::none; - } - - if (!validate_websocket_connection(request.headers)) { - ec = HttpError::bad_request_header_connection; - return util::none; - } - - if (!validate_sec_websocket_version(request.headers)) { - ec = HttpError::bad_request_header_websocket_version; - return util::none; - } - - if (!find_sec_websocket_key(request.headers, sec_websocket_key)) { - ec = HttpError::bad_request_header_websocket_key; - return util::none; - } - - std::string sec_websocket_accept = make_sec_websocket_accept(sec_websocket_key); - - HTTPResponse response; - response.status = HTTPStatus::SwitchingProtocols; - response.headers["Upgrade"] = "websocket"; - response.headers["Connection"] = "Upgrade"; - response.headers["Sec-WebSocket-Accept"] = sec_websocket_accept; - response.headers["Sec-WebSocket-Protocol"] = sec_websocket_protocol; - - return response; -} - -// mask_payload masks (and demasks) the payload sent from the client to the server. -void mask_payload(char* masking_key, const char* payload, size_t payload_len, char* output) -{ - for (size_t i = 0; i < payload_len; ++i) { - output[i] = payload[i] ^ masking_key[i % 4]; - } -} - -// make_frame() creates a WebSocket frame according to the WebSocket standard. -// \param fin indicates whether the frame is the final fragment in a message. -// Sync clients and servers will only send unfragmented messages, but they must be -// prepared to receive fragmented messages. -// \param opcode must be one of six values: -// 0 = continuation frame -// 1 = text frame -// 2 = binary frame -// 8 = ping frame -// 9 = pong frame -// 10 = close frame. -// Sync clients and server will only send the last four, but must be prepared to -// receive all. -// \param mask indicates whether the payload of the frame should be masked. Frames -// are masked if and only if they originate from the client. -// The payload is located in the buffer \param payload, and has size \param payload_size. -// \param output is the output buffer. It must be large enough to contain the frame. -// The frame size can at most be payload_size + 14. -// \param random is used to create a random masking key. -// The return value is the size of the frame. -size_t make_frame(bool fin, int opcode, bool mask, const char* payload, size_t payload_size, char* output, - std::mt19937_64& random) -{ - int index = 0; // used to keep track of position within the header. - using uchar = unsigned char; - output[0] = (fin ? char(uchar(128)) : 0) + opcode; // fin and opcode in the first byte. - output[1] = (mask ? char(uchar(128)) : 0); // First bit of the second byte is mask. - if (payload_size <= 125) { // The payload length is contained in the second byte. - output[1] += static_cast(payload_size); - index = 2; - } - else if (payload_size < 65536) { // The payload length is contained bytes 3-4. - output[1] += 126; - // FIXME: Verify that this code works even if one sync-client is on a platform where - // a 'char' is signed by default and the other client is on a platform where char is - // unsigned. Note that the result of payload_size / 256 may not fit in a signed char. - output[2] = static_cast(payload_size / 256); - - // FIXME: Verify that the modulo arithmetic is well defined - output[3] = payload_size % 256; - index = 4; - } - else { // The payload length is contained in bytes 3-10. - output[1] += 127; - size_t fraction = payload_size; - int remainder = 0; - for (int i = 0; i < 8; ++i) { - remainder = fraction % 256; - fraction /= 256; - output[9 - i] = remainder; - } - index = 10; - } - if (mask) { - char masking_key[4]; - std::uniform_int_distribution<> dis(0, 255); - for (int i = 0; i < 4; ++i) { - masking_key[i] = dis(random); - } - output[index++] = masking_key[0]; - output[index++] = masking_key[1]; - output[index++] = masking_key[2]; - output[index++] = masking_key[3]; - mask_payload(masking_key, payload, payload_size, output + index); - } - else { - std::copy(payload, payload + payload_size, output + index); - } - - return payload_size + index; -} - -// class FrameReader takes care of parsing the incoming bytes and -// constructing the received WebSocket messages. FrameReader manages -// read buffers internally. FrameReader handles fragmented messages as -// well. FrameRader is used in the following way: -// -// After constructing a FrameReader, the user runs in a loop. -// -// Loop start: -// -// Call frame_reader.next(). -// if (frame_reader.protocol_error) { -// // report a protocol error and -// // break out of this loop -// } -// else if (frame_reader.delivery_ready) { -// // use the message -// // in frame_reader.delivery_buffer -// // of size -// // frame_reader.delivery_size -// // with opcode (type) -// // frame_reader.delivery_opcode -// } -// else { -// // read frame_reader.read_size -// // bytes into the buffer -// // frame_reader.read_buffer -// } -// Goto Loop start. -// -class FrameReader { -public: - // FrameReader is owned by a websocket, so a shared_ptr is not needed - util::Logger& logger; - - char* delivery_buffer = nullptr; - size_t delivery_size = 0; - size_t read_size = 0; - char* read_buffer = nullptr; - bool protocol_error = false; - bool delivery_ready = false; - websocket::Opcode delivery_opcode = websocket::Opcode::continuation; - - FrameReader(util::Logger& logger, bool& is_client) - : logger(logger) - , m_is_client(is_client) - { - } - - // reset() resets the frame reader such that it is ready to work on - // a new WebSocket connection. - void reset() - { - m_stage = Stage::init; - } - - // next() parses the new information and moves - // one stage forward. - void next() - { - switch (m_stage) { - case Stage::init: - stage_init(); - break; - case Stage::header_beginning: - stage_header_beginning(); - break; - case Stage::header_end: - stage_header_end(); - break; - case Stage::payload: - stage_payload(); - break; - case Stage::delivery: - stage_delivery(); - break; - default: - break; - } - } - -private: - bool& m_is_client; - - char header_buffer[14]; - char* m_masking_key; - size_t m_payload_size; - websocket::Opcode m_opcode = websocket::Opcode::continuation; - bool m_fin = false; - bool m_mask = false; - char m_short_payload_size = 0; - - char control_buffer[125]; // close, ping, pong. - - // A text or binary message can be fragmented. The - // message is built up in m_message_buffer. - std::vector m_message_buffer; - - // The opcode of the message. - websocket::Opcode m_message_opcode = websocket::Opcode::continuation; - - // The size of the stored Websocket message. - // This size is not the same as the size of the buffer. - size_t m_message_size = 0; - - // The message buffer has a minimum size, - // and is extended when a large message arrives. - // The message_buffer is resized to this value after - // a larger message has been delivered. - static const size_t s_message_buffer_min_size = 2048; - - enum class Stage { init, header_beginning, header_end, payload, delivery }; - Stage m_stage = Stage::init; - - void set_protocol_error() - { - protocol_error = true; - } - - void set_payload_buffer() - { - read_size = m_payload_size; - - if (m_opcode == websocket::Opcode::close || m_opcode == websocket::Opcode::ping || - m_opcode == websocket::Opcode::pong) { - read_buffer = control_buffer; - } - else { - size_t required_size = m_message_size + m_payload_size; - if (m_message_buffer.size() < required_size) - m_message_buffer.resize(required_size); - - read_buffer = m_message_buffer.data() + m_message_size; - } - } - - void reset_message_buffer() - { - if (m_message_buffer.size() != s_message_buffer_min_size) - m_message_buffer.resize(s_message_buffer_min_size); - m_message_opcode = websocket::Opcode::continuation; - m_message_size = 0; - } - - - // stage_init() is only called once for a FrameReader. - // It just moves the stage to header_beginning and - // asks for two bytes to be read into the header_buffer. - void stage_init() - { - protocol_error = false; - delivery_ready = false; - delivery_buffer = nullptr; - delivery_size = 0; - delivery_opcode = websocket::Opcode::continuation; - m_stage = Stage::header_beginning; - reset_message_buffer(); - read_buffer = header_buffer; - read_size = 2; - } - - // When stage_header_beginning() is called, the - // first two bytes in the header_buffer are - // the first two bytes of an incoming WebSocket frame. - void stage_header_beginning() - { - // bit 1. - m_fin = ((header_buffer[0] & 128) == 128); - - // bit 2,3, and 4. - char rsv = (header_buffer[0] & 112) >> 4; - if (rsv != 0) - return set_protocol_error(); - - // bit 5, 6, 7, and 8. - char op = (header_buffer[0] & 15); - - if (op != 0 && op != 1 && op != 2 && op != 8 && op != 9 && op != 10) - return set_protocol_error(); - - m_opcode = websocket::Opcode(op); - - // bit 9. - m_mask = ((header_buffer[1] & 128) == 128); - if ((m_mask && m_is_client) || (!m_mask && !m_is_client)) - return set_protocol_error(); - - // Remainder of second byte. - m_short_payload_size = (header_buffer[1] & 127); - - if (m_opcode == websocket::Opcode::continuation) { - if (m_message_opcode == websocket::Opcode::continuation) - return set_protocol_error(); - } - else if (m_opcode == websocket::Opcode::text || m_opcode == websocket::Opcode::binary) { - if (m_message_opcode != websocket::Opcode::continuation) - return set_protocol_error(); - - m_message_opcode = m_opcode; - } - else { // close, ping, pong. - if (!m_fin || m_short_payload_size > 125) - return set_protocol_error(); - } - - if (m_short_payload_size <= 125 && m_mask) { - m_stage = Stage::header_end; - m_payload_size = m_short_payload_size; - read_size = 4; - read_buffer = header_buffer + 2; - } - else if (m_short_payload_size <= 125 && !m_mask) { - m_stage = Stage::payload; - m_payload_size = m_short_payload_size; - set_payload_buffer(); - } - else if (m_short_payload_size == 126 && m_mask) { - m_stage = Stage::header_end; - read_size = 6; - read_buffer = header_buffer + 2; - } - else if (m_short_payload_size == 126 && !m_mask) { - m_stage = Stage::header_end; - read_size = 2; - read_buffer = header_buffer + 2; - } - else if (m_short_payload_size == 127 && m_mask) { - m_stage = Stage::header_end; - read_size = 12; - read_buffer = header_buffer + 2; - } - else if (m_short_payload_size == 127 && !m_mask) { - m_stage = Stage::header_end; - read_size = 8; - read_buffer = header_buffer + 2; - } - } - - void stage_header_end() - { - if (m_short_payload_size <= 125) { - m_masking_key = header_buffer + 2; - } - else if (m_short_payload_size == 126) { - const unsigned char* bytes = reinterpret_cast(header_buffer + 2); - m_payload_size = bytes[0] * 256 + bytes[1]; - - if (m_mask) - m_masking_key = header_buffer + 4; - } - else if (m_short_payload_size == 127) { - if (header_buffer[2] != 0 || header_buffer[3] != 0 || header_buffer[4] != 0 || header_buffer[5] != 0) { - // Message should be smaller than 4GB - // FIXME: We should introduce a maximum size for messages. - set_protocol_error(); - return; - } - - // Assume size_t is at least 4 bytes wide. - const unsigned char* bytes = reinterpret_cast(header_buffer + 6); - m_payload_size = bytes[0]; - m_payload_size = 256 * m_payload_size + bytes[1]; - m_payload_size = 256 * m_payload_size + bytes[2]; - m_payload_size = 256 * m_payload_size + bytes[3]; - - if (m_mask) - m_masking_key = header_buffer + 10; - } - - m_stage = Stage::payload; - set_payload_buffer(); - } - - void stage_payload() - { - if (m_mask) - mask_payload(m_masking_key, read_buffer, m_payload_size, read_buffer); - - if (m_opcode == websocket::Opcode::close || m_opcode == websocket::Opcode::ping || - m_opcode == websocket::Opcode::pong) { - m_stage = Stage::delivery; - delivery_ready = true; - delivery_opcode = m_opcode; - delivery_buffer = control_buffer; - delivery_size = m_payload_size; - } - else { - m_message_size += m_payload_size; - if (m_fin) { - m_stage = Stage::delivery; - delivery_ready = true; - delivery_opcode = m_message_opcode; - delivery_buffer = m_message_buffer.data(); - delivery_size = m_message_size; - } - else { - m_stage = Stage::header_beginning; - read_buffer = header_buffer; - read_size = 2; - } - } - } - - void stage_delivery() - { - m_stage = Stage::header_beginning; - read_buffer = header_buffer; - read_size = 2; - delivery_ready = false; - delivery_buffer = nullptr; - delivery_size = 0; - delivery_opcode = websocket::Opcode::continuation; - - if (m_opcode == websocket::Opcode::continuation || m_opcode == websocket::Opcode::text || - m_opcode == websocket::Opcode::binary) - reset_message_buffer(); - } -}; - - -class WebSocket { -public: - WebSocket(websocket::Config& config) - : m_config(config) - , m_logger_ptr(config.websocket_get_logger()) - , m_logger{*m_logger_ptr} - , m_frame_reader(m_logger, m_is_client) - { - m_logger.debug(util::LogCategory::network, "WebSocket::Websocket()"); - } - - void initiate_client_handshake(const std::string& request_uri, const std::string& host, - const std::string& sec_websocket_protocol, HTTPHeaders headers) - { - m_logger.debug(util::LogCategory::network, "WebSocket::initiate_client_handshake()"); - - m_stopped = false; - m_is_client = true; - - m_sec_websocket_key = make_random_sec_websocket_key(m_config.websocket_get_random()); - - m_http_client.reset(new HTTPClient(m_config, m_logger_ptr)); - m_frame_reader.reset(); - - if (m_test_handshake_response) { - HTTPResponse test_response; - test_response.status = HTTPStatus(*m_test_handshake_response); - test_response.body = std::move(m_test_handshake_response_body); - m_test_handshake_response.reset(); - m_test_handshake_response_body.clear(); - handle_http_response_received(std::move(test_response)); // Throws - return; - } - - HTTPRequest req; - req.method = HTTPMethod::Get; - req.path = std::move(request_uri); - req.headers = std::move(headers); - req.headers["Host"] = std::move(host); - req.headers["Upgrade"] = "websocket"; - req.headers["Connection"] = "Upgrade"; - req.headers["Sec-WebSocket-Key"] = m_sec_websocket_key; - req.headers["Sec-WebSocket-Version"] = sec_websocket_version; - req.headers["Sec-WebSocket-Protocol"] = sec_websocket_protocol; - - m_logger.trace(util::LogCategory::network, "HTTP request =\n%1", req); - - auto handler = [this](HTTPResponse response, std::error_code ec) { - // If the operation is aborted, the WebSocket object may have been destroyed. - if (ec != util::error::operation_aborted) { - if (ec == HTTPParserError::MalformedResponse) { - error_client_malformed_response(); - return; - } - if (ec) { - stop(); - - // FIXME: Should be read instaed of write??? - m_config.websocket_write_error_handler(ec); - - return; - } - if (m_stopped) - return; - handle_http_response_received(std::move(response)); // Throws - } - }; - - m_http_client->async_request(req, std::move(handler)); - } - - void initiate_server_websocket_after_handshake() - { - m_stopped = false; - m_is_client = false; - m_frame_reader.reset(); - frame_reader_loop(); // Throws - } - - void initiate_server_handshake() - { - m_logger.debug(util::LogCategory::network, "WebSocket::initiate_server_handshake()"); - - m_stopped = false; - m_is_client = false; - m_http_server.reset(new HTTPServer(m_config, m_logger_ptr)); - m_frame_reader.reset(); - - auto handler = [this](HTTPRequest request, std::error_code ec) { - if (ec != util::error::operation_aborted) { - if (ec == HTTPParserError::MalformedRequest) { - error_server_malformed_request(); - return; - } - if (ec) { - stop(); - m_config.websocket_read_error_handler(ec); - return; - } - if (m_stopped) - return; - handle_http_request_received(std::move(request)); - } - }; - - m_http_server->async_receive_request(std::move(handler)); - } - - void async_write_frame(bool fin, int opcode, const char* data, size_t size, - sync::websocket::WriteCompletionHandler write_completion_handler) - { - REALM_ASSERT(!m_stopped); - - bool mask = m_is_client; - - // 14 is the maximum header length of a Websocket frame. - size_t required_size = size + 14; - if (m_write_buffer.size() < required_size) - m_write_buffer.resize(required_size); - - size_t message_size = - make_frame(fin, opcode, mask, data, size, m_write_buffer.data(), m_config.websocket_get_random()); - - auto handler = [this, handler = std::move(write_completion_handler)](std::error_code ec, size_t) mutable { - // If the operation is aborted, then the write operation was canceled and we should ignore this callback. - if (ec == util::error::operation_aborted) { - return handler(ec, 0); - } - - auto is_socket_closed_err = (ec == util::error::make_error_code(util::error::connection_reset) || - ec == util::error::make_error_code(util::error::broken_pipe) || - ec == util::make_error_code(util::MiscExtErrors::end_of_input)); - // If the socket has been closed then we should continue to read from it until we've drained - // the receive buffer. Eventually we will either receive an in-band error message from the - // server about why we got disconnected or we'll receive ECONNRESET on the receive side as well. - if (is_socket_closed_err) { - return; - } - - // Otherwise we've got some other I/O error that we should surface to the sync client. - if (ec) { - stop(); - return m_config.websocket_write_error_handler(ec); - } - - handle_write_message(std::move(handler)); - }; - - m_config.async_write(m_write_buffer.data(), message_size, std::move(handler)); - } - - void handle_write_message(sync::websocket::WriteCompletionHandler write_handler) - { - if (m_write_buffer.size() > s_write_buffer_stable_size) { - m_write_buffer.resize(s_write_buffer_stable_size); - m_write_buffer.shrink_to_fit(); - } - - write_handler(std::error_code(), m_write_buffer.size()); - } - - void stop() noexcept - { - m_stopped = true; - m_frame_reader.reset(); - } - - void force_handshake_response_for_testing(int status_code, std::string body) - { - m_test_handshake_response.emplace(status_code); - m_test_handshake_response_body = body; - } - -private: - websocket::Config& m_config; - const std::shared_ptr m_logger_ptr; - util::Logger& m_logger; - FrameReader m_frame_reader; - - bool m_stopped = false; - bool m_is_client; - - // Allocated on demand. - std::unique_ptr> m_http_client; - std::unique_ptr> m_http_server; - - std::string m_sec_websocket_key; - std::string m_sec_websocket_accept; - - std::vector m_write_buffer; - static const size_t s_write_buffer_stable_size = 2048; - - std::optional m_test_handshake_response; - std::string m_test_handshake_response_body; - - void error_client_malformed_response() - { - m_stopped = true; - m_logger.error(util::LogCategory::network, "WebSocket: Received malformed HTTP response"); - std::error_code ec = HttpError::bad_response_invalid_http; - m_config.websocket_handshake_error_handler(ec, nullptr, {}); // Throws - } - - void error_client_response_not_101(const HTTPResponse& response) - { - m_stopped = true; - - m_logger.error(util::LogCategory::network, - "Websocket: Expected HTTP response 101 Switching Protocols, " - "but received:\n%1", - response); - - int status_code = int(response.status); - std::error_code ec; - - if (status_code == 200) - ec = HttpError::bad_response_200_ok; - else if (status_code >= 200 && status_code < 300) - ec = HttpError::bad_response_2xx_successful; - else if (status_code == 301) - ec = HttpError::bad_response_301_moved_permanently; - else if (status_code == 308) - ec = HttpError::bad_response_308_permanent_redirect; - else if (status_code >= 300 && status_code < 400) - ec = HttpError::bad_response_3xx_redirection; - else if (status_code == 401) - ec = HttpError::bad_response_401_unauthorized; - else if (status_code == 403) - ec = HttpError::bad_response_403_forbidden; - else if (status_code == 404) - ec = HttpError::bad_response_404_not_found; - else if (status_code == 410) - ec = HttpError::bad_response_410_gone; - else if (status_code >= 400 && status_code < 500) - ec = HttpError::bad_response_4xx_client_errors; - else if (status_code == 500) - ec = HttpError::bad_response_500_internal_server_error; - else if (status_code == 502) - ec = HttpError::bad_response_502_bad_gateway; - else if (status_code == 503) - ec = HttpError::bad_response_503_service_unavailable; - else if (status_code == 504) - ec = HttpError::bad_response_504_gateway_timeout; - else if (status_code >= 500 && status_code < 600) - ec = HttpError::bad_response_5xx_server_error; - else - ec = HttpError::bad_response_unexpected_status_code; - - std::string_view body; - if (response.body) { - body = *response.body; - } - m_config.websocket_handshake_error_handler(ec, &response.headers, body); // Throws - } - - void error_client_response_websocket_headers_invalid(const HTTPResponse& response) - { - m_stopped = true; - - m_logger.error(util::LogCategory::network, - "Websocket: HTTP response has invalid websocket headers." - "HTTP response = \n%1", - response); - std::error_code ec = HttpError::bad_response_header_protocol_violation; - std::string_view body; - if (response.body) { - body = *response.body; - } - m_config.websocket_handshake_error_handler(ec, &response.headers, body); // Throws - } - - void error_server_malformed_request() - { - m_stopped = true; - m_logger.error(util::LogCategory::network, "WebSocket: Received malformed HTTP request"); - std::error_code ec = HttpError::bad_request_malformed_http; - m_config.websocket_handshake_error_handler(ec, nullptr, {}); // Throws - } - - void error_server_request_header_protocol_violation(std::error_code ec, const HTTPRequest& request) - { - m_stopped = true; - - m_logger.error(util::LogCategory::network, - "Websocket: HTTP request has invalid websocket headers." - "HTTP request = \n%1", - request); - m_config.websocket_handshake_error_handler(ec, &request.headers, {}); // Throws - } - - void protocol_error(std::error_code ec) - { - m_stopped = true; - m_config.websocket_protocol_error_handler(ec); - } - - // The client receives the HTTP response. - void handle_http_response_received(HTTPResponse response) - { - m_logger.debug(util::LogCategory::network, "WebSocket::handle_http_response_received()"); - m_logger.trace(util::LogCategory::network, "HTTP response = %1", response); - - if (response.status != HTTPStatus::SwitchingProtocols) { - error_client_response_not_101(response); - return; - } - - bool valid = (find_sec_websocket_accept(response.headers) && - m_sec_websocket_accept == make_sec_websocket_accept(m_sec_websocket_key)); - if (!valid) { - error_client_response_websocket_headers_invalid(response); - return; - } - - m_config.websocket_handshake_completion_handler(response.headers); - - if (m_stopped) - return; - - frame_reader_loop(); - } - - void handle_http_request_received(HTTPRequest request) - { - m_logger.trace(util::LogCategory::network, "WebSocket::handle_http_request_received()"); - - util::Optional sec_websocket_protocol = websocket::read_sec_websocket_protocol(request); - - std::error_code ec; - util::Optional response = - do_make_http_response(request, sec_websocket_protocol ? *sec_websocket_protocol : "realm.io", ec); - - if (ec) { - error_server_request_header_protocol_violation(ec, request); - return; - } - REALM_ASSERT(response); - - auto handler = [request, this](std::error_code ec) { - // If the operation is aborted, the socket object may have been destroyed. - if (ec != util::error::operation_aborted) { - if (ec) { - stop(); - m_config.websocket_write_error_handler(ec); - return; - } - - if (m_stopped) - return; - - m_config.websocket_handshake_completion_handler(request.headers); - - if (m_stopped) - return; - - frame_reader_loop(); // Throws - } - }; - m_http_server->async_send_response(*response, std::move(handler)); - } - - // find_sec_websocket_accept is similar to - // find_sec_websockey_key. - bool find_sec_websocket_accept(const HTTPHeaders& headers) - { - util::Optional header_value = find_http_header_value(headers, "Sec-WebSocket-Accept"); - - if (!header_value) - return false; - - m_sec_websocket_accept = *header_value; - - return true; - } - - std::pair parse_close_message(const char* data, size_t size) - { - uint16_t error_code; - std::string_view error_message; - if (size < 2) { - // Error code 1005 is defined as - // 1005 is a reserved value and MUST NOT be set as a status code in a - // Close control frame by an endpoint. It is designated for use in - // applications expecting a status code to indicate that no status - // code was actually present. - // See https://tools.ietf.org/html/rfc6455#section-7.4.1 for more details - error_code = 1005; - } - else { - // Otherwise, the error code is the first two bytes of the body as a uint16_t in - // network byte order. See https://tools.ietf.org/html/rfc6455#section-5.5.1 for more - // details. - error_code = ntohs((uint8_t(data[1]) << 8) | uint8_t(data[0])); - error_message = std::string_view(data + 2, size - 2); - } - - switch (static_cast(error_code)) { - case WebSocketError::websocket_ok: - case WebSocketError::websocket_going_away: - case WebSocketError::websocket_protocol_error: - case WebSocketError::websocket_unsupported_data: - case WebSocketError::websocket_reserved: - case WebSocketError::websocket_no_status_received: - case WebSocketError::websocket_abnormal_closure: - case WebSocketError::websocket_invalid_payload_data: - case WebSocketError::websocket_policy_violation: - case WebSocketError::websocket_message_too_big: - case WebSocketError::websocket_invalid_extension: - case WebSocketError::websocket_internal_server_error: - case WebSocketError::websocket_tls_handshake_failed: - - case WebSocketError::websocket_unauthorized: - case WebSocketError::websocket_forbidden: - case WebSocketError::websocket_moved_permanently: - break; - default: - error_code = 1008; - } - - return std::make_pair(static_cast(error_code), error_message); - } - - // frame_reader_loop() uses the frame_reader to read and process the incoming - // WebSocket messages. - void frame_reader_loop() - { - // Advance parsing stage. - m_frame_reader.next(); - - if (m_frame_reader.protocol_error) { - protocol_error(HttpError::bad_message); - return; - } - - if (m_frame_reader.delivery_ready) { - bool should_continue = true; - - switch (m_frame_reader.delivery_opcode) { - case websocket::Opcode::text: - should_continue = m_config.websocket_text_message_received(m_frame_reader.delivery_buffer, - m_frame_reader.delivery_size); - break; - case websocket::Opcode::binary: - should_continue = m_config.websocket_binary_message_received(m_frame_reader.delivery_buffer, - m_frame_reader.delivery_size); - break; - case websocket::Opcode::close: { - auto [error_code, error_message] = - parse_close_message(m_frame_reader.delivery_buffer, m_frame_reader.delivery_size); - should_continue = m_config.websocket_close_message_received(error_code, error_message); - break; - } - case websocket::Opcode::ping: - should_continue = m_config.websocket_ping_message_received(m_frame_reader.delivery_buffer, - m_frame_reader.delivery_size); - break; - case websocket::Opcode::pong: - should_continue = m_config.websocket_pong_message_received(m_frame_reader.delivery_buffer, - m_frame_reader.delivery_size); - break; - default: - break; - } - - // The websocket object might not even exist anymore - if (!should_continue) - return; - - if (m_stopped) - return; - - // recursion is harmless, since the depth will be at most 2. - frame_reader_loop(); - return; - } - - auto handler = [this](std::error_code ec, size_t) { - // If the operation is aborted, the socket object may have been destroyed. - if (ec != util::error::operation_aborted) { - if (ec) { - stop(); - m_config.websocket_read_error_handler(ec); - return; - } - - if (m_stopped) - return; - - frame_reader_loop(); - } - }; - - m_config.async_read(m_frame_reader.read_buffer, m_frame_reader.read_size, std::move(handler)); - } -}; - - -const char* get_error_message(HttpError error_code) -{ - switch (error_code) { - case HttpError::bad_request_malformed_http: - return "Bad WebSocket request malformed HTTP"; - case HttpError::bad_request_header_upgrade: - return "Bad WebSocket request header: Upgrade"; - case HttpError::bad_request_header_connection: - return "Bad WebSocket request header: Connection"; - case HttpError::bad_request_header_websocket_version: - return "Bad WebSocket request header: Sec-Websocket-Version"; - case HttpError::bad_request_header_websocket_key: - return "Bad WebSocket request header: Sec-Websocket-Key"; - case HttpError::bad_response_invalid_http: - return "Bad WebSocket response invalid HTTP"; - case HttpError::bad_response_2xx_successful: - return "Bad WebSocket response 2xx successful"; - case HttpError::bad_response_200_ok: - return "Bad WebSocket response 200 ok"; - case HttpError::bad_response_3xx_redirection: - return "Bad WebSocket response 3xx redirection"; - case HttpError::bad_response_301_moved_permanently: - return "Bad WebSocket response 301 moved permanently"; - case HttpError::bad_response_308_permanent_redirect: - return "Bad WebSocket response 308 permanent redirect"; - case HttpError::bad_response_4xx_client_errors: - return "Bad WebSocket response 4xx client errors"; - case HttpError::bad_response_401_unauthorized: - return "Bad WebSocket response 401 unauthorized"; - case HttpError::bad_response_403_forbidden: - return "Bad WebSocket response 403 forbidden"; - case HttpError::bad_response_404_not_found: - return "Bad WebSocket response 404 not found"; - case HttpError::bad_response_410_gone: - return "Bad WebSocket response 410 gone"; - case HttpError::bad_response_5xx_server_error: - return "Bad WebSocket response 5xx server error"; - case HttpError::bad_response_500_internal_server_error: - return "Bad WebSocket response 500 internal server error"; - case HttpError::bad_response_502_bad_gateway: - return "Bad WebSocket response 502 bad gateway"; - case HttpError::bad_response_503_service_unavailable: - return "Bad WebSocket response 503 service unavailable"; - case HttpError::bad_response_504_gateway_timeout: - return "Bad WebSocket response 504 gateway timeout"; - case HttpError::bad_response_unexpected_status_code: - return "Bad Websocket response unexpected status code"; - case HttpError::bad_response_header_protocol_violation: - return "Bad WebSocket response header protocol violation"; - case HttpError::bad_message: - return "Ill-formed WebSocket message"; - } - return nullptr; -} - - -class HttpErrorCategory : public std::error_category { -public: - const char* name() const noexcept override final - { - return "realm::sync::websocket::HttpError"; - } - std::string message(int error_code) const override final - { - const char* msg = get_error_message(HttpError(error_code)); - if (!msg) - msg = "Unknown error"; - std::string msg_2{msg}; // Throws (copy) - return msg_2; - } -}; - -} // unnamed namespace - -namespace realm::sync::websocket { - -std::ostream& operator<<(std::ostream& os, WebSocketError code) -{ - /// WebSocket error codes - auto str = [&]() -> const char* { - switch (code) { - case WebSocketError::websocket_ok: - return "WebSocket: OK"; - case WebSocketError::websocket_going_away: - return "WebSocket: Going Away"; - case WebSocketError::websocket_protocol_error: - return "WebSocket: Protocol Error"; - case WebSocketError::websocket_unsupported_data: - return "WebSocket: Unsupported Data"; - case WebSocketError::websocket_reserved: - return "WebSocket: Reserved"; - case WebSocketError::websocket_no_status_received: - return "WebSocket: No Status Received"; - case WebSocketError::websocket_abnormal_closure: - return "WebSocket: Abnormal Closure"; - case WebSocketError::websocket_invalid_payload_data: - return "WebSocket: Invalid Payload Data"; - case WebSocketError::websocket_policy_violation: - return "WebSocket: Policy Violation"; - case WebSocketError::websocket_message_too_big: - return "WebSocket: Message Too Big"; - case WebSocketError::websocket_invalid_extension: - return "WebSocket: Invalid Extension"; - case WebSocketError::websocket_internal_server_error: - return "WebSocket: Internal Server Error"; - case WebSocketError::websocket_tls_handshake_failed: - return "WebSocket: TLS Handshake Failed"; - - /// WebSocket Errors - reported by server - case WebSocketError::websocket_unauthorized: - return "WebSocket: Unauthorized"; - case WebSocketError::websocket_forbidden: - return "WebSocket: Forbidden"; - case WebSocketError::websocket_moved_permanently: - return "WebSocket: Moved Permanently"; - - case WebSocketError::websocket_resolve_failed: - return "WebSocket: Resolve Failed"; - case WebSocketError::websocket_connection_failed: - return "WebSocket: Connection Failed"; - case WebSocketError::websocket_read_error: - return "WebSocket: Read Error"; - case WebSocketError::websocket_write_error: - return "WebSocket: Write Error"; - case WebSocketError::websocket_retry_error: - return "WebSocket: Retry Error"; - case WebSocketError::websocket_fatal_error: - return "WebSocket: Fatal Error"; - } - return nullptr; - }(); - - if (str == nullptr) { - os << "WebSocket: Unknown Error (" << static_cast>(code) << ")"; - } - else { - os << str; - } - return os; -} - -} // namespace realm::sync::websocket - -bool websocket::Config::websocket_text_message_received(const char*, size_t) -{ - return true; -} - -bool websocket::Config::websocket_binary_message_received(const char*, size_t) -{ - return true; -} - -bool websocket::Config::websocket_close_message_received(WebSocketError, std::string_view) -{ - return true; -} - -bool websocket::Config::websocket_ping_message_received(const char*, size_t) -{ - return true; -} - -bool websocket::Config::websocket_pong_message_received(const char*, size_t) -{ - return true; -} - - -class websocket::Socket::Impl : public WebSocket { -public: - Impl(Config& config) - : WebSocket(config) // Throws - { - } -}; - -websocket::Socket::Socket(Config& config) - : m_impl(new Impl{config}) -{ -} - -websocket::Socket::Socket(Socket&& socket) noexcept - : m_impl(std::move(socket.m_impl)) -{ -} - -websocket::Socket::~Socket() noexcept {} - -void websocket::Socket::initiate_client_handshake(const std::string& request_uri, const std::string& host, - const std::string& sec_websocket_protocol, HTTPHeaders headers) -{ - m_impl->initiate_client_handshake(request_uri, host, sec_websocket_protocol, std::move(headers)); -} - -void websocket::Socket::initiate_server_handshake() -{ - m_impl->initiate_server_handshake(); -} - -void websocket::Socket::initiate_server_websocket_after_handshake() -{ - m_impl->initiate_server_websocket_after_handshake(); -} - -void websocket::Socket::async_write_frame(bool fin, Opcode opcode, const char* data, size_t size, - WriteCompletionHandler handler) -{ - m_impl->async_write_frame(fin, int(opcode), data, size, std::move(handler)); -} - -void websocket::Socket::async_write_text(const char* data, size_t size, WriteCompletionHandler handler) -{ - async_write_frame(true, Opcode::text, data, size, std::move(handler)); -} - -void websocket::Socket::async_write_binary(const char* data, size_t size, WriteCompletionHandler handler) -{ - async_write_frame(true, Opcode::binary, data, size, std::move(handler)); -} - -void websocket::Socket::async_write_close(const char* data, size_t size, WriteCompletionHandler handler) -{ - async_write_frame(true, Opcode::close, data, size, std::move(handler)); -} - -void websocket::Socket::async_write_ping(const char* data, size_t size, WriteCompletionHandler handler) -{ - async_write_frame(true, Opcode::ping, data, size, std::move(handler)); -} - -void websocket::Socket::async_write_pong(const char* data, size_t size, WriteCompletionHandler handler) -{ - async_write_frame(true, Opcode::pong, data, size, std::move(handler)); -} - -void websocket::Socket::stop() noexcept -{ - m_impl->stop(); -} - -void websocket::Socket::force_handshake_response_for_testing(int status_code, std::string body) -{ - m_impl->force_handshake_response_for_testing(status_code, body); -} - -util::Optional websocket::read_sec_websocket_protocol(const HTTPRequest& request) -{ - const HTTPHeaders& headers = request.headers; - const StringData header = "Sec-WebSocket-Protocol"; - util::Optional value = find_http_header_value(headers, header); - return value ? util::Optional(std::string(*value)) : util::none; -} - -util::Optional websocket::make_http_response(const HTTPRequest& request, - const std::string& sec_websocket_protocol, - std::error_code& ec) -{ - return do_make_http_response(request, sec_websocket_protocol, ec); -} - -const std::error_category& websocket::http_error_category() noexcept -{ - static const HttpErrorCategory category = {}; - return category; -} - -std::error_code websocket::make_error_code(HttpError error_code) noexcept -{ - return std::error_code{int(error_code), http_error_category()}; -} diff --git a/src/realm/sync/network/websocket.hpp b/src/realm/sync/network/websocket.hpp deleted file mode 100644 index c485ce9d384..00000000000 --- a/src/realm/sync/network/websocket.hpp +++ /dev/null @@ -1,232 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include -#include - -namespace realm::sync::websocket { - -using WriteCompletionHandler = util::UniqueFunction; -using ReadCompletionHandler = util::UniqueFunction; - -class Config { -public: - virtual ~Config() {} - - /// The Socket uses the caller supplied logger for logging. - virtual const std::shared_ptr& websocket_get_logger() noexcept = 0; - - /// The Socket needs random numbers to satisfy the Websocket protocol. - /// The caller must supply a random number generator. - virtual std::mt19937_64& websocket_get_random() noexcept = 0; - - //@{ - /// The three functions below are used by the Socket to read and write to the underlying - /// stream. The functions will typically be implemented as wrappers to a TCP/TLS stream, - /// but could also map to pure memory streams. These functions abstract away the details of - /// the underlying sockets. - /// The functions have the same semantics as util::Socket. - /// - /// FIXME: Require that implementations ensure no callback reentrance, i.e., - /// that the completion handler is never called from within the execution of - /// async_write(), async_read(), or async_read_until(). This guarantee is - /// provided by both network::Socket and network::ssl::Stream. - virtual void async_write(const char* data, size_t size, WriteCompletionHandler handler) = 0; - virtual void async_read(char* buffer, size_t size, ReadCompletionHandler handler) = 0; - virtual void async_read_until(char* buffer, size_t size, char delim, ReadCompletionHandler handler) = 0; - //@} - - /// websocket_handshake_completion_handler() is called when the websocket is connected, .i.e. - /// after the handshake is done. It is not allowed to send messages on the socket before the - /// handshake is done. No message_received callbacks will be called before the handshake is done. - virtual void websocket_handshake_completion_handler(const HTTPHeaders&) = 0; - - //@{ - /// websocket_read_error_handler() and websocket_write_error_handler() are called when an - /// error occurs on the underlying stream given by the async_read and async_write functions above. - /// The error_code is passed through. - /// - /// websocket_handshake_error_handler() will be called when there is an error in the handshake - /// such as "404 Not found". - /// - /// websocket_protocol_error_handler() is called when there is an protocol error in the incoming - /// websocket messages. - /// - /// After calling any of these error callbacks, the Socket will move into the stopped state, and - /// no more messages should be sent, or will be received. - /// It is safe to destroy the WebSocket object in these handlers. - virtual void websocket_read_error_handler(std::error_code) = 0; - virtual void websocket_write_error_handler(std::error_code) = 0; - virtual void websocket_handshake_error_handler(std::error_code, const HTTPHeaders*, std::string_view body) = 0; - virtual void websocket_protocol_error_handler(std::error_code) = 0; - //@} - - //@{ - /// The five callback functions below are called whenever a full message has arrived. - /// The Socket defragments fragmented messages internally and delivers a full message. - /// \param data size The message is delivered in this buffer - /// The buffer is only valid until the function returns. - /// \return value designates whether the WebSocket object should continue - /// processing messages. The normal return value is true. False must be returned if the - /// websocket object is destroyed during execution of the function. - virtual bool websocket_text_message_received(const char* data, size_t size); - virtual bool websocket_binary_message_received(const char* data, size_t size); - virtual bool websocket_close_message_received(WebSocketError code, std::string_view message); - virtual bool websocket_ping_message_received(const char* data, size_t size); - virtual bool websocket_pong_message_received(const char* data, size_t size); - //@} -}; - - -enum class Opcode { continuation = 0, text = 1, binary = 2, close = 8, ping = 9, pong = 10 }; - - -class Socket { -public: - Socket(Config&); - Socket(Socket&&) noexcept; - ~Socket() noexcept; - - /// initiate_client_handshake() starts the Socket in client mode. The Socket - /// will send the HTTP request that initiates the WebSocket protocol and - /// wait for the HTTP response from the server. The HTTP request will - /// contain the \param request_uri in the HTTP request line. The \param host - /// will be sent as the value in a HTTP Host header line. - /// \param sec_websocket_protocol will be set as header value for - /// Sec-WebSocket-Protocol. Extra HTTP headers can be provided in \a headers. - /// - /// When the server responds with a valid HTTP response, the callback - /// function websocket_handshake_completion_handler() is called. Messages - /// can only be sent and received after the handshake has completed. - void initiate_client_handshake(const std::string& request_uri, const std::string& host, - const std::string& sec_websocket_protocol, HTTPHeaders headers = HTTPHeaders{}); - - /// initiate_server_handshake() starts the Socket in server mode. It will - /// wait for a HTTP request from a client and respond with a HTTP response. - /// After sending a HTTP response, websocket_handshake_completion_handler() - /// is called. Messages can only be sent and received after the handshake - /// has completed. - void initiate_server_handshake(); - - /// initiate_server_websocket_after_handshake() starts the Socket in a state - /// where it will read and write WebSocket messages but it will expect the - /// handshake to have been completed by the caller. The use of this - /// function is to perform HTTP routing externally and then start the - /// WebSocket in case the HTTP request is an Upgrade to WebSocket. - /// Typically, the caller will have used make_http_response() to send the - /// HTTP response itself. - void initiate_server_websocket_after_handshake(); - - /// The async_write_* functions send frames. Only one frame should be sent at a time, - /// meaning that the user must wait for the handler to be called before sending the next frame. - /// The handler is type util::UniqueFunction and is called when the frame has been successfully - /// sent. In case of errors, the Config::websocket_write_error_handler() is called. - - /// async_write_frame() sends a single frame with this content: - /// \param fin The fin bit set to 0 or 1 - /// \param opcode Specifies the opcpde. - /// \param data size The frame payload is taken from this buffer. - /// \param handler Called when the frame has been successfully sent. Error s are reported through - /// websocket_write_error_handler() in Config. - /// This function is rather low level and should only be used with knowledge of the WebSocket protocol. - /// The five utility functions below are recommended for message sending. - /// - /// FIXME: Guarantee no callback reentrance, i.e., that the completion - /// handler, or the error handler in case an error occurs, is never called - /// from within the execution of async_write_frame(). - void async_write_frame(bool fin, Opcode opcode, const char* data, size_t size, WriteCompletionHandler handler); - - //@{ - /// Five utility functions used to send whole messages. These five - /// functions are implemented in terms of async_write_frame(). These - /// functions send whole unfragmented messages. These functions should be - /// preferred over async_write_frame() for most use cases. - /// - /// FIXME: Guarantee no callback reentrance, i.e., that the completion - /// handler, or the error handler in case an error occurs, is never called - /// from within the execution of async_write_text(), and its friends. This - /// is already assumed by the client and server implementations of the sync - /// protocol. - void async_write_text(const char* data, size_t size, WriteCompletionHandler handler); - void async_write_binary(const char* data, size_t size, WriteCompletionHandler handler); - void async_write_close(const char* data, size_t size, WriteCompletionHandler handler); - void async_write_ping(const char* data, size_t size, WriteCompletionHandler handler); - void async_write_pong(const char* data, size_t size, WriteCompletionHandler handler); - //@} - - /// stop() stops the socket. The socket will stop processing incoming data, - /// sending data, and calling callbacks. It is an error to attempt to send - /// a message after stop() has been called. stop() will typically be called - /// before the underlying TCP/TLS connection is closed. The Socket can be - /// restarted with initiate_client_handshake() and - /// initiate_server_handshake(). - void stop() noexcept; - - /// Specifies an alternate status code for the handshake response to simulate - /// failures returned from the server. - void force_handshake_response_for_testing(int status_code, std::string body = ""); - -private: - class Impl; - std::unique_ptr m_impl; -}; - - -/// read_sec_websocket_protocol() returns the value of the -/// header Sec-WebSocket-Protocol in the http request \a request. -/// None is returned if the header Sec-WebSocket-Protocol is absent -/// in the request. -util::Optional read_sec_websocket_protocol(const HTTPRequest& request); - -/// make_http_response() takes \a request as a WebSocket handshake request, -/// validates it, and makes a HTTP response. If the request is invalid, the -/// return value is None, and ec is set to Error::bad_request_header_*. -util::Optional make_http_response(const HTTPRequest& request, const std::string& sec_websocket_protocol, - std::error_code& ec); - -enum class HttpError { - bad_request_malformed_http, - bad_request_header_upgrade, - bad_request_header_connection, - bad_request_header_websocket_version, - bad_request_header_websocket_key, - bad_response_invalid_http, - bad_response_2xx_successful, - bad_response_200_ok, - bad_response_3xx_redirection, - bad_response_301_moved_permanently, - bad_response_308_permanent_redirect, - bad_response_4xx_client_errors, - bad_response_401_unauthorized, - bad_response_403_forbidden, - bad_response_404_not_found, - bad_response_410_gone, - bad_response_5xx_server_error, - bad_response_500_internal_server_error, - bad_response_502_bad_gateway, - bad_response_503_service_unavailable, - bad_response_504_gateway_timeout, - bad_response_unexpected_status_code, - bad_response_header_protocol_violation, - bad_message -}; - -const std::error_category& http_error_category() noexcept; - -std::error_code make_error_code(HttpError) noexcept; - -} // namespace realm::sync::websocket - -namespace std { - -template <> -struct is_error_code_enum { - static const bool value = true; -}; - -} // namespace std diff --git a/src/realm/sync/network/websocket_error.hpp b/src/realm/sync/network/websocket_error.hpp deleted file mode 100644 index 6ec2fc7268b..00000000000 --- a/src/realm/sync/network/websocket_error.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/************************************************************************* - * - * Copyright 2023 Realm Inc. - * - * 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 "realm/error_codes.h" - -#include - -namespace realm::sync::websocket { - -enum class WebSocketError { - websocket_ok = RLM_ERR_WEBSOCKET_OK, - websocket_going_away = RLM_ERR_WEBSOCKET_GOINGAWAY, - websocket_protocol_error = RLM_ERR_WEBSOCKET_PROTOCOLERROR, - websocket_unsupported_data = RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, - websocket_reserved = RLM_ERR_WEBSOCKET_RESERVED, - websocket_no_status_received = RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, - websocket_abnormal_closure = RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, - websocket_invalid_payload_data = RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, - websocket_policy_violation = RLM_ERR_WEBSOCKET_POLICYVIOLATION, - websocket_message_too_big = RLM_ERR_WEBSOCKET_MESSAGETOOBIG, - websocket_invalid_extension = RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, - websocket_internal_server_error = RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, - websocket_tls_handshake_failed = RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, // Used by default WebSocket - - // WebSocket Errors - reported by server - websocket_unauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, - websocket_forbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, - websocket_moved_permanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, - - websocket_resolve_failed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, - websocket_connection_failed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - websocket_read_error = RLM_ERR_WEBSOCKET_READ_ERROR, - websocket_write_error = RLM_ERR_WEBSOCKET_WRITE_ERROR, - websocket_retry_error = RLM_ERR_WEBSOCKET_RETRY_ERROR, - websocket_fatal_error = RLM_ERR_WEBSOCKET_FATAL_ERROR, -}; - -std::ostream& operator<<(std::ostream& os, WebSocketError code); - -} // namespace realm::sync::websocket diff --git a/src/realm/sync/noinst/changeset_index.cpp b/src/realm/sync/noinst/changeset_index.cpp deleted file mode 100644 index 473fd5a09e2..00000000000 --- a/src/realm/sync/noinst/changeset_index.cpp +++ /dev/null @@ -1,543 +0,0 @@ -#include -#include - -#include // std::distance, std::advance - -using namespace realm::sync; - -namespace realm { -namespace _impl { - -#if REALM_DEBUG -static bool compare_ranges(const Changeset::Range& left, const Changeset::Range& right) -{ - return left.begin < right.begin; -} - -template -static bool check_ranges(InputIterator begin, InputIterator end) -{ - if (!std::is_sorted(begin, end, compare_ranges)) - return false; - - // Check that there are no overlaps - if (begin != end) { - auto last_end = begin->end; - for (auto i = begin + 1; i != end; ++i) { - if (last_end > i->begin) - return false; - last_end = i->end; - } - } - return true; -} - -static bool check_ranges(const ChangesetIndex::Ranges& ranges) -{ - for (auto& pair : ranges) { - if (!check_ranges(pair.second.begin(), pair.second.end())) - return false; - } - return true; -} -#endif // REALM_DEBUG - - -void ChangesetIndex::clear() noexcept -{ - m_object_instructions.clear(); - m_schema_instructions.clear(); - m_conflict_groups_owner.clear(); - m_num_conflict_groups = 0; -} - - -void ChangesetIndex::scan_changeset(Changeset& changeset) -{ - if (m_contains_destructive_schema_changes) - return; - -#if defined(REALM_DEBUG) // LCOV_EXCL_START - for (auto& confict_group : m_conflict_groups_owner) { - // Check that add_changeset() has not been called yet. - REALM_ASSERT(confict_group.ranges.empty()); - } -#endif // REALM_DEBUG LCOV_EXCL_STOP - - using Instruction = realm::sync::Instruction; - - for (auto it = changeset.begin(); it != changeset.end(); ++it) { - if (!*it) - continue; - - const auto& instr = **it; - - if (auto add_table_instr = instr.get_if()) { - auto& p = *add_table_instr; - schema_conflict_group(changeset.get_string(p.table)); - } - else if (instr.get_if()) { - m_contains_destructive_schema_changes = true; - clear(); - return; - } - else if (auto add_column_instr = instr.get_if()) { - auto& p = *add_column_instr; - StringData table_name = changeset.get_string(p.table); - ConflictGroup& cg = schema_conflict_group(table_name); - if (p.type == Instruction::Payload::Type::Link) { - StringData target_table = changeset.get_string(p.link_target_table); - ConflictGroup& cg2 = schema_conflict_group(target_table); - merge_conflict_groups(cg, cg2); - } - } - else if (instr.get_if()) { - m_contains_destructive_schema_changes = true; - clear(); - return; - } - else { - GlobalID ids[2]; - size_t num_ids = get_object_ids_in_instruction(changeset, instr, ids, 2); - REALM_ASSERT(num_ids >= 1); - REALM_ASSERT(num_ids <= 2); - - ConflictGroup& cg = object_conflict_group(ids[0]); - for (size_t i = 1; i < num_ids; ++i) { - ConflictGroup& cg2 = object_conflict_group(ids[1]); - merge_conflict_groups(cg, cg2); - } - } - } -} - - -void ChangesetIndex::merge_conflict_groups(ConflictGroup& into, ConflictGroup& from) -{ - if (&into == &from) - return; - - if (from.size > into.size) { - // This is an optimization. The time it takes to merge two conflict - // groups is proportional to the size of the incoming group (in number - // of objects touched). If the incoming group is larger, merge the other - // way. - merge_conflict_groups(from, into); - return; - } - - REALM_ASSERT(into.ranges.empty()); - REALM_ASSERT(from.ranges.empty()); - - for (auto& class_name : from.schemas) { - m_schema_instructions[class_name] = &into; - } - into.schemas.insert(into.schemas.end(), from.schemas.begin(), from.schemas.end()); - - for (auto& pair : from.objects) { - auto& objset = into.objects[pair.first]; - auto& objinstr = m_object_instructions[pair.first]; - for (auto& object : pair.second) { - objinstr[object] = &into; - } - objset.insert(objset.end(), pair.second.begin(), pair.second.end()); - } - into.size += from.size; - - m_conflict_groups_owner.erase(from.self_it); - --m_num_conflict_groups; -} - - -void ChangesetIndex::add_changeset(Changeset& log) -{ - if (!log.empty()) - m_everything[&log] = std::vector(1, Changeset::Range{log.begin(), log.end()}); - - if (m_contains_destructive_schema_changes) - return; // Just add to everything. - - using Instruction = realm::sync::Instruction; - - // Iterate over all instructions (skipping tombstones), and add them to the - // index. - for (auto it = log.begin(); it != log.end(); ++it) { - if (!*it) - continue; - - const auto& instr = **it; - - if (auto add_table_instr = instr.get_if()) { - StringData table = log.get_string(add_table_instr->table); - auto& cg = schema_conflict_group(table); - add_instruction_at(cg.ranges, log, it); - } - else if (instr.get_if()) { - REALM_TERMINATE("Call scan_changeset() before add_changeset()."); - } - else if (auto add_column_instr = instr.get_if()) { - auto& p = *add_column_instr; - StringData table = log.get_string(p.table); - auto& cg = schema_conflict_group(table); - if (p.type == Instruction::Payload::Type::Link) { - REALM_ASSERT(&cg == &schema_conflict_group(log.get_string(p.link_target_table))); - } - add_instruction_at(cg.ranges, log, it); - } - else if (instr.get_if()) { - REALM_TERMINATE("Call scan_changeset() before add_changeset()."); - } - else { - GlobalID ids[2]; - size_t num_ids = get_object_ids_in_instruction(log, instr, ids, 2); - REALM_ASSERT(num_ids >= 1); - REALM_ASSERT(num_ids <= 2); - - auto& cg = object_conflict_group(ids[0]); - for (size_t i = 1; i < num_ids; ++i) { - REALM_ASSERT(&cg == &object_conflict_group(ids[i])); - } - add_instruction_at(cg.ranges, log, it); - } - } -} - -size_t get_object_ids_in_instruction(const Changeset& changeset, const sync::Instruction& instr, - ChangesetIndex::GlobalID* ids, size_t max_num_ids) -{ - REALM_ASSERT_RELEASE(max_num_ids >= 2); - - using Instruction = realm::sync::Instruction; - - if (auto obj_instr = instr.get_if()) { - ids[0] = {changeset.get_string(obj_instr->table), changeset.get_key(obj_instr->object)}; - - if (auto set_instr = instr.get_if()) { - auto& p = *set_instr; - if (p.value.type == Instruction::Payload::Type::Link) { - ids[1] = {changeset.get_string(p.value.data.link.target_table), - changeset.get_key(p.value.data.link.target)}; - return 2; - } - } - else if (auto insert_instr = instr.get_if()) { - auto& p = *insert_instr; - if (p.value.type == Instruction::Payload::Type::Link) { - ids[1] = {changeset.get_string(p.value.data.link.target_table), - changeset.get_key(p.value.data.link.target)}; - return 2; - } - } - - return 1; - } - - return 0; -} - -auto ChangesetIndex::get_schema_changes_for_class(StringData class_name) const -> const Ranges* -{ - return const_cast(this)->get_schema_changes_for_class(class_name); -} - -auto ChangesetIndex::get_schema_changes_for_class(StringData class_name) -> Ranges* -{ - if (m_contains_destructive_schema_changes) - return &m_everything; - auto it = m_schema_instructions.find(class_name); - if (it == m_schema_instructions.end()) - return &m_empty; - return &it->second->ranges; -} - -auto ChangesetIndex::get_modifications_for_object(GlobalID id) -> Ranges* -{ - if (m_contains_destructive_schema_changes) - return &m_everything; - auto it = m_object_instructions.find(id.table_name); - if (it == m_object_instructions.end()) - return &m_empty; - - auto& object_instructions = it->second; - auto it2 = object_instructions.find(id.object_id); - if (it2 == object_instructions.end()) - return &m_empty; - return &it2->second->ranges; -} - -auto ChangesetIndex::get_modifications_for_object(GlobalID id) const -> const Ranges* -{ - return const_cast(this)->get_modifications_for_object(id); -} - -auto ChangesetIndex::schema_conflict_group(StringData class_name) -> ConflictGroup& -{ - auto& conflict_group = m_schema_instructions[class_name]; - if (conflict_group == nullptr) { - m_conflict_groups_owner.emplace_back(); - ++m_num_conflict_groups; - auto new_cg = std::prev(m_conflict_groups_owner.end()); - new_cg->schemas.push_back(class_name); - new_cg->size = 1; - new_cg->self_it = new_cg; - conflict_group = &*new_cg; - } - return *conflict_group; -} - -auto ChangesetIndex::object_conflict_group(const GlobalID& object_id) -> ConflictGroup& -{ - auto& objects_for_table = m_object_instructions[object_id.table_name]; - auto& conflict_group = objects_for_table[object_id.object_id]; - if (conflict_group == nullptr) { - m_conflict_groups_owner.emplace_back(); - ++m_num_conflict_groups; - auto new_cg = std::prev(m_conflict_groups_owner.end()); - new_cg->objects[object_id.table_name].push_back(object_id.object_id); - new_cg->size = 1; - new_cg->self_it = new_cg; - conflict_group = &*new_cg; - } - return *conflict_group; -} - -auto ChangesetIndex::erase_instruction(RangeIterator pos) -> RangeIterator -{ - Changeset* changeset = pos.m_outer->first; - pos.check(); - auto new_pos = pos; - new_pos.m_pos = changeset->erase_stable(pos.m_pos); - - if (new_pos.m_pos >= new_pos.m_inner->end) { - // erased the last instruction in the range, move to the next range. - REALM_ASSERT(new_pos.m_inner < new_pos.m_outer->second.end()); - ++new_pos.m_inner; - if (new_pos.m_inner == new_pos.m_outer->second.end()) { - REALM_ASSERT(new_pos.m_outer != new_pos.m_ranges->end()); - ++new_pos.m_outer; - if (new_pos.m_outer == new_pos.m_ranges->end()) { - return RangeIterator{new_pos.m_ranges, RangeIterator::end_tag{}}; // avoid new_pos.check() - } - else { - new_pos.m_inner = new_pos.m_outer->second.begin(); - REALM_ASSERT(new_pos.m_inner != new_pos.m_outer->second.end()); - new_pos.m_pos = new_pos.m_inner->begin; - } - } - else { - new_pos.m_pos = new_pos.m_inner->begin; - REALM_ASSERT(new_pos.m_pos != new_pos.m_inner->end); // empty ranges not allowed - } - } - new_pos.check(); - return new_pos; -} - -#if REALM_DEBUG -static std::ostream& operator<<(std::ostream& os, GlobalID gid) -{ - return os << gid.table_name << "/" << format_pk(gid.object_id); -} - -void ChangesetIndex::print(std::ostream& os) const -{ - auto print_ranges = [&](auto& subjects, const Ranges& ranges) { - std::sort(subjects.begin(), subjects.end()); - subjects.erase(std::unique(subjects.begin(), subjects.end()), subjects.end()); - - os << "["; - for (auto it = subjects.begin(); it != subjects.end();) { - os << *it; - auto next = it; - ++next; - if (next != subjects.end()) - os << ", "; - it = next; - } - os << "]: "; - for (auto it = ranges.begin(); it != ranges.end();) { - os << "Changeset" << std::dec << it->first->version << "("; - for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { - auto offset = std::distance(it->first->begin(), it2->begin); - auto length = std::distance(it2->begin, it2->end); - os << "[" << std::dec << offset << "+" << length << "]"; - if (it2 + 1 != it->second.end()) - os << ", "; - } - os << ")"; - auto next = it; - ++next; - if (next != ranges.end()) - os << ", "; - it = next; - } - }; - - std::map> schema_modifications; - std::map> object_modifications; - - for (auto& pair : m_schema_instructions) { - schema_modifications[&pair.second->ranges].push_back(pair.first); - } - - for (auto& pair : m_object_instructions) { - for (auto& pair2 : pair.second) { - object_modifications[&pair2.second->ranges].push_back(GlobalID{pair.first, pair2.first}); - } - } - - if (schema_modifications.size()) { - os << "SCHEMA MODIFICATIONS:\n"; - for (auto& pair : schema_modifications) { - print_ranges(pair.second, *pair.first); - os << "\n"; - } - os << "\n"; - } - - if (object_modifications.size()) { - os << "OBJECT MODIFICATIONS:\n"; - for (auto& pair : object_modifications) { - print_ranges(pair.second, *pair.first); - os << "\n"; - } - os << "\n"; - } -} - -void ChangesetIndex::verify() const -{ - REALM_ASSERT(m_num_conflict_groups == m_conflict_groups_owner.size()); - - // Verify that there are no stray pointers. - for (auto& pair : m_object_instructions) { - for (auto& pair2 : pair.second) { - REALM_ASSERT(&*pair2.second->self_it == pair2.second); - REALM_ASSERT(std::any_of(m_conflict_groups_owner.begin(), m_conflict_groups_owner.end(), [&](auto& cg) { - return &cg == pair2.second; - })); - } - } - - for (auto& pair : m_schema_instructions) { - REALM_ASSERT(&*pair.second->self_it == pair.second); - REALM_ASSERT(std::any_of(m_conflict_groups_owner.begin(), m_conflict_groups_owner.end(), [&](auto& cg) { - return &cg == pair.second; - })); - } - - // Collect all changesets - std::vector changesets; - for (auto& cg : m_conflict_groups_owner) { - check_ranges(cg.ranges); - - for (auto& ranges : cg.ranges) { - changesets.push_back(ranges.first); - } - } - std::sort(changesets.begin(), changesets.end(), std::less<>()); - changesets.erase(std::unique(changesets.begin(), changesets.end()), changesets.end()); - - // Run through all instructions in each changeset and check that - // instructions are correctly covered by the index. - for (auto changeset : changesets) { - auto& log = *changeset; - - using Instruction = realm::sync::Instruction; - - // Iterate over all instructions (skipping tombstones), and verify that - // the index covers any objects mentioned in that instruction. - for (auto it = log.begin(); it != log.end(); ++it) { - if (!*it) - continue; - - const auto& instr = **it; - - if (auto add_table_instr = instr.get_if()) { - StringData table = log.get_string(add_table_instr->table); - auto ranges = *get_schema_changes_for_class(table); - REALM_ASSERT(ranges_cover(ranges, log, it)); - } - else if (auto erase_table_instr = instr.get_if()) { - StringData table = log.get_string(erase_table_instr->table); - auto ranges = *get_schema_changes_for_class(table); - REALM_ASSERT(ranges_cover(ranges, log, it)); - } - else if (auto add_column_instr = instr.get_if()) { - StringData table = log.get_string(add_column_instr->table); - auto ranges = *get_schema_changes_for_class(table); - REALM_ASSERT(ranges_cover(ranges, log, it)); - } - else if (auto erase_column_instr = instr.get_if()) { - StringData table = log.get_string(erase_column_instr->table); - auto ranges = *get_schema_changes_for_class(table); - REALM_ASSERT(ranges_cover(ranges, log, it)); - } - else { - GlobalID ids[2]; - size_t num_ids = get_object_ids_in_instruction(log, instr, ids, 2); - REALM_ASSERT(num_ids >= 1); - REALM_ASSERT(num_ids <= 2); - auto& ranges_first = *get_modifications_for_object(ids[0]); - - for (size_t i = 0; i < num_ids; ++i) { - auto& ranges = *get_modifications_for_object(ids[i]); - REALM_ASSERT(&ranges == &ranges_first); - REALM_ASSERT(ranges_cover(ranges, log, it)); - } - } - } - } -} - -bool ChangesetIndex::ranges_cover(const Ranges& ranges, Changeset& log, Changeset::const_iterator it) const -{ - auto outer = ranges.find(&log); - if (outer == ranges.end()) - return false; - for (auto& range : outer->second) { - if (it >= range.begin && it < range.end) - return true; - } - return false; -} - -#endif // REALM_DEBUG - -void ChangesetIndex::add_instruction_at(Ranges& ranges, Changeset& changeset, Changeset::iterator pos) -{ - auto& ranges_for_changeset = ranges[&changeset]; - - REALM_ASSERT(pos != changeset.end()); - auto next = pos; - ++next; - - Changeset::Range incoming{pos, next}; - - auto cmp = [](const Changeset::Range& range_a, const Changeset::Range& range_b) { - return range_a.begin < range_b.begin; - }; - - auto it = std::lower_bound(ranges_for_changeset.begin(), ranges_for_changeset.end(), incoming, cmp); - - it = ranges_for_changeset.insert(it, incoming); - if (it != ranges_for_changeset.begin()) - --it; - - // Merge adjacent overlapping ranges - while (it + 1 != ranges_for_changeset.end()) { - auto next_it = it + 1; - if (it->end >= next_it->begin) { - it->end = std::max(it->end, next_it->end); - next_it = ranges_for_changeset.erase(next_it); - it = next_it - 1; - } - else { - ++it; - } - } -} - - -} // namespace _impl -} // namespace realm diff --git a/src/realm/sync/noinst/changeset_index.hpp b/src/realm/sync/noinst/changeset_index.hpp deleted file mode 100644 index 0280b11621b..00000000000 --- a/src/realm/sync/noinst/changeset_index.hpp +++ /dev/null @@ -1,308 +0,0 @@ - -#ifndef REALM_NOINST_CHANGESET_INDEX_HPP -#define REALM_NOINST_CHANGESET_INDEX_HPP - -#include -#include -#include - -#include - -namespace realm { -namespace _impl { - -/// The ChangesetIndex is responsible for keeping track of exactly which -/// instructions touch which objects. It does this by recording ranges of -/// instructions in changesets, such that the merge algorithm can do with -/// just merging the "relevant" instructions. Due to the semantics of link -/// nullification, instruction ranges for objects that have ever been -/// "connected" by a link instruction must be joined together. In other words, -/// if two objects are connected by a link instruction in a changeset, all -/// instructions pertaining to both objects will be merged with any instruction -/// that touches either. -struct ChangesetIndex { - using Changeset = realm::sync::Changeset; - using GlobalID = realm::sync::GlobalID; - using PrimaryKey = realm::sync::PrimaryKey; - - struct CompareChangesetPointersByVersion { - bool operator()(const Changeset* a, const Changeset* b) const noexcept - { - if (a->version == b->version) { - return a->transform_sequence < b->transform_sequence; - } - return a->version < b->version; - } - }; - - // This is always sorted by (changeset->version, range->begin). - using Ranges = std::map, CompareChangesetPointersByVersion>; - - /// Scan changeset to discover objects connected by link instructions, - /// classes connected by link columns, and destructive schema changes. - /// - /// Note: This function must be called before calling `add_changeset()`, and - /// it must be called for both the changesets added to the index (incoming - /// changesets) and reciprocal changesets. - void scan_changeset(Changeset& changeset); - - /// Add instructions from \a changeset to the index. - /// - /// Note: It is an error to add the same changeset more than once. - void add_changeset(Changeset& changeset); - - //@{ - /// Returns ranges for every schema change that mentions any of the class - /// names. - /// Includes SelectTable instructions for column modifications. - /// - /// NOTE: The non-const version does not modify the index, but returns a - /// Ranges object that may iterated over in a non-const fashion (such as by - /// the OT merge algorithm). - Ranges* get_schema_changes_for_class(StringData class_name); - const Ranges* get_schema_changes_for_class(StringData class_name) const; - //@} - - //@{ - /// Returns ranges for every instruction touching the objects. - /// This includes schema changes for each object's class, and object - /// modifications to other objects that link to these objects. - /// - /// NOTE: The non-const version does not modify the index, but returns a - /// Ranges object that may iterated over in a non-const fashion (such as by - /// the OT merge algorithm). - Ranges* get_modifications_for_object(GlobalID id); - const Ranges* get_modifications_for_object(GlobalID id) const; - //@} - - //@{ - /// Returns the ranges for all instructions added to the index. - /// - /// NOTE: The non-const version does not modify the index, but returns a - /// Ranges object that may iterated over in a non-const fashion (such as by - /// the OT merge algorithm). - Ranges* get_everything() - { - return &m_everything; - } - const Ranges* get_everything() const - { - return &m_everything; - } - //@} - - size_t get_num_conflict_groups() const noexcept - { - return m_num_conflict_groups; - } - - struct RangeIterator; - - RangeIterator erase_instruction(RangeIterator); - -#if REALM_DEBUG - void print(std::ostream&) const; - void verify() const; - bool ranges_cover(const Ranges&, Changeset&, Changeset::const_iterator) const; -#endif // REALM_DEBUG - - // If ndx is inside or one-beyond the last range in `ranges`, that range is - // expanded. Otherwise, a new range is appended beginning at pos. - static void add_instruction_at(Ranges&, Changeset&, Changeset::iterator pos); - -private: - struct ConflictGroup { - Ranges ranges; - std::map> objects; - std::vector schemas; - size_t size = 0; - std::list::iterator self_it; - }; - std::map> m_object_instructions; - std::map m_schema_instructions; - std::list m_conflict_groups_owner; - size_t m_num_conflict_groups = 0; // must be kept in sync with m_conflict_groups_owner - - void clear() noexcept; - - Ranges m_empty; - Ranges m_everything; - bool m_contains_destructive_schema_changes = false; - - ConflictGroup& schema_conflict_group(StringData class_name); - ConflictGroup& object_conflict_group(const GlobalID& object_id); - - // Merge \a from into \a into, and delete \a from. - void merge_conflict_groups(ConflictGroup& into, ConflictGroup& from); -}; - - -/// Collapse and compact adjacent and overlapping ranges. -void compact_ranges(ChangesetIndex::Ranges& ranges, bool is_sorted = false); - -bool is_schema_change(const sync::Instruction&) noexcept; -bool is_container_instruction(const sync::Instruction&) noexcept; - -/// Extract any object references from the instruction and place them in the -/// buffer at \a ids, until \a max_ids references are found. -/// -/// \returns the number of object IDs found. At the time of writing, this cannot -/// surpass 2. -size_t get_object_ids_in_instruction(const sync::Changeset&, const sync::Instruction&, ChangesetIndex::GlobalID* ids, - size_t max_ids); - -/// The RangeIterator is used to iterate over instructions in a set of ranges. -/// -/// `Changeset::Ranges` is a list of ranges of instructions. This iterator hides -/// the indirection, and simply iterates over all the instructions covered by -/// the ranges provided to the constructor. -/// -/// The `RangeIterator` is composed of the `ChangesetIndex::Ranges::iterator` -/// and a `Changeset::iterator`. -struct ChangesetIndex::RangeIterator { - using pointer_type = sync::Instruction*; - using reference_type = sync::Instruction*; - - RangeIterator() {} - - /// Create an iterator representing the beginning. - explicit RangeIterator(ChangesetIndex::Ranges* ranges) noexcept - : m_ranges(ranges) - , m_outer(ranges->begin()) - { - if (m_outer != ranges->end()) { - m_inner = m_outer->second.begin(); - m_pos = m_inner->begin; - REALM_ASSERT(m_pos != m_inner->end); // empty ranges not allowed! - check(); - } - } - - struct end_tag {}; - /// Create an iterator representing the end. - RangeIterator(ChangesetIndex::Ranges* ranges, end_tag) noexcept - : m_ranges(ranges) - , m_outer(ranges->end()) - { - } - - void check() const noexcept - { - REALM_ASSERT_DEBUG(m_ranges); - REALM_ASSERT_DEBUG(m_outer != m_ranges->end()); - REALM_ASSERT_DEBUG(m_inner >= m_outer->second.begin()); - REALM_ASSERT_DEBUG(m_inner < m_outer->second.end()); - REALM_ASSERT_DEBUG(m_pos >= m_inner->begin); - REALM_ASSERT_DEBUG(m_pos < m_inner->end); - REALM_ASSERT_DEBUG(m_pos.m_inner >= m_outer->first->begin().m_inner); - REALM_ASSERT_DEBUG(m_pos.m_inner < m_outer->first->end().m_inner); - } - - /// Go to the next instruction in the range. If there are no more - /// instructions in the range, go to the next ranges. - RangeIterator& operator++() noexcept - { - REALM_ASSERT_DEBUG(m_outer != m_ranges->end()); - - ++m_pos; - if (REALM_UNLIKELY(m_pos == m_inner->end)) { - // Slow path - inc_inner(); - } - return *this; - } - - RangeIterator operator++(int) noexcept - { - auto copy = *this; - ++(*this); - return copy; - } - - RangeIterator& operator+=(size_t diff) noexcept - { - for (size_t i = 0; i < diff; ++i) { - ++(*this); - } - return *this; - } - - inline reference_type operator*() const noexcept - { - check(); - return *m_pos; - } - - pointer_type operator->() const noexcept - { - check(); - return m_pos.operator->(); - } - - bool operator==(const RangeIterator& other) const noexcept - { - REALM_ASSERT(m_ranges == other.m_ranges); - if (m_outer == other.m_outer) { - if (m_outer != m_ranges->end()) { - if (m_inner == other.m_inner) { - return m_pos == other.m_pos; - } - return false; - } - return true; - } - return false; - } - - bool operator!=(const RangeIterator& other) const noexcept - { - return !(*this == other); - } - - // FIXME: Quadruply nested iterators. This is madness. - ChangesetIndex::Ranges* m_ranges = nullptr; - ChangesetIndex::Ranges::iterator m_outer; - ChangesetIndex::Ranges::mapped_type::iterator m_inner; - Changeset::iterator m_pos; - -private: - void inc_inner() noexcept - { - ++m_inner; - if (m_inner == m_outer->second.end()) { - ++m_outer; - if (m_outer == m_ranges->end()) { - *this = RangeIterator{m_ranges, end_tag{}}; - return; // avoid check() - } - m_inner = m_outer->second.begin(); - } - m_pos = m_inner->begin; - REALM_ASSERT(m_pos < m_inner->end); // empty ranges not allowed - check(); - } -}; - - -// Implementation: - -inline bool is_schema_change(const sync::Instruction& instr) noexcept -{ - using Instruction = realm::sync::Instruction; - return instr.get_if() == nullptr; -} - -inline bool is_container_instruction(const sync::Instruction& instr) noexcept -{ - using Instruction = realm::sync::Instruction; - auto& v = instr.m_instr; - - return mpark::holds_alternative(v) || - mpark::holds_alternative(v) || - mpark::holds_alternative(v) || mpark::holds_alternative(v); -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_NOINST_CHANGESET_INDEX_HPP diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp deleted file mode 100644 index fbd51d88904..00000000000 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ /dev/null @@ -1,1516 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// 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 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace realm::sync { - -void ClientHistory::set_history_adjustments( - util::Logger& logger, version_type current_version, SaltedFileIdent client_file_ident, - SaltedVersion server_version, const std::vector<_impl::client_reset::RecoveredChange>& recovered_changesets) -{ - ensure_updated(current_version); // Throws - prepare_for_write(); // Throws - - version_type client_version = m_sync_history_base_version + sync_history_size(); - REALM_ASSERT(client_version == current_version); // For now - Array& root = m_arrays->root; - m_group->set_sync_file_id(client_file_ident.ident); // Throws - - size_t uploadable_bytes = 0; - if (recovered_changesets.empty()) { - // Either we had nothing to upload or we're discarding the unsynced changes - logger.debug("History adjustments: discarding %1 history entries", sync_history_size()); - do_trim_sync_history(sync_history_size()); // Throws - } - else { - // Discard all sync history before the first recovered changeset. This is - // required because we are going to discard our progress information and - // so won't know which history entries have been uploaded already. - auto first_version = recovered_changesets.front().version; - REALM_ASSERT(first_version >= m_sync_history_base_version); - auto discard_count = std::size_t(first_version - m_sync_history_base_version); - do_trim_sync_history(discard_count); - - if (logger.would_log(util::Logger::Level::debug)) { - logger.debug("History adjustments: trimming %1 history entries and updating the remaining history " - "entries (%2)", - discard_count, sync_history_size()); - for (size_t i = 0, size = m_arrays->changesets.size(); i < size; ++i) { - logger.debug("- %1: ident(%2) changeset_size(%3) remote_version(%4)", i, - m_arrays->origin_file_idents.get(i), m_arrays->changesets.get(i).size(), - m_arrays->remote_versions.get(i)); - } - } - - util::compression::CompressMemoryArena arena; - util::AppendBuffer compressed; - for (auto& [changeset, version] : recovered_changesets) { - uploadable_bytes += changeset.size(); - auto i = size_t(version - m_sync_history_base_version); - util::compression::allocate_and_compress_nonportable(arena, changeset, compressed); - m_arrays->changesets.set(i, BinaryData{compressed.data(), compressed.size()}); // Throws - m_arrays->reciprocal_transforms.set(i, BinaryData()); - } - // Server version is updated for *every* entry in the sync history to ensure that server versions don't - // decrease. - for (size_t i = 0, size = m_arrays->remote_versions.size(); i < size; ++i) { - m_arrays->remote_versions.set(i, server_version.version); - version_type version = m_sync_history_base_version + i; - logger.debug("Updating %1: client_version(%2) changeset_size(%3) server_version(%4)", i, version + 1, - m_arrays->changesets.get(i).size(), server_version.version); - } - } - logger.debug("New uploadable bytes after history adjustment: %1", uploadable_bytes); - - // Client progress versions are set to 0 to signal to the server that we've - // reset our versioning. If we send the actual values, the server would - // complain that the versions (probably) don't correspond to the ones sent - // when downloading the fresh realm. - root.set(s_progress_download_client_version_iip, - RefOrTagged::make_tagged(0)); // Throws - root.set(s_progress_upload_client_version_iip, - RefOrTagged::make_tagged(0)); // Throws - - root.set(s_client_file_ident_salt_iip, - RefOrTagged::make_tagged(client_file_ident.salt)); // Throws - root.set(s_progress_download_server_version_iip, - RefOrTagged::make_tagged(server_version.version)); // Throws - root.set(s_progress_latest_server_version_iip, - RefOrTagged::make_tagged(server_version.version)); // Throws - root.set(s_progress_latest_server_version_salt_iip, - RefOrTagged::make_tagged(server_version.salt)); // Throws - root.set(s_progress_upload_server_version_iip, - RefOrTagged::make_tagged(server_version.version)); // Throws - root.set(s_progress_downloaded_bytes_iip, - RefOrTagged::make_tagged(0)); // Throws - root.set(s_progress_downloadable_bytes_iip, - RefOrTagged::make_tagged(0)); // Throws - root.set(s_progress_uploaded_bytes_iip, - RefOrTagged::make_tagged(0)); // Throws - root.set(s_progress_uploadable_bytes_iip, - RefOrTagged::make_tagged(uploadable_bytes)); // Throws - - m_progress_download = {server_version.version, 0}; - m_applying_client_reset = true; -} - -std::vector ClientHistory::get_local_changes(version_type current_version) const -{ - ensure_updated(current_version); // Throws - std::vector changesets; - if (!m_arrays || m_arrays->changesets.is_empty()) - return changesets; - - sync::version_type begin_version = 0; - { - sync::version_type local_version; - SaltedFileIdent local_ident; - SyncProgress local_progress; - get_status(local_version, local_ident, local_progress); - begin_version = local_progress.upload.client_version; - } - - version_type end_version = m_sync_history_base_version + sync_history_size(); - if (begin_version < m_sync_history_base_version) - begin_version = m_sync_history_base_version; - - for (version_type version = begin_version; version < end_version; ++version) { - std::size_t ndx = std::size_t(version - m_sync_history_base_version); - std::int_fast64_t origin_file_ident = m_arrays->origin_file_idents.get(ndx); - bool not_from_server = (origin_file_ident == 0); - if (not_from_server) { - bool compressed = false; - // find_sync_history_entry() returns 0 to indicate not found and - // otherwise adds 1 to the version, and then get_reciprocal_transform() - // subtracts 1 from the version - if (auto changeset = get_reciprocal_transform(version + 1, compressed); !changeset.empty()) { - changesets.push_back({version, changeset}); - } - } - } - return changesets; -} - -void ClientHistory::set_local_origin_timestamp_source(util::UniqueFunction source_fn) -{ - m_local_origin_timestamp_source = std::move(source_fn); -} - -// Overriding member function in realm::Replication -void ClientReplication::initialize(DB& sg) -{ - SyncReplication::initialize(sg); // Throws - m_history.initialize(sg); -} - - -// Overriding member function in realm::Replication -auto ClientReplication::get_history_type() const noexcept -> HistoryType -{ - return hist_SyncClient; -} - - -// Overriding member function in realm::Replication -int ClientReplication::get_history_schema_version() const noexcept -{ - return get_client_history_schema_version(); -} - - -// Overriding member function in realm::Replication -bool ClientReplication::is_upgradable_history_schema(int stored_schema_version) const noexcept -{ - if (stored_schema_version == 11) { - return true; - } - return false; -} - - -// Overriding member function in realm::Replication -void ClientReplication::upgrade_history_schema(int stored_schema_version) -{ - // upgrade_history_schema() is called only when there is a need to upgrade - // (`stored_schema_version < get_server_history_schema_version()`), and only - // when is_upgradable_history_schema() returned true (`stored_schema_version - // >= 1`). - REALM_ASSERT(stored_schema_version < get_client_history_schema_version()); - REALM_ASSERT(stored_schema_version >= 11); - int orig_schema_version = stored_schema_version; - int schema_version = orig_schema_version; - - if (schema_version < 12) { - m_history.compress_stored_changesets(); - schema_version = 12; - } - - // NOTE: Future migration steps go here. - - REALM_ASSERT(schema_version == get_client_history_schema_version()); - - // Record migration event - m_history.record_current_schema_version(); // Throws -} - -void ClientHistory::compress_stored_changesets() -{ - using gf = _impl::GroupFriend; - Allocator& alloc = gf::get_alloc(*m_group); - auto ref = gf::get_history_ref(*m_group); - Arrays arrays{alloc, m_group, ref}; - - util::AppendBuffer compressed_buffer; - util::AppendBuffer decompressed_buffer; - util::compression::CompressMemoryArena arena; - auto columns = {&arrays.reciprocal_transforms, &arrays.changesets}; - for (auto column : columns) { - for (size_t i = 0; i < column->size(); ++i) { - ChunkedBinaryData data(*column, i); - if (data.is_null()) - continue; - data.copy_to(compressed_buffer); - util::compression::allocate_and_compress_nonportable(arena, compressed_buffer, decompressed_buffer); - column->set(i, BinaryData{decompressed_buffer.data(), decompressed_buffer.size()}); // Throws - } - } -} - -// Overriding member function in realm::Replication -auto ClientReplication::prepare_changeset(const char* data, size_t size, version_type orig_version) -> version_type -{ - m_history.ensure_updated(orig_version); - m_history.prepare_for_write(); // Throws - - BinaryData ct_changeset{data, size}; - auto& buffer = get_instruction_encoder().buffer(); - BinaryData sync_changeset(buffer.data(), buffer.size()); - - return m_history.add_changeset(ct_changeset, sync_changeset); // Throws -} - -util::UniqueFunction ClientReplication::make_write_validator(Transaction& tr) -{ - if (!m_write_validator_factory) { - return {}; - } - - return m_write_validator_factory(tr); -} - -void ClientHistory::get_status(version_type& current_client_version, SaltedFileIdent& client_file_ident, - SyncProgress& progress) const -{ - TransactionRef rt = m_db->start_read(); // Throws - version_type current_client_version_2 = rt->get_version(); - - SaltedFileIdent client_file_ident_2{rt->get_sync_file_id(), 0}; - SyncProgress progress_2; - using gf = _impl::GroupFriend; - if (ref_type ref = gf::get_history_ref(*rt)) { - Array root(m_db->get_alloc()); - root.init_from_ref(ref); - client_file_ident_2.salt = salt_type(root.get_as_ref_or_tagged(s_client_file_ident_salt_iip).get_as_int()); - progress_2.latest_server_version.version = - version_type(root.get_as_ref_or_tagged(s_progress_latest_server_version_iip).get_as_int()); - progress_2.latest_server_version.salt = - version_type(root.get_as_ref_or_tagged(s_progress_latest_server_version_salt_iip).get_as_int()); - progress_2.download.server_version = - version_type(root.get_as_ref_or_tagged(s_progress_download_server_version_iip).get_as_int()); - progress_2.download.last_integrated_client_version = - version_type(root.get_as_ref_or_tagged(s_progress_download_client_version_iip).get_as_int()); - progress_2.upload.client_version = - version_type(root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int()); - progress_2.upload.last_integrated_server_version = - version_type(root.get_as_ref_or_tagged(s_progress_upload_server_version_iip).get_as_int()); - } - - current_client_version = current_client_version_2; - client_file_ident = client_file_ident_2; - progress = progress_2; - - REALM_ASSERT(current_client_version >= s_initial_version + 0); - if (current_client_version == s_initial_version + 0) - current_client_version = 0; -} - -void ClientHistory::set_client_file_ident(SaltedFileIdent client_file_ident, bool fix_up_object_ids) -{ - REALM_ASSERT(client_file_ident.ident != 0); - - TransactionRef wt = m_db->start_write(); // Throws - version_type local_version = wt->get_version(); - ensure_updated(local_version); // Throws - prepare_for_write(); // Throws - - Array& root = m_arrays->root; - REALM_ASSERT(wt->get_sync_file_id() == 0); - wt->set_sync_file_id(client_file_ident.ident); - root.set(s_client_file_ident_salt_iip, - RefOrTagged::make_tagged(client_file_ident.salt)); // Throws - root.set(s_progress_download_client_version_iip, RefOrTagged::make_tagged(0)); - root.set(s_progress_upload_client_version_iip, RefOrTagged::make_tagged(0)); - - if (fix_up_object_ids) { - fix_up_client_file_ident_in_stored_changesets(*wt, client_file_ident.ident); // Throws - } - - // Note: This transaction produces an empty changeset. Empty changesets are - // not uploaded to the server. - wt->commit(); // Throws -} - - -// Overriding member function in realm::sync::ClientHistoryBase -void ClientHistory::set_sync_progress(const SyncProgress& progress, DownloadableProgress downloadable_bytes, - VersionInfo& version_info) -{ - TransactionRef wt = m_db->start_write(); // Throws - version_type local_version = wt->get_version(); - ensure_updated(local_version); // Throws - prepare_for_write(); // Throws - - update_sync_progress(progress, downloadable_bytes); // Throws - - // Note: This transaction produces an empty changeset. Empty changesets are - // not uploaded to the server. - version_type new_version = wt->commit(); // Throws - version_info.realm_version = new_version; - version_info.sync_version = {new_version, 0}; -} - -void ClientHistory::find_uploadable_changesets(UploadCursor& upload_progress, version_type end_version, - std::vector& uploadable_changesets, - version_type& locked_server_version) const -{ - TransactionRef rt = m_db->start_read(); // Throws - auto& alloc = m_db->get_alloc(); - using gf = _impl::GroupFriend; - ref_type ref = gf::get_history_ref(*rt); - REALM_ASSERT(ref); - - Arrays arrays(alloc, rt.get(), ref); - const auto sync_history_size = arrays.changesets.size(); - const auto sync_history_base_version = rt->get_version() - sync_history_size; - - std::size_t accum_byte_size_soft_limit = 131072; // 128 KB - std::size_t accum_byte_size_hard_limit = 16777216; // server-imposed limit - std::size_t accum_byte_size = 0; - - version_type begin_version_2 = std::max(upload_progress.client_version, sync_history_base_version); - version_type end_version_2 = std::max(end_version, sync_history_base_version); - version_type last_integrated_upstream_version = upload_progress.last_integrated_server_version; - - while (accum_byte_size < accum_byte_size_soft_limit) { - HistoryEntry entry; - version_type last_integrated_upstream_version_2 = last_integrated_upstream_version; - version_type version = find_sync_history_entry(arrays, sync_history_base_version, begin_version_2, - end_version_2, entry, last_integrated_upstream_version_2); - - if (version == 0) { - begin_version_2 = end_version_2; - last_integrated_upstream_version = last_integrated_upstream_version_2; - break; - } - - ChunkedBinaryInputStream is(entry.changeset); - size_t size = util::compression::get_uncompressed_size_from_header(is); - if (accum_byte_size + size >= accum_byte_size_hard_limit && !uploadable_changesets.empty()) - break; - accum_byte_size += size; - last_integrated_upstream_version = last_integrated_upstream_version_2; - begin_version_2 = version; - - UploadChangeset uc; - util::AppendBuffer decompressed; - ChunkedBinaryInputStream is_2(entry.changeset); - auto ec = util::compression::decompress_nonportable(is_2, decompressed); - if (ec == util::compression::error::decompress_unsupported) { - REALM_TERMINATE( - "Synchronized Realm files with unuploaded local changes cannot be copied between platforms."); - } - REALM_ASSERT_3(ec, ==, std::error_code{}); - - uc.origin_timestamp = entry.origin_timestamp; - uc.origin_file_ident = entry.origin_file_ident; - uc.progress = UploadCursor{version, entry.remote_version}; - uc.changeset = BinaryData{decompressed.data(), decompressed.size()}; - uc.buffer = decompressed.release().release(); - uploadable_changesets.push_back(std::move(uc)); // Throws - } - - upload_progress = {std::min(begin_version_2, end_version), last_integrated_upstream_version}; - - locked_server_version = arrays.root.get_as_ref_or_tagged(s_progress_download_server_version_iip).get_as_int(); -} - - -void ClientHistory::integrate_server_changesets( - const SyncProgress& progress, DownloadableProgress downloadable_bytes, - util::Span incoming_changesets, VersionInfo& version_info, DownloadBatchState batch_state, - util::Logger& logger, const TransactionRef& transact, - util::UniqueFunction)> run_in_write_tr) -{ - REALM_ASSERT(incoming_changesets.size() != 0); - REALM_ASSERT( - (transact->get_transact_stage() == DB::transact_Writing && batch_state != DownloadBatchState::SteadyState) || - (transact->get_transact_stage() == DB::transact_Reading && batch_state == DownloadBatchState::SteadyState)); - std::vector changesets; - changesets.resize(incoming_changesets.size()); // Throws - - // Parse incoming changesets without holding the write lock unless 'transact' is specified. - try { - for (std::size_t i = 0; i < incoming_changesets.size(); ++i) { - const RemoteChangeset& changeset = incoming_changesets[i]; - parse_remote_changeset(changeset, changesets[i]); // Throws - changesets[i].transform_sequence = i; - } - } - catch (const BadChangesetError& e) { - throw IntegrationException(ErrorCodes::BadChangeset, - util::format("Failed to parse received changeset: %1", e.what()), - ProtocolError::bad_changeset); - } - - VersionID new_version{0, 0}; - auto num_changesets = incoming_changesets.size(); - util::Span changesets_to_integrate(changesets); - const bool allow_lock_release = batch_state == DownloadBatchState::SteadyState; - - // Ideally, this loop runs only once, but it can run up to `incoming_changesets.size()` times, depending on the - // number of times the sync client yields the write lock to allow the user to commit their changes. - // In each iteration, at least one changeset is transformed and committed. - // In FLX, all changesets are committed at once in the bootstrap phase (i.e, in one iteration). - while (!changesets_to_integrate.empty()) { - if (transact->get_transact_stage() == DB::transact_Reading) { - transact->promote_to_write(); // Throws - } - VersionID old_version = transact->get_version_of_current_transaction(); - version_type local_version = old_version.version; - auto sync_file_id = transact->get_sync_file_id(); - REALM_ASSERT(sync_file_id != 0); - - ensure_updated(local_version); // Throws - prepare_for_write(); // Throws - - std::uint64_t downloaded_bytes_in_transaction = 0; - auto changesets_transformed_count = transform_and_apply_server_changesets( - changesets_to_integrate, transact, logger, downloaded_bytes_in_transaction, allow_lock_release); - - // downloaded_bytes always contains the total number of downloaded bytes - // from the Realm. downloaded_bytes must be persisted in the Realm, since - // the downloaded changesets are trimmed after use, and since it would be - // expensive to traverse the entire history. - Array& root = m_arrays->root; - auto downloaded_bytes = - std::uint64_t(root.get_as_ref_or_tagged(s_progress_downloaded_bytes_iip).get_as_int()); - downloaded_bytes += downloaded_bytes_in_transaction; - root.set(s_progress_downloaded_bytes_iip, RefOrTagged::make_tagged(downloaded_bytes)); // Throws - - const RemoteChangeset& last_changeset = incoming_changesets[changesets_transformed_count - 1]; - auto changesets_for_cb = changesets_to_integrate.first(changesets_transformed_count); - changesets_to_integrate = changesets_to_integrate.sub_span(changesets_transformed_count); - incoming_changesets = incoming_changesets.sub_span(changesets_transformed_count); - - // During the bootstrap phase in flexible sync, the server sends multiple download messages with the same - // synthetic server version that represents synthetic changesets generated from state on the server. - if (batch_state == DownloadBatchState::LastInBatch && changesets_to_integrate.empty()) { - update_sync_progress(progress, downloadable_bytes); // Throws - } - // Always update progress for download messages from steady state. - else if (batch_state == DownloadBatchState::SteadyState && !changesets_to_integrate.empty()) { - auto partial_progress = progress; - partial_progress.download.server_version = last_changeset.remote_version; - partial_progress.download.last_integrated_client_version = last_changeset.last_integrated_local_version; - update_sync_progress(partial_progress, downloadable_bytes); // Throws - } - else if (batch_state == DownloadBatchState::SteadyState && changesets_to_integrate.empty()) { - update_sync_progress(progress, downloadable_bytes); // Throws - } - if (run_in_write_tr) { - run_in_write_tr(*transact, changesets_for_cb); - } - - // The reason we can use the `origin_timestamp`, and the `origin_file_ident` - // from the last transformed changeset, and ignore all the other changesets, is - // that these values are actually irrelevant for changesets of remote origin - // stored in the client-side history (for now), except that - // `origin_file_ident` is required to be nonzero, to mark it as having been - // received from the server. - HistoryEntry entry; - entry.origin_timestamp = last_changeset.origin_timestamp; - entry.origin_file_ident = last_changeset.origin_file_ident; - entry.remote_version = last_changeset.remote_version; - add_sync_history_entry(entry); // Throws - - // Tell prepare_commit()/add_changeset() not to write a history entry for - // this transaction as we already did it. - REALM_ASSERT(!m_applying_server_changeset); - m_applying_server_changeset = true; - // Commit and continue to write if in bootstrap phase and there are still changes to integrate. - if (batch_state == DownloadBatchState::MoreToCome || - (batch_state == DownloadBatchState::LastInBatch && !changesets_to_integrate.empty())) { - new_version = transact->commit_and_continue_writing(); // Throws - } - else { - new_version = transact->commit_and_continue_as_read(); // Throws - } - - logger.debug(util::LogCategory::changeset, "Integrated %1 changesets out of %2", changesets_transformed_count, - num_changesets); - } - - REALM_ASSERT(new_version.version > 0); - REALM_ASSERT( - (batch_state == DownloadBatchState::MoreToCome && transact->get_transact_stage() == DB::transact_Writing) || - (batch_state != DownloadBatchState::MoreToCome && transact->get_transact_stage() == DB::transact_Reading)); - version_info.realm_version = new_version.version; - version_info.sync_version = {new_version.version, 0}; -} - - -size_t ClientHistory::transform_and_apply_server_changesets(util::Span changesets_to_integrate, - TransactionRef transact, util::Logger& logger, - std::uint64_t& downloaded_bytes, bool allow_lock_release) -{ - REALM_ASSERT(transact->get_transact_stage() == DB::transact_Writing); - - if (!m_replication.apply_server_changes()) { - std::for_each(changesets_to_integrate.begin(), changesets_to_integrate.end(), [&](const Changeset& c) { - downloaded_bytes += c.original_changeset_size; - }); - // Skip over all changesets if they don't need to be transformed and applied. - return changesets_to_integrate.size(); - } - - version_type local_version = transact->get_version_of_current_transaction().version; - auto sync_file_id = transact->get_sync_file_id(); - - try { - for (auto& changeset : changesets_to_integrate) { - REALM_ASSERT(changeset.last_integrated_remote_version <= local_version); - REALM_ASSERT(changeset.origin_file_ident > 0 && changeset.origin_file_ident != sync_file_id); - - // It is possible that the synchronization history has been trimmed - // to a point where a prefix of the merge window is no longer - // available, but this can only happen if that prefix consisted - // entirely of upload skippable entries. Since such entries (those - // that are empty or of remote origin) will be skipped by the - // transformer anyway, we can simply clamp the beginning of the - // merge window to the beginning of the synchronization history, - // when this situation occurs. - // - // See trim_sync_history() for further details. - if (changeset.last_integrated_remote_version < m_sync_history_base_version) - changeset.last_integrated_remote_version = m_sync_history_base_version; - } - - constexpr std::size_t commit_byte_size_limit = 102400; // 100 KB - - auto changeset_applier = [&](const Changeset* transformed_changeset) -> bool { - InstructionApplier applier{*transact}; - { - TempShortCircuitReplication tscr{m_replication}; - applier.apply(*transformed_changeset); // Throws - } - downloaded_bytes += transformed_changeset->original_changeset_size; - - return !(allow_lock_release && m_db->other_writers_waiting_for_lock() && - transact->get_commit_size() >= commit_byte_size_limit); - }; - sync::Transformer transformer; - auto changesets_transformed_count = transformer.transform_remote_changesets( - *this, sync_file_id, local_version, changesets_to_integrate, changeset_applier, logger); // Throws - return changesets_transformed_count; - } - catch (const BadChangesetError& e) { - throw IntegrationException(ErrorCodes::BadChangeset, - util::format("Failed to apply received changeset: %1", e.what()), - ProtocolError::bad_changeset); - } - catch (const TransformError& e) { - throw IntegrationException(ErrorCodes::BadChangeset, - util::format("Failed to transform received changeset: %1", e.what()), - ProtocolError::bad_changeset); - } -} - - -void ClientHistory::get_upload_download_state(Transaction& rt, Allocator& alloc, std::uint_fast64_t& downloaded_bytes, - DownloadableProgress& downloadable_bytes, - std::uint_fast64_t& uploaded_bytes, - std::uint_fast64_t& uploadable_bytes, - std::uint_fast64_t& snapshot_version, version_type& uploaded_version) -{ - version_type current_client_version = rt.get_version(); - - downloaded_bytes = 0; - downloadable_bytes = uint64_t(0); - uploaded_bytes = 0; - uploadable_bytes = 0; - snapshot_version = current_client_version; - uploaded_version = 0; - - using gf = _impl::GroupFriend; - ref_type ref = gf::get_history_ref(rt); - if (!ref) - return; - - Array root(alloc); - root.init_from_ref(ref); - downloaded_bytes = root.get_as_ref_or_tagged(s_progress_downloaded_bytes_iip).get_as_int(); - downloadable_bytes = root.get_as_ref_or_tagged(s_progress_downloadable_bytes_iip).get_as_int(); - uploadable_bytes = root.get_as_ref_or_tagged(s_progress_uploadable_bytes_iip).get_as_int(); - uploaded_bytes = root.get_as_ref_or_tagged(s_progress_uploaded_bytes_iip).get_as_int(); - - uploaded_version = root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int(); - if (uploaded_version == current_client_version) - return; - - BinaryColumn changesets(alloc); - changesets.init_from_ref(root.get_as_ref(s_changesets_iip)); - IntegerBpTree origin_file_idents(alloc); - origin_file_idents.init_from_ref(root.get_as_ref(s_origin_file_idents_iip)); - - // `base_version` is the oldest version we have history for. If this is - // greater than uploaded_version, all of the versions in between the two had - // empty changesets and did not need to be uploaded. If this is less than - // uploaded_version, we have changesets which have been uploaded but the - // server has not yet told us we can delete and we may need to use for merging. - auto base_version = current_client_version - changesets.size(); - if (uploaded_version < base_version) { - uploaded_version = base_version; - } - - auto count = size_t(current_client_version - uploaded_version); - for (size_t i = changesets.size() - count; i < changesets.size(); ++i) { - if (origin_file_idents.get(i) == 0) { - size_t pos = 0; - if (changesets.get_at(i, pos).size() != 0) - break; - } - ++uploaded_version; - } -} - -void ClientHistory::get_upload_download_state(DB* db, std::uint_fast64_t& downloaded_bytes, - std::uint_fast64_t& uploaded_bytes) -{ - TransactionRef rt = db->start_read(); // Throws - downloaded_bytes = 0; - uploaded_bytes = 0; - - using gf = _impl::GroupFriend; - if (ref_type ref = gf::get_history_ref(*rt)) { - Array root(db->get_alloc()); - root.init_from_ref(ref); - downloaded_bytes = root.get_as_ref_or_tagged(s_progress_downloaded_bytes_iip).get_as_int(); - uploaded_bytes = root.get_as_ref_or_tagged(s_progress_uploaded_bytes_iip).get_as_int(); - } -} - -auto ClientHistory::find_history_entry(version_type begin_version, version_type end_version, - HistoryEntry& entry) const noexcept -> version_type -{ - version_type last_integrated_server_version; - return find_sync_history_entry(*m_arrays, m_sync_history_base_version, begin_version, end_version, entry, - last_integrated_server_version); -} - - -ChunkedBinaryData ClientHistory::get_reciprocal_transform(version_type version, bool& is_compressed) const -{ - is_compressed = true; - REALM_ASSERT(version > m_sync_history_base_version); - - std::size_t index = to_size_t(version - m_sync_history_base_version) - 1; - REALM_ASSERT(index < sync_history_size()); - - ChunkedBinaryData reciprocal{m_arrays->reciprocal_transforms, index}; - if (!reciprocal.is_null()) - return reciprocal; - return ChunkedBinaryData{m_arrays->changesets, index}; -} - - -void ClientHistory::set_reciprocal_transform(version_type version, BinaryData data) -{ - REALM_ASSERT(version > m_sync_history_base_version); - - std::size_t index = size_t(version - m_sync_history_base_version) - 1; - REALM_ASSERT(index < sync_history_size()); - - if (data.size() == 0) { - m_arrays->reciprocal_transforms.set(index, BinaryData{"", 0}); // Throws - return; - } - - auto compressed = util::compression::allocate_and_compress_nonportable(data); - m_arrays->reciprocal_transforms.set(index, BinaryData{compressed.data(), compressed.size()}); // Throws -} - - -auto ClientHistory::find_sync_history_entry(Arrays& arrays, version_type base_version, version_type begin_version, - version_type end_version, HistoryEntry& entry, - version_type& last_integrated_server_version) noexcept -> version_type -{ - if (begin_version == 0) - begin_version = s_initial_version + 0; - - REALM_ASSERT(begin_version <= end_version); - REALM_ASSERT(begin_version >= base_version); - REALM_ASSERT(end_version <= base_version + arrays.changesets.size()); - std::size_t n = to_size_t(end_version - begin_version); - std::size_t offset = to_size_t(begin_version - base_version); - for (std::size_t i = 0; i < n; ++i) { - std::int_fast64_t origin_file_ident = arrays.origin_file_idents.get(offset + i); - last_integrated_server_version = version_type(arrays.remote_versions.get(offset + i)); - bool not_from_server = (origin_file_ident == 0); - if (not_from_server) { - ChunkedBinaryData chunked_changeset(arrays.changesets, offset + i); - if (!chunked_changeset.empty()) { - entry.origin_file_ident = file_ident_type(origin_file_ident); - entry.remote_version = last_integrated_server_version; - entry.origin_timestamp = timestamp_type(arrays.origin_timestamps.get(offset + i)); - entry.changeset = chunked_changeset; - return begin_version + i + 1; - } - } - } - return 0; -} - -// sum_of_history_entry_sizes calculates the sum of the changeset sizes of the -// local history entries that produced a version that succeeds `begin_version` -// and precedes `end_version`. -std::uint_fast64_t ClientHistory::sum_of_history_entry_sizes(version_type begin_version, - version_type end_version) const noexcept -{ - if (begin_version >= end_version) - return 0; - - REALM_ASSERT(m_arrays->changesets.is_attached()); - REALM_ASSERT(m_arrays->origin_file_idents.is_attached()); - REALM_ASSERT(end_version <= m_sync_history_base_version + sync_history_size()); - - version_type begin_version_2 = begin_version; - version_type end_version_2 = end_version; - clamp_sync_version_range(begin_version_2, end_version_2); - - std::uint_fast64_t sum_of_sizes = 0; - - std::size_t n = to_size_t(end_version_2 - begin_version_2); - std::size_t offset = to_size_t(begin_version_2 - m_sync_history_base_version); - for (std::size_t i = 0; i < n; ++i) { - - // Only local changesets are considered - if (m_arrays->origin_file_idents.get(offset + i) != 0) - continue; - - ChunkedBinaryData changeset(m_arrays->changesets, offset + i); - ChunkedBinaryInputStream in{changeset}; - sum_of_sizes += util::compression::get_uncompressed_size_from_header(in); - } - - return sum_of_sizes; -} - -void ClientHistory::prepare_for_write() -{ - if (m_arrays) { - REALM_ASSERT(m_arrays->root.size() == s_root_size); - return; - } - - m_arrays.emplace(*m_db, *m_group); -} - - -Replication::version_type ClientHistory::add_changeset(BinaryData ct_changeset, BinaryData sync_changeset) -{ - // FIXME: BinaryColumn::set() currently interprets BinaryData(0,0) as - // null. It should probably be changed such that BinaryData(0,0) is always - // interpreted as the empty string. For the purpose of setting null values, - // BinaryColumn::set() should accept values of type Optional(). - if (ct_changeset.is_null()) - ct_changeset = BinaryData("", 0); - m_arrays->ct_history.add(ct_changeset); // Throws - - REALM_ASSERT(!m_applying_server_changeset || !m_applying_client_reset); - - // If we're applying a changeset from the server then we should have already - // added the history entry and don't need to do so here - if (m_applying_server_changeset) { - // We need to unset this before committing the write, as it's guarded - // by the write lock - m_applying_server_changeset = false; - REALM_ASSERT(m_ct_history_base_version + ct_history_size() == - m_sync_history_base_version + sync_history_size()); - REALM_ASSERT(sync_changeset.size() == 0); - return m_ct_history_base_version + ct_history_size(); - } - - // We don't generate a changeset for any of the changes made as part of - // applying a client reset as those changes are just bringing us into - // alignment with the new server state - if (m_applying_client_reset) { - m_applying_client_reset = false; - sync_changeset = {}; - } - - HistoryEntry entry; - entry.origin_timestamp = m_local_origin_timestamp_source(); - entry.origin_file_ident = 0; // Of local origin - entry.remote_version = m_progress_download.server_version; - entry.changeset = sync_changeset; - add_sync_history_entry(entry); // Throws - - // uploadable_bytes is updated at every local Realm change. The total - // number of uploadable bytes must be persisted in the Realm, since the - // synchronization history is trimmed. Even if the synchronization - // history wasn't trimmed, it would be expensive to traverse the entire - // history at every access to uploadable bytes. - Array& root = m_arrays->root; - std::uint_fast64_t uploadable_bytes = root.get_as_ref_or_tagged(s_progress_uploadable_bytes_iip).get_as_int(); - uploadable_bytes += entry.changeset.size(); - root.set(s_progress_uploadable_bytes_iip, RefOrTagged::make_tagged(uploadable_bytes)); - - return m_ct_history_base_version + ct_history_size(); -} - -void ClientHistory::add_sync_history_entry(const HistoryEntry& entry) -{ - REALM_ASSERT(m_arrays->changesets.size() == sync_history_size()); - REALM_ASSERT(m_arrays->reciprocal_transforms.size() == sync_history_size()); - REALM_ASSERT(m_arrays->remote_versions.size() == sync_history_size()); - REALM_ASSERT(m_arrays->origin_file_idents.size() == sync_history_size()); - REALM_ASSERT(m_arrays->origin_timestamps.size() == sync_history_size()); - - if (!entry.changeset.is_null()) { - auto changeset = entry.changeset.get_first_chunk(); - auto compressed = util::compression::allocate_and_compress_nonportable(changeset); - m_arrays->changesets.add(BinaryData{compressed.data(), compressed.size()}); // Throws - } - else { - m_arrays->changesets.add(BinaryData("", 0)); // Throws - } - - m_arrays->reciprocal_transforms.add(BinaryData{}); // Throws - m_arrays->remote_versions.insert(realm::npos, std::int_fast64_t(entry.remote_version)); // Throws - m_arrays->origin_file_idents.insert(realm::npos, std::int_fast64_t(entry.origin_file_ident)); // Throws - m_arrays->origin_timestamps.insert(realm::npos, std::int_fast64_t(entry.origin_timestamp)); // Throws -} - - -void ClientHistory::update_sync_progress(const SyncProgress& progress, DownloadableProgress downloadable_bytes) -{ - Array& root = m_arrays->root; - - // Progress must never decrease - if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_latest_server_version_iip).get_as_int()); - progress.latest_server_version.version < current) { - throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, - util::format("latest server version cannot decrease (current: %1, new: %2)", - current, progress.latest_server_version.version), - ProtocolError::bad_progress); - } - if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_download_server_version_iip).get_as_int()); - progress.download.server_version < current) { - throw IntegrationException( - ErrorCodes::SyncProtocolInvariantFailed, - util::format("server version of download cursor cannot decrease (current: %1, new: %2)", current, - progress.download.server_version), - ProtocolError::bad_progress); - } - if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_download_client_version_iip).get_as_int()); - progress.download.last_integrated_client_version < current) { - throw IntegrationException( - ErrorCodes::SyncProtocolInvariantFailed, - util::format("last integrated client version of download cursor cannot decrease (current: %1, new: %2)", - current, progress.download.last_integrated_client_version), - ProtocolError::bad_progress); - } - if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int()); - progress.upload.client_version < current) { - throw IntegrationException( - ErrorCodes::SyncProtocolInvariantFailed, - util::format("client version of upload cursor cannot decrease (current: %1, new: %2)", current, - progress.upload.client_version), - ProtocolError::bad_progress); - } - const auto last_integrated_server_version = progress.upload.last_integrated_server_version; - if (auto current = version_type(root.get_as_ref_or_tagged(s_progress_upload_server_version_iip).get_as_int()); - last_integrated_server_version > 0 && last_integrated_server_version < current) { - throw IntegrationException( - ErrorCodes::SyncProtocolInvariantFailed, - util::format("last integrated server version of upload cursor cannot decrease (current: %1, new: %2)", - current, last_integrated_server_version), - ProtocolError::bad_progress); - } - - auto uploaded_bytes = std::uint_fast64_t(root.get_as_ref_or_tagged(s_progress_uploaded_bytes_iip).get_as_int()); - auto previous_upload_client_version = - version_type(root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int()); - uploaded_bytes += sum_of_history_entry_sizes(previous_upload_client_version, progress.upload.client_version); - - root.set(s_progress_download_server_version_iip, - RefOrTagged::make_tagged(progress.download.server_version)); // Throws - root.set(s_progress_download_client_version_iip, - RefOrTagged::make_tagged(progress.download.last_integrated_client_version)); // Throws - root.set(s_progress_latest_server_version_iip, - RefOrTagged::make_tagged(progress.latest_server_version.version)); // Throws - root.set(s_progress_latest_server_version_salt_iip, - RefOrTagged::make_tagged(progress.latest_server_version.salt)); // Throws - root.set(s_progress_upload_client_version_iip, - RefOrTagged::make_tagged(progress.upload.client_version)); // Throws - if (progress.upload.last_integrated_server_version > 0) { - root.set(s_progress_upload_server_version_iip, - RefOrTagged::make_tagged(progress.upload.last_integrated_server_version)); // Throws - } - - root.set(s_progress_downloadable_bytes_iip, - RefOrTagged::make_tagged(downloadable_bytes.as_bytes())); // Throws - root.set(s_progress_uploaded_bytes_iip, - RefOrTagged::make_tagged(uploaded_bytes)); // Throws - - m_progress_download = progress.download; - - trim_sync_history(); // Throws -} - -void ClientHistory::set_download_progress(Transaction& tr, DownloadableProgress p) -{ - using gf = _impl::GroupFriend; - ref_type ref = gf::get_history_ref(tr); - REALM_ASSERT(ref); - Array root(gf::get_alloc(tr)); - root.init_from_ref(ref); - gf::set_history_parent(tr, root); - REALM_ASSERT(root.size() > s_progress_uploadable_bytes_iip); - root.set(s_progress_downloadable_bytes_iip, - RefOrTagged::make_tagged(p.as_bytes())); // Throws -} - -void ClientHistory::trim_ct_history() -{ - version_type begin = m_ct_history_base_version; - version_type end = m_version_of_oldest_bound_snapshot; - REALM_ASSERT(end >= begin); - - std::size_t n = std::size_t(end - begin); - if (n == 0) - return; - - // The new changeset is always added before set_oldest_bound_version() - // is called. Therefore, the trimming operation can never leave the - // history empty. - REALM_ASSERT(n < ct_history_size()); - - for (std::size_t i = 0; i < n; ++i) { - std::size_t j = (n - 1) - i; - m_arrays->ct_history.erase(j); - } - - m_ct_history_base_version += n; - - REALM_ASSERT(m_ct_history_base_version + ct_history_size() == m_sync_history_base_version + sync_history_size()); -} - - -// Trimming rules for synchronization history: -// -// Let C be the latest client version that was integrated on the server prior to -// the latest server version currently integrated by the client -// (`m_progress_download.last_integrated_client_version`). -// -// Definition: An *upload skippable history entry* is one whose changeset is -// either empty, or of remote origin. -// -// Then, a history entry, E, can be trimmed away if it precedes C, or E is -// upload skippable, and there are no upload nonskippable entries between C and -// E. -// -// Since the history representation is contiguous, it is necessary that the -// trimming rule upholds the following invariant: -// -// > If a changeset can be trimmed, then any earlier changeset can also be -// > trimmed. -// -// Note that C corresponds to the earliest possible beginning of the merge -// window for the next incoming changeset from the server. -void ClientHistory::trim_sync_history() -{ - version_type begin = m_sync_history_base_version; - version_type end = std::max(m_progress_download.last_integrated_client_version, s_initial_version + 0); - // Note: At this point, `end` corresponds to C in the description above. - - // `end` (`m_progress_download.last_integrated_client_version`) will precede - // the beginning of the history, if we trimmed beyond - // `m_progress_download.last_integrated_client_version` during the previous - // trimming session. Since new entries, that have now become eligible for - // scanning, may also be upload skippable, we need to continue the scan from - // the beginning of the history in that case. - if (end < begin) - end = begin; - - // FIXME: It seems like in some cases, a particular history entry that - // terminates the scan may get examined over and over every time - // trim_history() is called. For this reason, it seems like it would be - // worth considering to cache the outcome. - - // FIXME: It seems like there is a significant overlap between what is going - // on here and in a place like find_uploadable_changesets(). Maybe there is - // grounds for some refactoring to take that into account, especially, to - // avoid scanning the same parts of the history for the same information - // multiple times. - - { - std::size_t offset = std::size_t(end - begin); - std::size_t n = std::size_t(sync_history_size() - offset); - std::size_t i = 0; - while (i < n) { - std::int_fast64_t origin_file_ident = m_arrays->origin_file_idents.get(offset + i); - bool of_local_origin = (origin_file_ident == 0); - if (of_local_origin) { - std::size_t pos = 0; - BinaryData chunk = m_arrays->changesets.get_at(offset + i, pos); - bool nonempty = (chunk.size() > 0); - if (nonempty) - break; // Not upload skippable - } - ++i; - } - end += i; - } - - std::size_t n = std::size_t(end - begin); - do_trim_sync_history(n); // Throws -} - -bool ClientHistory::no_pending_local_changes(version_type version) const -{ - ensure_updated(version); - size_t base_version = 0; - auto upload_client_version = - version_type(m_arrays->root.get_as_ref_or_tagged(s_progress_upload_client_version_iip).get_as_int()); - if (upload_client_version > m_sync_history_base_version) - base_version = size_t(upload_client_version - m_sync_history_base_version); - for (size_t i = base_version; i < sync_history_size(); i++) { - if (m_arrays->origin_file_idents.get(i) == 0) { - std::size_t pos = 0; - BinaryData chunk = m_arrays->changesets.get_at(i, pos); - if (chunk.size() > 0) - return false; - } - } - return true; -} - -void ClientHistory::do_trim_sync_history(std::size_t n) -{ - REALM_ASSERT(m_arrays->changesets.size() == sync_history_size()); - REALM_ASSERT(m_arrays->reciprocal_transforms.size() == sync_history_size()); - REALM_ASSERT(m_arrays->remote_versions.size() == sync_history_size()); - REALM_ASSERT(m_arrays->origin_file_idents.size() == sync_history_size()); - REALM_ASSERT(m_arrays->origin_timestamps.size() == sync_history_size()); - REALM_ASSERT(n <= sync_history_size()); - - if (n == sync_history_size()) { - m_arrays->changesets.clear(); - m_arrays->reciprocal_transforms.clear(); - m_arrays->remote_versions.clear(); - m_arrays->origin_file_idents.clear(); - m_arrays->origin_timestamps.clear(); - } - else if (n > 0) { - for (std::size_t i = 0; i < n; ++i) { - std::size_t j = (n - 1) - i; - m_arrays->changesets.erase(j); // Throws - } - for (std::size_t i = 0; i < n; ++i) { - std::size_t j = (n - 1) - i; - m_arrays->reciprocal_transforms.erase(j); // Throws - } - for (std::size_t i = 0; i < n; ++i) { - std::size_t j = (n - 1) - i; - m_arrays->remote_versions.erase(j); // Throws - } - for (std::size_t i = 0; i < n; ++i) { - std::size_t j = (n - 1) - i; - m_arrays->origin_file_idents.erase(j); // Throws - } - for (std::size_t i = 0; i < n; ++i) { - std::size_t j = (n - 1) - i; - m_arrays->origin_timestamps.erase(j); // Throws - } - } - - m_sync_history_base_version += n; -} - -void ClientHistory::fix_up_client_file_ident_in_stored_changesets(Transaction& group, - file_ident_type client_file_ident) -{ - // Must be in write transaction! - - REALM_ASSERT(client_file_ident != 0); - auto promote_global_key = [client_file_ident](GlobalKey* oid) { - if (oid->hi() == 0) { - // client_file_ident == 0 - *oid = GlobalKey{uint64_t(client_file_ident), oid->lo()}; - return true; - } - return false; - }; - - Group::TableNameBuffer buffer; - auto get_table_for_class = [&](StringData class_name) -> ConstTableRef { - return group.get_table(Group::class_name_to_table_name(class_name, buffer)); - }; - - util::compression::CompressMemoryArena arena; - util::AppendBuffer compressed; - - // Fix up changesets. - Array& root = m_arrays->root; - uint64_t uploadable_bytes = root.get_as_ref_or_tagged(s_progress_uploadable_bytes_iip).get_as_int(); - for (size_t i = 0; i < sync_history_size(); ++i) { - // We could have opened a pre-provisioned realm file. In this case we can skip the entries downloaded - // from the server. - if (m_arrays->origin_file_idents.get(i) != 0) - continue; - - ChunkedBinaryData changeset{m_arrays->changesets, i}; - ChunkedBinaryInputStream is{changeset}; - size_t decompressed_size; - auto decompressed = util::compression::decompress_nonportable_input_stream(is, decompressed_size); - if (!decompressed) - continue; - Changeset log; - parse_changeset(*decompressed, log); - - bool did_modify = false; - auto last_class_name = InternString::npos; - ConstTableRef selected_table; - for (auto instr : log) { - if (!instr) - continue; - - if (auto obj_instr = instr->get_if()) { - // Cache the TableRef - if (obj_instr->table != last_class_name) { - StringData class_name = log.get_string(obj_instr->table); - last_class_name = obj_instr->table; - selected_table = get_table_for_class(class_name); - } - - // Fix up instructions using GlobalKey to identify objects. - if (auto global_key = mpark::get_if(&obj_instr->object)) { - did_modify = promote_global_key(global_key); - } - - // Fix up the payload for Set and ArrayInsert. - Instruction::Payload* payload = nullptr; - if (auto set_instr = instr->get_if()) { - payload = &set_instr->value; - } - else if (auto list_insert_instr = instr->get_if()) { - payload = &list_insert_instr->value; - } - - if (payload && payload->type == Instruction::Payload::Type::Link) { - if (auto global_key = mpark::get_if(&payload->data.link.target)) { - did_modify = promote_global_key(global_key); - } - } - } - } - - if (did_modify) { - ChangesetEncoder::Buffer modified; - encode_changeset(log, modified); - util::compression::allocate_and_compress_nonportable(arena, modified, compressed); - m_arrays->changesets.set(i, BinaryData{compressed.data(), compressed.size()}); // Throws - - uploadable_bytes += modified.size() - decompressed_size; - } - } - - root.set(s_progress_uploadable_bytes_iip, RefOrTagged::make_tagged(uploadable_bytes)); -} - -void ClientHistory::set_group(Group* group, bool updated) -{ - _impl::History::set_group(group, updated); - if (m_arrays) - _impl::GroupFriend::set_history_parent(*m_group, m_arrays->root); -} - -void ClientHistory::record_current_schema_version() -{ - using gf = _impl::GroupFriend; - Allocator& alloc = gf::get_alloc(*m_group); - auto ref = gf::get_history_ref(*m_group); - REALM_ASSERT(ref != 0); - Array root{alloc}; - gf::set_history_parent(*m_group, root); - root.init_from_ref(ref); - Array schema_versions{alloc}; - schema_versions.set_parent(&root, s_schema_versions_iip); - schema_versions.init_from_parent(); - version_type snapshot_version = m_db->get_version_of_latest_snapshot(); - record_current_schema_version(schema_versions, snapshot_version); // Throws -} - - -void ClientHistory::record_current_schema_version(Array& schema_versions, version_type snapshot_version) -{ - static_assert(s_schema_versions_size == 4, ""); - REALM_ASSERT(schema_versions.size() == s_schema_versions_size); - - Allocator& alloc = schema_versions.get_alloc(); - { - Array sv_schema_versions{alloc}; - sv_schema_versions.set_parent(&schema_versions, s_sv_schema_versions_iip); - sv_schema_versions.init_from_parent(); - int schema_version = get_client_history_schema_version(); - sv_schema_versions.add(schema_version); // Throws - } - { - Array sv_library_versions{alloc}; - sv_library_versions.set_parent(&schema_versions, s_sv_library_versions_iip); - sv_library_versions.init_from_parent(); - const char* library_version = REALM_VERSION_STRING; - std::size_t size = std::strlen(library_version); - Array value{alloc}; - bool context_flag = false; - value.create(Array::type_Normal, context_flag, size); // Throws - _impl::ShallowArrayDestroyGuard adg{&value}; - using uchar = unsigned char; - for (std::size_t i = 0; i < size; ++i) - value.set(i, std::int_fast64_t(uchar(library_version[i]))); // Throws - sv_library_versions.add(std::int_fast64_t(value.get_ref())); // Throws - adg.release(); // Ownership transferred to parent array - } - { - Array sv_snapshot_versions{alloc}; - sv_snapshot_versions.set_parent(&schema_versions, s_sv_snapshot_versions_iip); - sv_snapshot_versions.init_from_parent(); - sv_snapshot_versions.add(std::int_fast64_t(snapshot_version)); // Throws - } - { - Array sv_timestamps{alloc}; - sv_timestamps.set_parent(&schema_versions, s_sv_timestamps_iip); - sv_timestamps.init_from_parent(); - std::time_t timestamp = std::time(nullptr); - sv_timestamps.add(std::int_fast64_t(timestamp)); // Throws - } -} - -// Overriding member function in realm::_impl::History -void ClientHistory::update_from_ref_and_version(ref_type ref, version_type version) -{ - if (ref == 0) { - // No history - m_ct_history_base_version = version; - m_sync_history_base_version = version; - m_arrays.reset(); - m_progress_download = {0, 0}; - return; - } - if (REALM_LIKELY(m_arrays)) { - m_arrays->init_from_ref(ref); - } - else { - m_arrays.emplace(m_db->get_alloc(), m_group, ref); - } - - m_ct_history_base_version = version - ct_history_size(); - m_sync_history_base_version = version - sync_history_size(); - REALM_ASSERT(m_arrays->changesets.size() == sync_history_size()); - REALM_ASSERT(m_arrays->reciprocal_transforms.size() == sync_history_size()); - REALM_ASSERT(m_arrays->remote_versions.size() == sync_history_size()); - REALM_ASSERT(m_arrays->origin_file_idents.size() == sync_history_size()); - REALM_ASSERT(m_arrays->origin_timestamps.size() == sync_history_size()); - - const Array& root = m_arrays->root; - m_progress_download.server_version = - version_type(root.get_as_ref_or_tagged(s_progress_download_server_version_iip).get_as_int()); - m_progress_download.last_integrated_client_version = - version_type(root.get_as_ref_or_tagged(s_progress_download_client_version_iip).get_as_int()); -} - - -// Overriding member function in realm::_impl::History -void ClientHistory::update_from_parent(version_type current_version) -{ - using gf = _impl::GroupFriend; - ref_type ref = gf::get_history_ref(*m_group); - update_from_ref_and_version(ref, current_version); // Throws -} - - -// Overriding member function in realm::_impl::History -void ClientHistory::get_changesets(version_type begin_version, version_type end_version, - BinaryIterator* iterators) const noexcept -{ - REALM_ASSERT(begin_version <= end_version); - REALM_ASSERT(begin_version >= m_ct_history_base_version); - REALM_ASSERT(end_version <= m_ct_history_base_version + ct_history_size()); - std::size_t n = to_size_t(end_version - begin_version); - REALM_ASSERT(n == 0 || m_arrays); - std::size_t offset = to_size_t(begin_version - m_ct_history_base_version); - for (std::size_t i = 0; i < n; ++i) - iterators[i] = BinaryIterator(&m_arrays->ct_history, offset + i); -} - - -// Overriding member function in realm::_impl::History -void ClientHistory::set_oldest_bound_version(version_type version) -{ - REALM_ASSERT(version >= m_version_of_oldest_bound_snapshot); - if (version > m_version_of_oldest_bound_snapshot) { - m_version_of_oldest_bound_snapshot = version; - trim_ct_history(); // Throws - } -} - -// Overriding member function in realm::_impl::History -void ClientHistory::verify() const -{ -#ifdef REALM_DEBUG - // The size of the continuous transactions history can only be zero when the - // Realm is in the initial empty state where top-ref is null. - REALM_ASSERT(ct_history_size() != 0 || m_ct_history_base_version == s_initial_version + 0); - - if (!m_arrays) { - REALM_ASSERT(m_progress_download.server_version == 0); - REALM_ASSERT(m_progress_download.last_integrated_client_version == 0); - return; - } - m_arrays->verify(); - - auto& root = m_arrays->root; - version_type progress_download_server_version = - version_type(root.get_as_ref_or_tagged(s_progress_download_server_version_iip).get_as_int()); - version_type progress_download_client_version = - version_type(root.get_as_ref_or_tagged(s_progress_download_client_version_iip).get_as_int()); - REALM_ASSERT(progress_download_server_version == m_progress_download.server_version); - REALM_ASSERT(progress_download_client_version == m_progress_download.last_integrated_client_version); - REALM_ASSERT(progress_download_client_version <= m_sync_history_base_version + sync_history_size()); - version_type remote_version_of_last_entry = 0; - if (auto size = sync_history_size()) - remote_version_of_last_entry = m_arrays->remote_versions.get(size - 1); - REALM_ASSERT(progress_download_server_version >= remote_version_of_last_entry); - - // Verify that there is no cooked history. - Array cooked_history{m_db->get_alloc()}; - cooked_history.set_parent(&root, s_cooked_history_iip); - REALM_ASSERT(cooked_history.get_ref_from_parent() == 0); -#endif // REALM_DEBUG -} - -ClientHistory::Arrays::Arrays(Allocator& alloc) noexcept - : root(alloc) - , ct_history(alloc) - , changesets(alloc) - , reciprocal_transforms(alloc) - , remote_versions(alloc) - , origin_file_idents(alloc) - , origin_timestamps(alloc) -{ -} - -ClientHistory::Arrays::Arrays(DB& db, Group& group) - : Arrays(db.get_alloc()) -{ - auto& alloc = db.get_alloc(); - { - bool context_flag = false; - std::size_t size = s_root_size; - root.create(Array::type_HasRefs, context_flag, size); // Throws - } - _impl::DeepArrayDestroyGuard dg{&root}; - - ct_history.set_parent(&root, s_ct_history_iip); - ct_history.create(); // Throws - changesets.set_parent(&root, s_changesets_iip); - changesets.create(); // Throws - reciprocal_transforms.set_parent(&root, s_reciprocal_transforms_iip); - reciprocal_transforms.create(); // Throws - remote_versions.set_parent(&root, s_remote_versions_iip); - remote_versions.create(); // Throws - origin_file_idents.set_parent(&root, s_origin_file_idents_iip); - origin_file_idents.create(); // Throws - origin_timestamps.set_parent(&root, s_origin_timestamps_iip); - origin_timestamps.create(); // Throws - - { // `schema_versions` table - Array schema_versions{alloc}; - bool context_flag = false; - std::size_t size = s_schema_versions_size; - schema_versions.create(Array::type_HasRefs, context_flag, size); // Throws - _impl::DeepArrayDestroyGuard adg{&schema_versions}; - - auto create_array = [&](NodeHeader::Type type, int ndx_in_parent) { - MemRef mem = Array::create_empty_array(type, context_flag, alloc); - ref_type ref = mem.get_ref(); - _impl::DeepArrayRefDestroyGuard ardg{ref, alloc}; - schema_versions.set_as_ref(ndx_in_parent, ref); // Throws - ardg.release(); // Ownership transferred to parent array - }; - create_array(Array::type_Normal, s_sv_schema_versions_iip); - create_array(Array::type_HasRefs, s_sv_library_versions_iip); - create_array(Array::type_Normal, s_sv_snapshot_versions_iip); - create_array(Array::type_Normal, s_sv_timestamps_iip); - - version_type snapshot_version = db.get_version_of_latest_snapshot(); - record_current_schema_version(schema_versions, snapshot_version); // Throws - root.set_as_ref(s_schema_versions_iip, schema_versions.get_ref()); // Throws - adg.release(); // Ownership transferred to parent array - } - _impl::GroupFriend::prepare_history_parent(group, root, Replication::hist_SyncClient, - get_client_history_schema_version(), 0); // Throws - // Note: gf::prepare_history_parent() also ensures the the root array has a - // slot for the history ref. - root.update_parent(); // Throws - dg.release(); -} - -ClientHistory::Arrays::Arrays(Allocator& alloc, Group* parent, ref_type ref) - : Arrays(alloc) -{ - using gf = _impl::GroupFriend; - root.init_from_ref(ref); - if (parent) - gf::set_history_parent(*parent, root); - - ct_history.set_parent(&root, s_ct_history_iip); - changesets.set_parent(&root, s_changesets_iip); - reciprocal_transforms.set_parent(&root, s_reciprocal_transforms_iip); - remote_versions.set_parent(&root, s_remote_versions_iip); - origin_file_idents.set_parent(&root, s_origin_file_idents_iip); - origin_timestamps.set_parent(&root, s_origin_timestamps_iip); - - init_from_ref(ref); // Throws - - Array cooked_history{alloc}; - cooked_history.set_parent(&root, s_cooked_history_iip); - // We should have no cooked history in existing Realms. - REALM_ASSERT(cooked_history.get_ref_from_parent() == 0); -} - -void ClientHistory::Arrays::Arrays::init_from_ref(ref_type ref) -{ - root.init_from_ref(ref); - REALM_ASSERT(root.size() == s_root_size); - { - ref_type ref_2 = root.get_as_ref(s_ct_history_iip); - ct_history.init_from_ref(ref_2); // Throws - } - { - ref_type ref_2 = root.get_as_ref(s_changesets_iip); - changesets.init_from_ref(ref_2); // Throws - } - { - ref_type ref_2 = root.get_as_ref(s_reciprocal_transforms_iip); - reciprocal_transforms.init_from_ref(ref_2); // Throws - } - remote_versions.init_from_parent(); // Throws - origin_file_idents.init_from_parent(); // Throws - origin_timestamps.init_from_parent(); // Throws -} - -void ClientHistory::Arrays::verify() const -{ -#ifdef REALM_DEBUG - root.verify(); - ct_history.verify(); - changesets.verify(); - reciprocal_transforms.verify(); - remote_versions.verify(); - origin_file_idents.verify(); - origin_timestamps.verify(); - REALM_ASSERT(root.size() == s_root_size); - REALM_ASSERT(reciprocal_transforms.size() == changesets.size()); - REALM_ASSERT(remote_versions.size() == changesets.size()); - REALM_ASSERT(origin_file_idents.size() == changesets.size()); - REALM_ASSERT(origin_timestamps.size() == changesets.size()); -#endif // REALM_DEBUG -} - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/client_history_impl.hpp b/src/realm/sync/noinst/client_history_impl.hpp deleted file mode 100644 index b1f74a1641b..00000000000 --- a/src/realm/sync/noinst/client_history_impl.hpp +++ /dev/null @@ -1,561 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_NOINST_CLIENT_HISTORY_IMPL_HPP -#define REALM_NOINST_CLIENT_HISTORY_IMPL_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace realm::_impl::client_reset { -struct RecoveredChange; -} - -namespace realm::sync { - -class ClientReplication; -// As new schema versions come into existence, describe them here. -// -// 0 Initial version -// -// 1 Added support for stable IDs. -// -// 2 Now allowing continuous transactions history and synchronization history -// to be separately trimmed. -// -// Added a slot for `progress_upload_server_version` to the root array. -// -// Reordered slots in root array. -// -// Added a `schema_versions` table for the purpose of recording the creation -// of, and the migrations of the history compartment from one schema version -// to the next. -// -// Slots pertaining to cooked history were moved into subarray -// `cooked_history`. -// -// Added slots `base_server_version` and `server_versions` to -// `cooked_history` array. The former contains a server version, and the -// latter contains a ref to a column of server versions. -// -// 3..9 Reserved for Core-5 based sync -// -// 10 Stable IDs supported by core. -// -// 11 Path-based instruction format for MongoDB Realm Sync (v10) -// -// Cooked history was removed, except to verify that there is no cooked history. -// -// 12 History entries are compressed. - -constexpr int get_client_history_schema_version() noexcept -{ - return 12; -} - -class IntegrationException : public Exception { -public: - IntegrationException(ErrorCodes::Error error, std::string message, - ProtocolError error_for_server = ProtocolError::other_session_error) - : Exception(error, message) - , error_for_server(error_for_server) - { - } - - explicit IntegrationException(Status status) - : Exception(std::move(status)) - , error_for_server(ProtocolError::other_session_error) - { - } - - ProtocolError error_for_server; -}; - -class ClientHistory final : public _impl::History, public TransformHistory { -public: - using version_type = sync::version_type; - - struct UploadChangeset { - timestamp_type origin_timestamp; - file_ident_type origin_file_ident; - UploadCursor progress; - ChunkedBinaryData changeset; - std::unique_ptr buffer; - }; - - /// set_history_adjustments() is used by client reset to adjust the - /// content of the history compartment. The DB associated with - /// this history object must be in a write transaction when this function - /// is called. - void set_history_adjustments(util::Logger& logger, version_type current_version, - SaltedFileIdent client_file_ident, SaltedVersion server_version, - const std::vector<_impl::client_reset::RecoveredChange>&); - - struct LocalChange { - version_type version; - ChunkedBinaryData changeset; - }; - /// get_local_changes returns a list of changes which have not been uploaded yet - /// 'current_version' is the version that the history should be updated to. - /// - /// The history must be in a transaction when this function is called. - std::vector get_local_changes(version_type current_version) const; - - /// Get the version of the latest snapshot of the associated Realm, as well - /// as the client file identifier and the synchronization progress as they - /// are stored in that snapshot. - /// - /// The returned current client version is the version produced by the last - /// changeset in the history. The type of version returned here, is the one - /// that identifies an entry in the sync history. Whether this is the same - /// as the snapshot number of the Realm file depends on the history - /// implementation. - /// - /// The returned client file identifier is the one that was last stored by - /// set_client_file_ident(), or `SaltedFileIdent{0, 0}` if - /// set_client_file_ident() has never been called. - /// - /// The returned SyncProgress is the one that was last stored by - /// set_sync_progress(), or `SyncProgress{}` if set_sync_progress() has - /// never been called. - void get_status(version_type& current_client_version, SaltedFileIdent& client_file_ident, - SyncProgress& progress) const; - - /// Stores the server assigned client file identifier in the associated - /// Realm file, such that it is available via get_status() during future - /// synchronization sessions. It is an error to set this identifier more - /// than once per Realm file. - /// - /// \param client_file_ident The server assigned client-side file - /// identifier. A client-side file identifier is a non-zero positive integer - /// strictly less than 2**64. The server guarantees that all client-side - /// file identifiers generated on behalf of a particular server Realm are - /// unique with respect to each other. The server is free to generate - /// identical identifiers for two client files if they are associated with - /// different server Realms. - /// - /// \param fix_up_object_ids The object ids that depend on client file ident - /// will be fixed in both state and history if this parameter is true. If - /// it is known that there are no objects to fix, it can be set to false to - /// achieve higher performance. - /// - /// The client is required to obtain the file identifier before engaging in - /// synchronization proper, and it must store the identifier and use it to - /// reestablish the connection between the client file and the server file - /// when engaging in future synchronization sessions. - void set_client_file_ident(SaltedFileIdent client_file_ident, bool fix_up_object_ids); - - /// Stores the synchronization progress in the associated Realm file in a - /// way that makes it available via get_status() during future - /// synchronization sessions. Progress is reported by the server in the - /// DOWNLOAD message. - /// - /// See struct SyncProgress for a description of \a progress. - /// - /// \param downloadable_bytes If specified, and if the implementation cares - /// about byte-level progress, this function updates the persistent record - /// of the estimate of the number of remaining bytes to be downloaded. - void set_sync_progress(const SyncProgress& progress, DownloadableProgress downloadable_bytes, VersionInfo&); - - /// \brief Scan through the history for changesets to be uploaded. - /// - /// This function scans the history for changesets to be uploaded, i.e., for - /// changesets that are not empty, and were not produced by integration of - /// changesets recieved from the server. The scan begins at the position - /// specified by the initial value of \a upload_progress.client_version, and - /// ends no later than at the position specified by \a end_version. - /// - /// The implementation is allowed to end the scan before \a end_version, - /// such as to limit the combined size of returned changesets. However, if - /// the specified range contains any changesets that are supposed to be - /// uploaded, this function must return at least one. - /// - /// Upon return, \a upload_progress will have been updated to point to the - /// position from which the next scan should resume. This must be a position - /// after the last returned changeset, and before any remaining changesets - /// that are supposed to be uploaded, although never a position that - /// succeeds \a end_version. - /// - /// The value passed as \a upload_progress by the caller, must either be one - /// that was produced by an earlier invocation of - /// find_uploadable_changesets(), one that was returned by get_status(), or - /// one that was received by the client in a DOWNLOAD message from the - /// server. When the value comes from a DOWNLOAD message, it is supposed to - /// reflect a value of UploadChangeset::progress produced by an earlier - /// invocation of find_uploadable_changesets(). - /// - /// Found changesets are added to \a uploadable_changesets. - /// - /// \param locked_server_version will be set to the value that should be - /// used as `` in a DOWNLOAD message. - /// - /// For changesets of local origin, UploadChangeset::origin_file_ident will - /// be zero. - void find_uploadable_changesets(UploadCursor& upload_progress, version_type end_version, - std::vector& uploadable_changesets, - version_type& locked_server_version) const; - - /// \brief Integrate a sequence of changesets received from the server using - /// a single Realm transaction. - /// - /// Each changeset will be transformed as if by a call to - /// Transformer::transform_remote_changeset(), and then applied to the - /// associated Realm. - /// - /// As a final step, each changeset will be added to the local history (list - /// of applied changesets). - /// - /// This function checks whether the specified changesets specify valid - /// remote origin file identifiers and whether the changesets contain valid - /// sequences of instructions. The caller must already have ensured that the - /// origin file identifiers are strictly positive and not equal to the file - /// identifier assigned to this client by the server. - /// - /// If any of the changesets are invalid, this function returns false and - /// sets `integration_error` to the appropriate value. If they are all - /// deemed valid, this function updates \a version_info to reflect the new - /// version produced by the transaction. - /// - /// \param progress The synchronization progress is what was received in the - /// DOWNLOAD message along with the specified changesets. The progress will - /// be persisted along with the changesets. - /// - /// \param downloadable_bytes If specified, and if the implementation cares - /// about byte-level progress, this function updates the persistent record - /// of the estimate of the number of remaining bytes to be downloaded. - /// - /// \param transact If specified, it is a transaction to be used to commit - /// the server changesets after they were transformed. - /// Note: In FLX, the transaction is left in reading state when bootstrap ends. - /// In all other cases, the transaction is left in reading state when the function returns. - void integrate_server_changesets( - const SyncProgress& progress, DownloadableProgress downloadable_bytes, - util::Span changesets, VersionInfo& new_version, DownloadBatchState download_type, - util::Logger&, const TransactionRef& transact, - util::UniqueFunction)> run_in_write_tr = nullptr); - - static void get_upload_download_state(Transaction&, Allocator& alloc, std::uint_fast64_t&, DownloadableProgress&, - std::uint_fast64_t&, std::uint_fast64_t&, std::uint_fast64_t&, - version_type&); - static void get_upload_download_state(DB*, std::uint_fast64_t&, std::uint_fast64_t&); - - /// Record the current download progress. - /// - /// This is used when storing FLX bootstraps to make the progress available - /// to other processes which are observing the file. It must be called - /// inside of a write transaction. The data stored here is only meaningful - /// until the next call of integrate_server_changesets(), which will - /// overwrite it. - static void set_download_progress(Transaction& tr, DownloadableProgress); - - // Overriding member functions in realm::TransformHistory - version_type find_history_entry(version_type, version_type, HistoryEntry&) const noexcept override; - ChunkedBinaryData get_reciprocal_transform(version_type, bool&) const override; - void set_reciprocal_transform(version_type, BinaryData) override; - -public: // Stuff in this section is only used by CLI tools. - /// set_local_origin_timestamp_override() allows you to override the origin timestamp of new changesets - /// of local origin. This should only be used for testing and defaults to calling - /// generate_changeset_timestamp(). - void set_local_origin_timestamp_source(util::UniqueFunction source_fn); - -private: - friend class ClientReplication; - static constexpr version_type s_initial_version = 1; - - ClientHistory(ClientReplication& owner) - : m_replication(owner) - { - } - - ClientReplication& m_replication; - DB* m_db = nullptr; - - /// The version on which the first changeset in the continuous transactions - /// history is based, or if that history is empty, the version associated - /// with currently bound snapshot. In general, `m_ct_history_base_version + - /// m_ct_history.size()` is equal to the version that is associated with the - /// currently bound snapshot, but after add_ct_history_entry() is called, it - /// is equal to that plus one. - mutable version_type m_ct_history_base_version = 0; - - /// Version on which the first changeset in the synchronization history is - /// based, or if that history is empty, the version on which the next - /// changeset, that is added, is based. In general, - /// `m_sync_history_base_version + m_sync_history_size` is equal to the - /// version, that is associated with the currently bound snapshot, but after - /// add_sync_history_entry() is called, it is equal to that plus one. - mutable version_type m_sync_history_base_version = 0; - - using IntegerBpTree = BPlusTree; - struct Arrays { - // Create the client history arrays in the target group - Arrays(DB&, Group& group); - // Initialize accessors for the existing history arrays - Arrays(Allocator& alloc, Group* group, ref_type ref); - - void init_from_ref(ref_type ref); - void verify() const; - - // Root of history compartment - Array root; - - /// Continuous transactions history - BinaryColumn ct_history; - - /// A column of changesets, one row for each entry in the history. - /// - /// FIXME: Ideally, the B+tree accessor below should have been just - /// Bptree, but Bptree seems to not allow that yet. - BinaryColumn changesets; - BinaryColumn reciprocal_transforms; - - IntegerBpTree remote_versions; - IntegerBpTree origin_file_idents; - IntegerBpTree origin_timestamps; - - private: - Arrays(Allocator&) noexcept; - }; - - // clang-format off - - // Sizes of fixed-size arrays - static constexpr int s_root_size = 21; - static constexpr int s_schema_versions_size = 4; - - // Slots in root array of history compartment - static constexpr int s_ct_history_iip = 0; // column ref - static constexpr int s_client_file_ident_iip = 1; // integer - static constexpr int s_client_file_ident_salt_iip = 2; // integer - static constexpr int s_progress_latest_server_version_iip = 3; // integer - static constexpr int s_progress_latest_server_version_salt_iip = 4; // integer - static constexpr int s_progress_download_server_version_iip = 5; // integer - static constexpr int s_progress_download_client_version_iip = 6; // integer - static constexpr int s_progress_upload_client_version_iip = 7; // integer - static constexpr int s_progress_upload_server_version_iip = 8; // integer - static constexpr int s_progress_downloaded_bytes_iip = 9; // integer - static constexpr int s_progress_downloadable_bytes_iip = 10; // integer - static constexpr int s_progress_uploaded_bytes_iip = 11; // integer - static constexpr int s_progress_uploadable_bytes_iip = 12; // integer - static constexpr int s_changesets_iip = 13; // column ref - static constexpr int s_reciprocal_transforms_iip = 14; // column ref - static constexpr int s_remote_versions_iip = 15; // column ref - static constexpr int s_origin_file_idents_iip = 16; // column ref - static constexpr int s_origin_timestamps_iip = 17; // column ref - static constexpr int s_object_id_history_state_iip = 18; // ref - static constexpr int s_cooked_history_iip = 19; // ref (removed) - static constexpr int s_schema_versions_iip = 20; // table ref - - // Slots in root array of `schema_versions` table - static constexpr int s_sv_schema_versions_iip = 0; // integer - static constexpr int s_sv_library_versions_iip = 1; // ref - static constexpr int s_sv_snapshot_versions_iip = 2; // integer (version_type) - static constexpr int s_sv_timestamps_iip = 3; // integer (seconds since epoch) - - // clang-format on - - // The construction of the array accessors need to be delayed, because the - // allocator (Allocator) is not known at the time of construction of the - // ServerHistory object. - mutable util::Optional m_arrays; - - // When applying server changesets, we create a history entry with the data - // from the server instead of using the one generated from applying the - // instructions to the local data. integrate_server_changesets() sets this - // to true to indicate to add_changeset() that it should skip creating a - // history entry. - // - // This field is guarded by the DB's write lock and should only be accessed - // while that is held. - mutable bool m_applying_server_changeset = false; - bool m_applying_client_reset = false; - - // Cache of s_progress_download_server_version_iip and - // s_progress_download_client_version_iip slots of history compartment root - // array. - mutable DownloadCursor m_progress_download = {0, 0}; - - version_type m_version_of_oldest_bound_snapshot = 0; - - util::UniqueFunction m_local_origin_timestamp_source = generate_changeset_timestamp; - - void initialize(DB& db) noexcept - { - m_db = &db; - } - - static version_type find_sync_history_entry(Arrays& arrays, version_type base_version, version_type begin_version, - version_type end_version, HistoryEntry& entry, - version_type& last_integrated_server_version) noexcept; - - // sum_of_history_entry_sizes calculates the sum of the changeset sizes of the local history - // entries that produced a version that succeeds `begin_version` and precedes `end_version`. - std::uint_fast64_t sum_of_history_entry_sizes(version_type begin_version, - version_type end_version) const noexcept; - - size_t transform_and_apply_server_changesets(util::Span changesets_to_integrate, TransactionRef, - util::Logger&, std::uint64_t& downloaded_bytes, - bool allow_lock_release); - - void prepare_for_write(); - Replication::version_type add_changeset(BinaryData changeset, BinaryData sync_changeset); - void add_sync_history_entry(const HistoryEntry&); - void update_sync_progress(const SyncProgress&, DownloadableProgress downloadable_bytes); - void trim_ct_history(); - void trim_sync_history(); - void do_trim_sync_history(std::size_t n); - void clamp_sync_version_range(version_type& begin, version_type& end) const noexcept; - void fix_up_client_file_ident_in_stored_changesets(Transaction&, file_ident_type); - void record_current_schema_version(); - static void record_current_schema_version(Array& schema_versions, version_type snapshot_version); - void compress_stored_changesets(); - - size_t sync_history_size() const noexcept - { - return m_arrays ? m_arrays->changesets.size() : 0; - } - size_t ct_history_size() const noexcept - { - return m_arrays ? m_arrays->ct_history.size() : 0; - } - - // Overriding member functions in realm::_impl::History - void set_group(Group* group, bool updated = false) override; - void update_from_ref_and_version(ref_type ref, version_type version) override; - void update_from_parent(version_type current_version) override; - void get_changesets(version_type, version_type, BinaryIterator*) const noexcept override; - void set_oldest_bound_version(version_type) override; - void verify() const override; - bool no_pending_local_changes(version_type version) const override; -}; - -class ClientReplication final : public SyncReplication { -public: - ClientReplication(bool apply_server_changes = true) - : m_history(*this) - , m_apply_server_changes(apply_server_changes) - { - } - - // A write validator factory takes a write transaction and returns a UniqueFunction containing a - // SyncReplication::WriteValidator. The factory will get called at the start of a write transaction - // and the WriteValidator it returns will be re-used for all mutations within the transaction. - using WriteValidatorFactory = util::UniqueFunction(Transaction&); - void set_write_validator_factory(util::UniqueFunction validator_factory) - { - m_write_validator_factory = std::move(validator_factory); - } - - // Overriding member functions in realm::Replication - void initialize(DB& sg) override; - HistoryType get_history_type() const noexcept override; - int get_history_schema_version() const noexcept override; - bool is_upgradable_history_schema(int) const noexcept override; - void upgrade_history_schema(int) override; - - _impl::History* _get_history_write() override - { - return &m_history; - } - std::unique_ptr<_impl::History> _create_history_read() override - { - auto hist = std::unique_ptr(new ClientHistory(*this)); - hist->initialize(*m_history.m_db); - return hist; - } - - // Overriding member functions in realm::Replication - version_type prepare_changeset(const char*, size_t, version_type) override; - - ClientHistory& get_history() noexcept - { - return m_history; - } - - const ClientHistory& get_history() const noexcept - { - return m_history; - } - - bool apply_server_changes() const noexcept - { - return m_apply_server_changes; - } - -protected: - util::UniqueFunction make_write_validator(Transaction& tr) override; - -private: - ClientHistory m_history; - const bool m_apply_server_changes; - util::UniqueFunction m_write_validator_factory; -}; - - -// Implementation - -// Clamp the beginning of the specified upload skippable version range to the -// beginning of the synchronization history. -// -// A version range whose beginning is related to -// `m_progress_download.last_intergated_client_version` is susceptible to fall -// wholly, or partly before the beginning of the synchronization history due to -// aggressive trimming. -// -// This is not a problem because -// -// - all such ranges are used in contexts where upload skippable history entries -// have no effect, -// -// - the beginning of such a range is always greater than or equal to -// `m_progress_download.last_integrated_client_version`, and -// -// - the trimming rules of the synchronization history ensure that whenever such -// a range refers to a history entry that is no longer in the history, then -// that entry is upload skippable. -// -// See trim_sync_history() for further details, and in particular, for a -// definition of *upload skippable*. -inline void ClientHistory::clamp_sync_version_range(version_type& begin, version_type& end) const noexcept -{ - REALM_ASSERT(begin <= end); - REALM_ASSERT(m_progress_download.last_integrated_client_version <= begin); - if (begin < m_sync_history_base_version) { - begin = m_sync_history_base_version; - if (end < m_sync_history_base_version) - end = m_sync_history_base_version; - } -} - - -/// \brief Create a "sync history" implementation of the realm::Replication -/// interface. -/// -/// The intended role for such an object is as a plugin for new -/// realm::DB objects. -std::unique_ptr make_client_replication(); - -} // namespace realm::sync - -#endif // REALM_NOINST_CLIENT_HISTORY_IMPL_HPP diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp deleted file mode 100644 index eb57eafd45c..00000000000 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ /dev/null @@ -1,2745 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -// NOTE: The protocol specification is in `/doc/protocol.md` - -using namespace realm; -using namespace _impl; -using namespace realm::util; -using namespace realm::sync; -using namespace realm::sync::websocket; - -// clang-format off -using Connection = ClientImpl::Connection; -using Session = ClientImpl::Session; -using UploadChangeset = ClientHistory::UploadChangeset; - -// These are a work-around for a bug in MSVC. It cannot find in-class types -// mentioned in signature of out-of-line member function definitions. -using ConnectionTerminationReason = ClientImpl::ConnectionTerminationReason; -using OutputBuffer = ClientImpl::OutputBuffer; -using ReceivedChangesets = ClientProtocol::ReceivedChangesets; -// clang-format on - -void ClientImpl::ReconnectInfo::reset() noexcept -{ - m_backoff_state.reset(); - scheduled_reset = false; -} - - -void ClientImpl::ReconnectInfo::update(ConnectionTerminationReason new_reason, - std::optional new_delay_info) -{ - m_backoff_state.update(new_reason, new_delay_info); -} - - -std::chrono::milliseconds ClientImpl::ReconnectInfo::delay_interval() -{ - if (scheduled_reset) { - reset(); - } - - if (!m_backoff_state.triggering_error) { - return std::chrono::milliseconds::zero(); - } - - switch (*m_backoff_state.triggering_error) { - case ConnectionTerminationReason::closed_voluntarily: - return std::chrono::milliseconds::zero(); - case ConnectionTerminationReason::server_said_do_not_reconnect: - return std::chrono::milliseconds::max(); - default: - if (m_reconnect_mode == ReconnectMode::testing) { - return std::chrono::milliseconds::max(); - } - - REALM_ASSERT(m_reconnect_mode == ReconnectMode::normal); - return m_backoff_state.delay_interval(); - } -} - - -bool ClientImpl::decompose_server_url(const std::string& url, ProtocolEnvelope& protocol, std::string& address, - port_type& port, std::string& path) const -{ - util::Uri uri(url); // Throws - uri.canonicalize(); // Throws - std::string userinfo, address_2, port_2; - bool realm_scheme = (uri.get_scheme() == "realm:" || uri.get_scheme() == "realms:"); - bool ws_scheme = (uri.get_scheme() == "ws:" || uri.get_scheme() == "wss:"); - bool good = ((realm_scheme || ws_scheme) && uri.get_auth(userinfo, address_2, port_2) && userinfo.empty() && - !address_2.empty() && uri.get_query().empty() && uri.get_frag().empty()); // Throws - if (REALM_UNLIKELY(!good)) - return false; - ProtocolEnvelope protocol_2; - port_type port_3; - if (realm_scheme) { - if (uri.get_scheme() == "realm:") { - protocol_2 = ProtocolEnvelope::realm; - port_3 = (m_enable_default_port_hack ? 80 : 7800); - } - else { - protocol_2 = ProtocolEnvelope::realms; - port_3 = (m_enable_default_port_hack ? 443 : 7801); - } - } - else { - REALM_ASSERT(ws_scheme); - if (uri.get_scheme() == "ws:") { - protocol_2 = ProtocolEnvelope::ws; - port_3 = 80; - } - else { - protocol_2 = ProtocolEnvelope::wss; - port_3 = 443; - } - } - if (!port_2.empty()) { - std::istringstream in(port_2); // Throws - in.imbue(std::locale::classic()); // Throws - in >> port_3; - if (REALM_UNLIKELY(!in || !in.eof() || port_3 < 1)) - return false; - } - std::string path_2 = uri.get_path(); // Throws (copy) - - protocol = protocol_2; - address = std::move(address_2); - port = port_3; - path = std::move(path_2); - return true; -} - -ClientImpl::ClientImpl(ClientConfig config) - : logger(std::make_shared(util::LogCategory::session, std::move(config.logger))) - , m_reconnect_mode{config.reconnect_mode} - , m_connect_timeout{config.connect_timeout} - , m_connection_linger_time{config.one_connection_per_session ? 0 : config.connection_linger_time} - , m_ping_keepalive_period{config.ping_keepalive_period} - , m_pong_keepalive_timeout{config.pong_keepalive_timeout} - , m_fast_reconnect_limit{config.fast_reconnect_limit} - , m_reconnect_backoff_info{config.reconnect_backoff_info} - , m_disable_upload_activation_delay{config.disable_upload_activation_delay} - , m_dry_run{config.dry_run} - , m_enable_default_port_hack{config.enable_default_port_hack} - , m_fix_up_object_ids{config.fix_up_object_ids} - , m_roundtrip_time_handler{std::move(config.roundtrip_time_handler)} - , m_socket_provider{std::move(config.socket_provider)} - , m_client_protocol{} // Throws - , m_one_connection_per_session{config.one_connection_per_session} - , m_random{} -{ - // FIXME: Would be better if seeding was up to the application. - util::seed_prng_nondeterministically(m_random); // Throws - - logger.info("Realm sync client (%1)", REALM_VER_CHUNK); // Throws - logger.debug("Supported protocol versions: %1-%2", get_oldest_supported_protocol_version(), - get_current_protocol_version()); // Throws - logger.info("Platform: %1", util::get_platform_info()); - const char* build_mode; -#if REALM_DEBUG - build_mode = "Debug"; -#else - build_mode = "Release"; -#endif - logger.debug("Build mode: %1", build_mode); - logger.debug("Config param: one_connection_per_session = %1", - config.one_connection_per_session); // Throws - logger.debug("Config param: connect_timeout = %1 ms", - config.connect_timeout); // Throws - logger.debug("Config param: connection_linger_time = %1 ms", - config.connection_linger_time); // Throws - logger.debug("Config param: ping_keepalive_period = %1 ms", - config.ping_keepalive_period); // Throws - logger.debug("Config param: pong_keepalive_timeout = %1 ms", - config.pong_keepalive_timeout); // Throws - logger.debug("Config param: fast_reconnect_limit = %1 ms", - config.fast_reconnect_limit); // Throws - logger.debug("Config param: disable_sync_to_disk = %1", - config.disable_sync_to_disk); // Throws - logger.debug( - "Config param: reconnect backoff info: max_delay: %1 ms, initial_delay: %2 ms, multiplier: %3, jitter: 1/%4", - m_reconnect_backoff_info.max_resumption_delay_interval.count(), - m_reconnect_backoff_info.resumption_delay_interval.count(), - m_reconnect_backoff_info.resumption_delay_backoff_multiplier, m_reconnect_backoff_info.delay_jitter_divisor); - - if (config.reconnect_mode != ReconnectMode::normal) { - logger.warn("Testing/debugging feature 'nonnormal reconnect mode' enabled - " - "never do this in production!"); - } - - if (config.dry_run) { - logger.warn("Testing/debugging feature 'dry run' enabled - " - "never do this in production!"); - } - - REALM_ASSERT_EX(m_socket_provider, "Must provide socket provider in sync Client config"); - - if (m_one_connection_per_session) { - logger.warn("Testing/debugging feature 'one connection per session' enabled - " - "never do this in production"); - } - - if (config.disable_upload_activation_delay) { - logger.warn("Testing/debugging feature 'disable_upload_activation_delay' enabled - " - "never do this in production"); - } - - if (config.disable_sync_to_disk) { - logger.warn("Testing/debugging feature 'disable_sync_to_disk' enabled - " - "never do this in production"); - } - - m_actualize_and_finalize = create_trigger([this](Status status) { - if (status == ErrorCodes::OperationAborted) - return; - else if (!status.is_ok()) - throw Exception(status); - actualize_and_finalize_session_wrappers(); // Throws - }); -} - -void ClientImpl::incr_outstanding_posts() -{ - util::CheckedLockGuard lock(m_drain_mutex); - ++m_outstanding_posts; - m_drained = false; -} - -void ClientImpl::decr_outstanding_posts() -{ - util::CheckedLockGuard lock(m_drain_mutex); - REALM_ASSERT(m_outstanding_posts); - if (--m_outstanding_posts <= 0) { - // Notify must happen with lock held or another thread could destroy - // ClientImpl between when we release the lock and when we call notify - m_drain_cv.notify_all(); - } -} - -void ClientImpl::post(SyncSocketProvider::FunctionHandler&& handler) -{ - REALM_ASSERT(m_socket_provider); - incr_outstanding_posts(); - m_socket_provider->post([handler = std::move(handler), this](Status status) { - auto decr_guard = util::make_scope_exit([&]() noexcept { - decr_outstanding_posts(); - }); - handler(status); - }); -} - -void ClientImpl::post(util::UniqueFunction&& handler) -{ - REALM_ASSERT(m_socket_provider); - incr_outstanding_posts(); - m_socket_provider->post([handler = std::move(handler), this](Status status) { - auto decr_guard = util::make_scope_exit([&]() noexcept { - decr_outstanding_posts(); - }); - if (status == ErrorCodes::OperationAborted) - return; - if (!status.is_ok()) - throw Exception(status); - handler(); - }); -} - - -void ClientImpl::drain_connections() -{ - logger.debug("Draining connections during sync client shutdown"); - for (auto& server_slot_pair : m_server_slots) { - auto& server_slot = server_slot_pair.second; - - if (server_slot.connection) { - auto& conn = server_slot.connection; - conn->force_close(); - } - else { - for (auto& conn_pair : server_slot.alt_connections) { - conn_pair.second->force_close(); - } - } - } -} - - -SyncSocketProvider::SyncTimer ClientImpl::create_timer(std::chrono::milliseconds delay, - SyncSocketProvider::FunctionHandler&& handler) -{ - REALM_ASSERT(m_socket_provider); - incr_outstanding_posts(); - return m_socket_provider->create_timer(delay, [handler = std::move(handler), this](Status status) { - auto decr_guard = util::make_scope_exit([&]() noexcept { - decr_outstanding_posts(); - }); - handler(status); - }); -} - - -ClientImpl::SyncTrigger ClientImpl::create_trigger(SyncSocketProvider::FunctionHandler&& handler) -{ - REALM_ASSERT(m_socket_provider); - return std::make_unique>(this, std::move(handler)); -} - -Connection::~Connection() -{ - if (m_websocket_sentinel) { - m_websocket_sentinel->destroyed = true; - m_websocket_sentinel.reset(); - } -} - -void Connection::activate() -{ - REALM_ASSERT(m_on_idle); - m_activated = true; - if (m_num_active_sessions == 0) - m_on_idle->trigger(); - // We cannot in general connect immediately, because a prior failure to - // connect may require a delay before reconnecting (see `m_reconnect_info`). - initiate_reconnect_wait(); // Throws -} - - -void Connection::activate_session(std::unique_ptr sess) -{ - REALM_ASSERT(sess); - REALM_ASSERT(&sess->m_conn == this); - REALM_ASSERT(!m_force_closed); - Session& sess_2 = *sess; - session_ident_type ident = sess->m_ident; - auto p = m_sessions.emplace(ident, std::move(sess)); // Throws - bool was_inserted = p.second; - REALM_ASSERT(was_inserted); - // Save the session ident to the historical list of session idents - m_session_history.insert(ident); - sess_2.activate(); // Throws - if (m_state == ConnectionState::connected) { - bool fast_reconnect = false; - sess_2.connection_established(fast_reconnect); // Throws - } - ++m_num_active_sessions; -} - - -void Connection::initiate_session_deactivation(Session* sess) -{ - REALM_ASSERT(sess); - REALM_ASSERT(&sess->m_conn == this); - REALM_ASSERT(m_num_active_sessions); - // Since the client may be waiting for m_num_active_sessions to reach 0 - // in stop_and_wait() (on a separate thread), deactivate Session before - // decrementing the num active sessions value. - sess->initiate_deactivation(); // Throws - if (sess->m_state == Session::Deactivated) { - finish_session_deactivation(sess); - } - if (REALM_UNLIKELY(--m_num_active_sessions == 0)) { - if (m_activated && m_state == ConnectionState::disconnected) - m_on_idle->trigger(); - } -} - - -void Connection::cancel_reconnect_delay() -{ - REALM_ASSERT(m_activated); - - if (m_reconnect_delay_in_progress) { - if (m_nonzero_reconnect_delay) - logger.detail("Canceling reconnect delay"); // Throws - - // Cancel the in-progress wait operation by destroying the timer - // object. Destruction is needed in this case, because a new wait - // operation might have to be initiated before the previous one - // completes (its completion handler starts to execute), so the new wait - // operation must be done on a new timer object. - m_reconnect_disconnect_timer.reset(); - m_reconnect_delay_in_progress = false; - m_reconnect_info.reset(); - initiate_reconnect_wait(); // Throws - return; - } - - // If we are not disconnected, then we need to make sure the next time we get disconnected - // that we are allowed to re-connect as quickly as possible. - // - // Setting m_reconnect_info.scheduled_reset will cause initiate_reconnect_wait to reset the - // backoff/delay state before calculating the next delay, unless a PONG message is received - // for the urgent PING message we send below. - // - // If we get a PONG message for the urgent PING message sent below, then the connection is - // healthy and we can calculate the next delay normally. - if (m_state != ConnectionState::disconnected) { - m_reconnect_info.scheduled_reset = true; - m_ping_after_scheduled_reset_of_reconnect_info = false; - - schedule_urgent_ping(); // Throws - return; - } - // Nothing to do in this case. The next reconnect attemp will be made as - // soon as there are any sessions that are both active and unsuspended. -} - -void Connection::finish_session_deactivation(Session* sess) -{ - REALM_ASSERT(sess->m_state == Session::Deactivated); - auto ident = sess->m_ident; - m_sessions.erase(ident); - m_session_history.erase(ident); -} - -void Connection::force_close() -{ - if (m_force_closed) { - return; - } - - m_force_closed = true; - - if (m_state != ConnectionState::disconnected) { - voluntary_disconnect(); - } - - REALM_ASSERT_EX(m_state == ConnectionState::disconnected, m_state); - if (m_reconnect_delay_in_progress || m_disconnect_delay_in_progress) { - m_reconnect_disconnect_timer.reset(); - m_reconnect_delay_in_progress = false; - m_disconnect_delay_in_progress = false; - } - - // We must copy any session pointers we want to close to a vector because force_closing - // the session may remove it from m_sessions and invalidate the iterator uses to loop - // through the map. By copying to a separate vector we ensure our iterators remain valid. - std::vector to_close; - for (auto& session_pair : m_sessions) { - if (session_pair.second->m_state == Session::State::Active) { - to_close.push_back(session_pair.second.get()); - } - } - - for (auto& sess : to_close) { - sess->force_close(); - } - - logger.debug("Force closed idle connection"); -} - - -void Connection::websocket_connected_handler(const std::string& protocol) -{ - if (!protocol.empty()) { - std::string_view expected_prefix = - is_flx_sync_connection() ? get_flx_websocket_protocol_prefix() : get_pbs_websocket_protocol_prefix(); - // FIXME: Use std::string_view::begins_with() in C++20. - auto prefix_matches = [&](std::string_view other) { - return protocol.size() >= other.size() && (protocol.substr(0, other.size()) == other); - }; - if (prefix_matches(expected_prefix)) { - util::MemoryInputStream in; - in.set_buffer(protocol.data() + expected_prefix.size(), protocol.data() + protocol.size()); - in.imbue(std::locale::classic()); - in.unsetf(std::ios_base::skipws); - int value_2 = 0; - in >> value_2; - if (in && in.eof() && value_2 >= 0) { - bool good_version = - (value_2 >= get_oldest_supported_protocol_version() && value_2 <= get_current_protocol_version()); - if (good_version) { - logger.detail("Negotiated protocol version: %1", value_2); - // For now, grab the connection ID from the websocket if it supports it. In the future, the server - // will provide the appservices connection ID via a log message. - // TODO: Remove once the server starts sending the connection ID - receive_appservices_request_id(m_websocket->get_appservices_request_id()); - m_negotiated_protocol_version = value_2; - handle_connection_established(); // Throws - return; - } - } - } - close_due_to_client_side_error({ErrorCodes::SyncProtocolNegotiationFailed, - util::format("Bad protocol info from server: '%1'", protocol)}, - IsFatal{true}, ConnectionTerminationReason::bad_headers_in_http_response); - } - else { - close_due_to_client_side_error( - {ErrorCodes::SyncProtocolNegotiationFailed, "Missing protocol info from server"}, IsFatal{true}, - ConnectionTerminationReason::bad_headers_in_http_response); - } -} - - -bool Connection::websocket_binary_message_received(util::Span data) -{ - if (m_force_closed) { - logger.debug("Received binary message after connection was force closed"); - return false; - } - - using sf = SimulatedFailure; - if (sf::check_trigger(sf::sync_client__read_head)) { - close_due_to_client_side_error( - {ErrorCodes::RuntimeError, "Simulated failure during sync client websocket read"}, IsFatal{false}, - ConnectionTerminationReason::read_or_write_error); - return bool(m_websocket); - } - - handle_message_received(data); - return bool(m_websocket); -} - - -void Connection::websocket_error_handler() -{ - m_websocket_error_received = true; -} - -bool Connection::websocket_closed_handler(bool was_clean, WebSocketError error_code, std::string_view msg) -{ - if (m_force_closed) { - logger.debug("Received websocket close message after connection was force closed"); - return false; - } - logger.info("Closing the websocket with error code=%1, message='%2', was_clean=%3", error_code, msg, was_clean); - - switch (error_code) { - case WebSocketError::websocket_ok: - break; - case WebSocketError::websocket_resolve_failed: - [[fallthrough]]; - case WebSocketError::websocket_connection_failed: { - SessionErrorInfo error_info( - {ErrorCodes::SyncConnectFailed, util::format("Failed to connect to sync: %1", msg)}, IsFatal{false}); - // If the connection fails/times out and the server has not been contacted yet, refresh the location - // to make sure the websocket URL is correct - if (!m_server_endpoint.is_verified) { - error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshLocation; - } - involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::connect_operation_failed); - break; - } - case WebSocketError::websocket_read_error: - [[fallthrough]]; - case WebSocketError::websocket_write_error: { - close_due_to_transient_error({ErrorCodes::ConnectionClosed, msg}, - ConnectionTerminationReason::read_or_write_error); - break; - } - case WebSocketError::websocket_going_away: - [[fallthrough]]; - case WebSocketError::websocket_protocol_error: - [[fallthrough]]; - case WebSocketError::websocket_unsupported_data: - [[fallthrough]]; - case WebSocketError::websocket_invalid_payload_data: - [[fallthrough]]; - case WebSocketError::websocket_policy_violation: - [[fallthrough]]; - case WebSocketError::websocket_reserved: - [[fallthrough]]; - case WebSocketError::websocket_no_status_received: - [[fallthrough]]; - case WebSocketError::websocket_invalid_extension: { - close_due_to_client_side_error({ErrorCodes::SyncProtocolInvariantFailed, msg}, IsFatal{false}, - ConnectionTerminationReason::websocket_protocol_violation); // Throws - break; - } - case WebSocketError::websocket_message_too_big: { - auto message = util::format( - "Sync websocket closed because the server received a message that was too large: %1", msg); - SessionErrorInfo error_info(Status(ErrorCodes::LimitExceeded, std::move(message)), IsFatal{false}); - error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::websocket_protocol_violation); // Throws - break; - } - case WebSocketError::websocket_tls_handshake_failed: { - close_due_to_client_side_error( - Status(ErrorCodes::TlsHandshakeFailed, util::format("TLS handshake failed: %1", msg)), IsFatal{false}, - ConnectionTerminationReason::ssl_certificate_rejected); // Throws - break; - } - case WebSocketError::websocket_fatal_error: { - // Error is fatal if the sync_route has already been verified - if the sync_route has not - // been verified, then use a non-fatal error and try to perform a location update. - SessionErrorInfo error_info( - {ErrorCodes::SyncConnectFailed, util::format("Failed to connect to sync: %1", msg)}, - IsFatal{m_server_endpoint.is_verified}); - ConnectionTerminationReason reason = ConnectionTerminationReason::http_response_says_fatal_error; - // If the connection fails/times out and the server has not been contacted yet, refresh the location - // to make sure the websocket URL is correct - if (!m_server_endpoint.is_verified) { - error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshLocation; - reason = ConnectionTerminationReason::connect_operation_failed; - } - involuntary_disconnect(std::move(error_info), reason); - break; - } - case WebSocketError::websocket_forbidden: { - SessionErrorInfo error_info({ErrorCodes::AuthError, msg}, IsFatal{true}); - error_info.server_requests_action = ProtocolErrorInfo::Action::LogOutUser; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::http_response_says_fatal_error); - break; - } - case WebSocketError::websocket_unauthorized: { - SessionErrorInfo error_info( - {ErrorCodes::AuthError, - util::format("Websocket was closed because of an authentication issue: %1", msg)}, - IsFatal{false}); - error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshUser; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::http_response_says_nonfatal_error); - break; - } - case WebSocketError::websocket_moved_permanently: { - SessionErrorInfo error_info({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}); - error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshLocation; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::http_response_says_nonfatal_error); - break; - } - case WebSocketError::websocket_abnormal_closure: { - SessionErrorInfo error_info({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}); - error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshUser; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::http_response_says_nonfatal_error); - break; - } - case WebSocketError::websocket_internal_server_error: - [[fallthrough]]; - case WebSocketError::websocket_retry_error: { - involuntary_disconnect(SessionErrorInfo({ErrorCodes::ConnectionClosed, msg}, IsFatal{false}), - ConnectionTerminationReason::http_response_says_nonfatal_error); - break; - } - } - - return bool(m_websocket); -} - -// Guarantees that handle_reconnect_wait() is never called from within the -// execution of initiate_reconnect_wait() (no callback reentrance). -void Connection::initiate_reconnect_wait() -{ - REALM_ASSERT(m_activated); - REALM_ASSERT(!m_reconnect_delay_in_progress); - REALM_ASSERT(!m_disconnect_delay_in_progress); - - // If we've been force closed then we don't need/want to reconnect. Just return early here. - if (m_force_closed) { - return; - } - - m_reconnect_delay_in_progress = true; - auto delay = m_reconnect_info.delay_interval(); - if (delay == std::chrono::milliseconds::max()) { - logger.detail("Reconnection delayed indefinitely"); // Throws - // Not actually starting a timer corresponds to an infinite wait - m_nonzero_reconnect_delay = true; - return; - } - - if (delay == std::chrono::milliseconds::zero()) { - m_nonzero_reconnect_delay = false; - } - else { - logger.detail("Allowing reconnection in %1 milliseconds", delay.count()); // Throws - m_nonzero_reconnect_delay = true; - } - - // We create a timer for the reconnect_disconnect timer even if the delay is zero because - // we need it to be cancelable in case the connection is terminated before the timer - // callback is run. - m_reconnect_disconnect_timer = m_client.create_timer(delay, [this](Status status) { - // If the operation is aborted, the connection object may have been - // destroyed. - if (status != ErrorCodes::OperationAborted) - handle_reconnect_wait(status); // Throws - }); // Throws -} - - -void Connection::handle_reconnect_wait(Status status) -{ - if (!status.is_ok()) { - REALM_ASSERT(status != ErrorCodes::OperationAborted); - throw Exception(status); - } - - REALM_ASSERT(m_reconnect_delay_in_progress); - m_reconnect_delay_in_progress = false; - - if (m_num_active_unsuspended_sessions > 0) - initiate_reconnect(); // Throws -} - -struct Connection::WebSocketObserverShim : public sync::WebSocketObserver { - explicit WebSocketObserverShim(Connection* conn) - : conn(conn) - , sentinel(conn->m_websocket_sentinel) - { - } - - Connection* conn; - util::bind_ptr sentinel; - - void websocket_connected_handler(const std::string& protocol) override - { - if (sentinel->destroyed) { - return; - } - - return conn->websocket_connected_handler(protocol); - } - - void websocket_error_handler() override - { - if (sentinel->destroyed) { - return; - } - - conn->websocket_error_handler(); - } - - bool websocket_binary_message_received(util::Span data) override - { - if (sentinel->destroyed) { - return false; - } - - return conn->websocket_binary_message_received(data); - } - - bool websocket_closed_handler(bool was_clean, WebSocketError error_code, std::string_view msg) override - { - if (sentinel->destroyed) { - return true; - } - - return conn->websocket_closed_handler(was_clean, error_code, msg); - } -}; - -void Connection::initiate_reconnect() -{ - REALM_ASSERT(m_activated); - - m_state = ConnectionState::connecting; - report_connection_state_change(ConnectionState::connecting); // Throws - if (m_websocket_sentinel) { - m_websocket_sentinel->destroyed = true; - } - m_websocket_sentinel = util::make_bind(); - m_websocket.reset(); - - // Watchdog - initiate_connect_wait(); // Throws - - std::vector sec_websocket_protocol; - { - auto protocol_prefix = - is_flx_sync_connection() ? get_flx_websocket_protocol_prefix() : get_pbs_websocket_protocol_prefix(); - int min = get_oldest_supported_protocol_version(); - int max = get_current_protocol_version(); - REALM_ASSERT_3(min, <=, max); - // List protocol version in descending order to ensure that the server - // selects the highest possible version. - for (int version = max; version >= min; --version) { - sec_websocket_protocol.push_back(util::format("%1%2", protocol_prefix, version)); // Throws - } - } - - logger.info("Connecting to '%1%2:%3%4'", to_string(m_server_endpoint.envelope), m_server_endpoint.address, - m_server_endpoint.port, m_http_request_path_prefix); - - m_websocket_error_received = false; - m_websocket = - m_client.m_socket_provider->connect(std::make_unique(this), - WebSocketEndpoint{ - m_server_endpoint.address, - m_server_endpoint.port, - get_http_request_path(), - std::move(sec_websocket_protocol), - is_ssl(m_server_endpoint.envelope), - /// DEPRECATED - The following will be removed in a future release - {m_custom_http_headers.begin(), m_custom_http_headers.end()}, - m_verify_servers_ssl_certificate, - m_ssl_trust_certificate_path, - m_ssl_verify_callback, - m_proxy_config, - }); -} - - -void Connection::initiate_connect_wait() -{ - // Deploy a watchdog to enforce an upper bound on the time it can take to - // fully establish the connection (including SSL and WebSocket - // handshakes). Without such a watchdog, connect operations could take very - // long, or even indefinite time. - milliseconds_type time = m_client.m_connect_timeout; - - m_connect_timer = m_client.create_timer(std::chrono::milliseconds(time), [this](Status status) { - // If the operation is aborted, the connection object may have been - // destroyed. - if (status != ErrorCodes::OperationAborted) - handle_connect_wait(status); // Throws - }); // Throws -} - - -void Connection::handle_connect_wait(Status status) -{ - if (!status.is_ok()) { - REALM_ASSERT(status != ErrorCodes::OperationAborted); - throw Exception(status); - } - - REALM_ASSERT_EX(m_state == ConnectionState::connecting, m_state); - logger.info("Connect timeout"); // Throws - SessionErrorInfo error_info({ErrorCodes::SyncConnectTimeout, "Sync connection was not fully established in time"}, - IsFatal{false}); - // If the connection fails/times out and the server has not been contacted yet, refresh the location - // to make sure the websocket URL is correct - if (!m_server_endpoint.is_verified) { - error_info.server_requests_action = ProtocolErrorInfo::Action::RefreshLocation; - } - involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::sync_connect_timeout); // Throws -} - - -void Connection::handle_connection_established() -{ - // Cancel connect timeout watchdog - m_connect_timer.reset(); - - m_state = ConnectionState::connected; - m_server_endpoint.is_verified = true; // sync route is valid since connection is successful - - milliseconds_type now = monotonic_clock_now(); - m_pong_wait_started_at = now; // Initially, no time was spent waiting for a PONG message - initiate_ping_delay(now); // Throws - - bool fast_reconnect = false; - if (m_disconnect_has_occurred) { - milliseconds_type time = now - m_disconnect_time; - if (time <= m_client.m_fast_reconnect_limit) - fast_reconnect = true; - } - - for (auto& p : m_sessions) { - Session& sess = *p.second; - sess.connection_established(fast_reconnect); // Throws - } - - report_connection_state_change(ConnectionState::connected); // Throws -} - - -void Connection::schedule_urgent_ping() -{ - REALM_ASSERT_EX(m_state != ConnectionState::disconnected, m_state); - if (m_ping_delay_in_progress) { - m_heartbeat_timer.reset(); - m_ping_delay_in_progress = false; - m_minimize_next_ping_delay = true; - milliseconds_type now = monotonic_clock_now(); - initiate_ping_delay(now); // Throws - return; - } - REALM_ASSERT_EX(m_state == ConnectionState::connecting || m_waiting_for_pong, m_state); - if (!m_send_ping) - m_minimize_next_ping_delay = true; -} - - -void Connection::initiate_ping_delay(milliseconds_type now) -{ - REALM_ASSERT(!m_ping_delay_in_progress); - REALM_ASSERT(!m_waiting_for_pong); - REALM_ASSERT(!m_send_ping); - - milliseconds_type delay = 0; - if (!m_minimize_next_ping_delay) { - delay = m_client.m_ping_keepalive_period; - // Make a randomized deduction of up to 10%, or up to 100% if this is - // the first PING message to be sent since the connection was - // established. The purpose of this randomized deduction is to reduce - // the risk of many connections sending PING messages simultaneously to - // the server. - milliseconds_type max_deduction = (m_ping_sent ? delay / 10 : delay); - auto distr = std::uniform_int_distribution(0, max_deduction); - milliseconds_type randomized_deduction = distr(m_client.get_random()); - delay -= randomized_deduction; - // Deduct the time spent waiting for PONG - REALM_ASSERT_3(now, >=, m_pong_wait_started_at); - milliseconds_type spent_time = now - m_pong_wait_started_at; - if (spent_time < delay) { - delay -= spent_time; - } - else { - delay = 0; - } - } - else { - m_minimize_next_ping_delay = false; - } - - - m_ping_delay_in_progress = true; - - m_heartbeat_timer = m_client.create_timer(std::chrono::milliseconds(delay), [this](Status status) { - if (status == ErrorCodes::OperationAborted) - return; - else if (!status.is_ok()) - throw Exception(status); - - handle_ping_delay(); // Throws - }); // Throws - logger.debug("Will emit a ping in %1 milliseconds", delay); // Throws -} - - -void Connection::handle_ping_delay() -{ - REALM_ASSERT(m_ping_delay_in_progress); - m_ping_delay_in_progress = false; - m_send_ping = true; - - initiate_pong_timeout(); // Throws - - if (m_state == ConnectionState::connected && !m_sending) - send_next_message(); // Throws -} - - -void Connection::initiate_pong_timeout() -{ - REALM_ASSERT(!m_ping_delay_in_progress); - REALM_ASSERT(!m_waiting_for_pong); - REALM_ASSERT(m_send_ping); - - m_waiting_for_pong = true; - m_pong_wait_started_at = monotonic_clock_now(); - - milliseconds_type time = m_client.m_pong_keepalive_timeout; - m_heartbeat_timer = m_client.create_timer(std::chrono::milliseconds(time), [this](Status status) { - if (status == ErrorCodes::OperationAborted) - return; - else if (!status.is_ok()) - throw Exception(status); - - handle_pong_timeout(); // Throws - }); // Throws -} - - -void Connection::handle_pong_timeout() -{ - REALM_ASSERT(m_waiting_for_pong); - logger.debug("Timeout on reception of PONG message"); // Throws - close_due_to_transient_error({ErrorCodes::ConnectionClosed, "Timed out waiting for PONG response from server"}, - ConnectionTerminationReason::pong_timeout); -} - - -void Connection::initiate_write_message(const OutputBuffer& out, Session* sess) -{ - // Stop sending messages if an websocket error was received. - if (m_websocket_error_received) - return; - - m_websocket->async_write_binary(out.as_span(), [this, sentinel = m_websocket_sentinel](Status status) { - if (sentinel->destroyed) { - return; - } - if (!status.is_ok()) { - if (status != ErrorCodes::Error::OperationAborted) { - // Write errors will be handled by the websocket_write_error_handler() callback - logger.error("Connection: write failed %1: %2", status.code_string(), status.reason()); - } - return; - } - handle_write_message(); // Throws - }); // Throws - m_sending_session = sess; - m_sending = true; -} - - -void Connection::handle_write_message() -{ - m_sending_session->message_sent(); // Throws - if (m_sending_session->m_state == Session::Deactivated) { - finish_session_deactivation(m_sending_session); - } - m_sending_session = nullptr; - m_sending = false; - send_next_message(); // Throws -} - - -void Connection::send_next_message() -{ - REALM_ASSERT_EX(m_state == ConnectionState::connected, m_state); - REALM_ASSERT(!m_sending_session); - REALM_ASSERT(!m_sending); - if (m_send_ping) { - send_ping(); // Throws - return; - } - while (!m_sessions_enlisted_to_send.empty()) { - // The state of being connected is not supposed to be able to change - // across this loop thanks to the "no callback reentrance" guarantee - // provided by Websocket::async_write_text(), and friends. - REALM_ASSERT_EX(m_state == ConnectionState::connected, m_state); - - Session& sess = *m_sessions_enlisted_to_send.front(); - m_sessions_enlisted_to_send.pop_front(); - sess.send_message(); // Throws - - if (sess.m_state == Session::Deactivated) { - finish_session_deactivation(&sess); - } - - // An enlisted session may choose to not send a message. In that case, - // we should pass the opportunity to the next enlisted session. - if (m_sending) - break; - } -} - - -void Connection::send_ping() -{ - REALM_ASSERT(!m_ping_delay_in_progress); - REALM_ASSERT(m_waiting_for_pong); - REALM_ASSERT(m_send_ping); - - m_send_ping = false; - if (m_reconnect_info.scheduled_reset) - m_ping_after_scheduled_reset_of_reconnect_info = true; - - m_last_ping_sent_at = monotonic_clock_now(); - logger.debug("Sending: PING(timestamp=%1, rtt=%2)", m_last_ping_sent_at, - m_previous_ping_rtt); // Throws - - ClientProtocol& protocol = get_client_protocol(); - OutputBuffer& out = get_output_buffer(); - protocol.make_ping(out, m_last_ping_sent_at, m_previous_ping_rtt); // Throws - initiate_write_ping(out); // Throws - m_ping_sent = true; -} - - -void Connection::initiate_write_ping(const OutputBuffer& out) -{ - m_websocket->async_write_binary(out.as_span(), [this, sentinel = m_websocket_sentinel](Status status) { - if (sentinel->destroyed) { - return; - } - if (!status.is_ok()) { - if (status != ErrorCodes::Error::OperationAborted) { - // Write errors will be handled by the websocket_write_error_handler() callback - logger.error("Connection: send ping failed %1: %2", status.code_string(), status.reason()); - } - return; - } - handle_write_ping(); // Throws - }); // Throws - m_sending = true; -} - - -void Connection::handle_write_ping() -{ - REALM_ASSERT(m_sending); - REALM_ASSERT(!m_sending_session); - m_sending = false; - send_next_message(); // Throws -} - - -void Connection::handle_message_received(util::Span data) -{ - // parse_message_received() parses the message and calls the proper handler - // on the Connection object (this). - get_client_protocol().parse_message_received(*this, std::string_view(data.data(), data.size())); -} - - -void Connection::initiate_disconnect_wait() -{ - REALM_ASSERT(!m_reconnect_delay_in_progress); - - if (m_disconnect_delay_in_progress) { - m_reconnect_disconnect_timer.reset(); - m_disconnect_delay_in_progress = false; - } - - milliseconds_type time = m_client.m_connection_linger_time; - - m_reconnect_disconnect_timer = m_client.create_timer(std::chrono::milliseconds(time), [this](Status status) { - // If the operation is aborted, the connection object may have been - // destroyed. - if (status != ErrorCodes::OperationAborted) - handle_disconnect_wait(status); // Throws - }); // Throws - m_disconnect_delay_in_progress = true; -} - - -void Connection::handle_disconnect_wait(Status status) -{ - if (!status.is_ok()) { - REALM_ASSERT(status != ErrorCodes::OperationAborted); - throw Exception(status); - } - - m_disconnect_delay_in_progress = false; - - REALM_ASSERT_EX(m_state != ConnectionState::disconnected, m_state); - if (m_num_active_unsuspended_sessions == 0) { - if (m_client.m_connection_linger_time > 0) - logger.detail("Linger time expired"); // Throws - voluntary_disconnect(); // Throws - logger.info("Disconnected"); // Throws - } -} - - -void Connection::close_due_to_protocol_error(Status status) -{ - SessionErrorInfo error_info(std::move(status), IsFatal{true}); - error_info.server_requests_action = ProtocolErrorInfo::Action::ProtocolViolation; - involuntary_disconnect(std::move(error_info), - ConnectionTerminationReason::sync_protocol_violation); // Throws -} - - -void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason) -{ - logger.info("Connection closed due to error: %1", status); // Throws - - involuntary_disconnect(SessionErrorInfo{std::move(status), is_fatal}, reason); // Throw -} - - -void Connection::close_due_to_transient_error(Status status, ConnectionTerminationReason reason) -{ - logger.info("Connection closed due to transient error: %1", status); // Throws - SessionErrorInfo error_info{std::move(status), IsFatal{false}}; - error_info.server_requests_action = ProtocolErrorInfo::Action::Transient; - - involuntary_disconnect(std::move(error_info), reason); // Throw -} - - -// Close connection due to error discovered on the server-side, and then -// reported to the client by way of a connection-level ERROR message. -void Connection::close_due_to_server_side_error(ProtocolError error_code, const ProtocolErrorInfo& info) -{ - logger.info("Connection closed due to error reported by server: %1 (%2)", info.message, - int(error_code)); // Throws - - const auto reason = info.is_fatal ? ConnectionTerminationReason::server_said_do_not_reconnect - : ConnectionTerminationReason::server_said_try_again_later; - involuntary_disconnect(SessionErrorInfo{info, protocol_error_to_status(error_code, info.message)}, - reason); // Throws -} - - -void Connection::disconnect(const SessionErrorInfo& info) -{ - // Cancel connect timeout watchdog - m_connect_timer.reset(); - - if (m_state == ConnectionState::connected) { - m_disconnect_time = monotonic_clock_now(); - m_disconnect_has_occurred = true; - - // Sessions that are in the Deactivating state at this time can be - // immediately discarded, in part because they are no longer enlisted to - // send. Such sessions will be taken to the Deactivated state by - // Session::connection_lost(), and then they will be removed from - // `m_sessions`. - auto i = m_sessions.begin(), end = m_sessions.end(); - while (i != end) { - // Prevent invalidation of the main iterator when erasing elements - auto j = i++; - Session& sess = *j->second; - sess.connection_lost(); // Throws - if (sess.m_state == Session::Unactivated || sess.m_state == Session::Deactivated) - m_sessions.erase(j); - } - } - - change_state_to_disconnected(); - - m_ping_delay_in_progress = false; - m_waiting_for_pong = false; - m_send_ping = false; - m_minimize_next_ping_delay = false; - m_ping_after_scheduled_reset_of_reconnect_info = false; - m_ping_sent = false; - m_heartbeat_timer.reset(); - m_previous_ping_rtt = 0; - - m_websocket_sentinel->destroyed = true; - m_websocket_sentinel.reset(); - m_websocket.reset(); - m_input_body_buffer.reset(); - m_sending_session = nullptr; - m_sessions_enlisted_to_send.clear(); - m_sending = false; - - if (!m_appservices_coid.empty()) { - m_appservices_coid.clear(); - logger.base_logger = make_logger(m_ident, std::nullopt, get_client().logger.base_logger); - for (auto& [ident, sess] : m_sessions) { - sess->logger.base_logger = Session::make_logger(ident, logger.base_logger); - } - } - - report_connection_state_change(ConnectionState::disconnected, info); // Throws - initiate_reconnect_wait(); // Throws -} - -bool Connection::is_flx_sync_connection() const noexcept -{ - return m_server_endpoint.server_mode != SyncServerMode::PBS; -} - -void Connection::receive_pong(milliseconds_type timestamp) -{ - logger.debug("Received: PONG(timestamp=%1)", timestamp); - - bool legal_at_this_time = (m_waiting_for_pong && !m_send_ping); - if (REALM_UNLIKELY(!legal_at_this_time)) { - close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, "Received PONG message when it was not valid"}); // Throws - return; - } - - if (REALM_UNLIKELY(timestamp != m_last_ping_sent_at)) { - close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received PONG message with an invalid timestamp (expected %1, received %2)", - m_last_ping_sent_at, timestamp)}); // Throws - return; - } - - milliseconds_type now = monotonic_clock_now(); - milliseconds_type round_trip_time = now - timestamp; - logger.debug("Round trip time was %1 milliseconds", round_trip_time); - m_previous_ping_rtt = round_trip_time; - - // If this PONG message is a response to a PING mesage that was sent after - // the last invocation of cancel_reconnect_delay(), then the connection is - // still good, and we do not have to skip the next reconnect delay. - if (m_ping_after_scheduled_reset_of_reconnect_info) { - REALM_ASSERT(m_reconnect_info.scheduled_reset); - m_ping_after_scheduled_reset_of_reconnect_info = false; - m_reconnect_info.scheduled_reset = false; - } - - m_heartbeat_timer.reset(); - m_waiting_for_pong = false; - - initiate_ping_delay(now); // Throws - - if (m_client.m_roundtrip_time_handler) - m_client.m_roundtrip_time_handler(m_previous_ping_rtt); // Throws -} - -Session* Connection::find_and_validate_session(session_ident_type session_ident, std::string_view message) noexcept -{ - if (session_ident == 0) { - return nullptr; - } - - auto* sess = get_session(session_ident); - if (REALM_LIKELY(sess)) { - return sess; - } - // Check the history to see if the message received was for a previous session - if (auto it = m_session_history.find(session_ident); it == m_session_history.end()) { - logger.error("Bad session identifier in %1 message, session_ident = %2", message, session_ident); - close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received message %1 for session iden %2 when that session never existed", message, - session_ident)}); - } - else { - logger.error("Received %1 message for closed session, session_ident = %2", message, - session_ident); // Throws - } - return nullptr; -} - -void Connection::receive_error_message(const ProtocolErrorInfo& info, session_ident_type session_ident) -{ - Session* sess = nullptr; - if (session_ident != 0) { - sess = find_and_validate_session(session_ident, "ERROR"); - if (REALM_UNLIKELY(!sess)) { - return; - } - if (auto status = sess->receive_error_message(info); !status.is_ok()) { - close_due_to_protocol_error(std::move(status)); // Throws - return; - } - - if (sess->m_state == Session::Deactivated) { - finish_session_deactivation(sess); - } - return; - } - - logger.info("Received: ERROR \"%1\" (error_code=%2, is_fatal=%3, session_ident=%4, error_action=%5)", - info.message, info.raw_error_code, info.is_fatal, session_ident, - info.server_requests_action); // Throws - - bool known_error_code = bool(get_protocol_error_message(info.raw_error_code)); - if (REALM_LIKELY(known_error_code)) { - ProtocolError error_code = ProtocolError(info.raw_error_code); - if (REALM_LIKELY(!is_session_level_error(error_code))) { - close_due_to_server_side_error(error_code, info); // Throws - return; - } - close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received ERROR message with a non-connection-level error code %1 without a session ident", - info.raw_error_code)}); - } - else { - close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received ERROR message with unknown error code %1", info.raw_error_code)}); - } -} - - -void Connection::receive_query_error_message(int raw_error_code, std::string_view message, int64_t query_version, - session_ident_type session_ident) -{ - if (session_ident == 0) { - return close_due_to_protocol_error( - {ErrorCodes::SyncProtocolInvariantFailed, "Received query error message for session ident 0"}); - } - - if (!is_flx_sync_connection()) { - return close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, - "Received a FLX query error message on a non-FLX sync connection"}); - } - - if (Session* sess = find_and_validate_session(session_ident, "QUERY_ERROR")) { - sess->receive_query_error_message(raw_error_code, message, query_version); - } -} - - -void Connection::receive_ident_message(session_ident_type session_ident, SaltedFileIdent client_file_ident) -{ - Session* sess = find_and_validate_session(session_ident, "IDENT"); - if (REALM_UNLIKELY(!sess)) { - return; - } - - if (auto status = sess->receive_ident_message(client_file_ident); !status.is_ok()) - close_due_to_protocol_error(std::move(status)); // Throws -} - -void Connection::receive_download_message(session_ident_type session_ident, const DownloadMessage& message) -{ - Session* sess = find_and_validate_session(session_ident, "DOWNLOAD"); - if (REALM_UNLIKELY(!sess)) { - return; - } - - if (auto status = sess->receive_download_message(message); !status.is_ok()) { - close_due_to_protocol_error(std::move(status)); - } -} - -void Connection::receive_mark_message(session_ident_type session_ident, request_ident_type request_ident) -{ - Session* sess = find_and_validate_session(session_ident, "MARK"); - if (REALM_UNLIKELY(!sess)) { - return; - } - - if (auto status = sess->receive_mark_message(request_ident); !status.is_ok()) - close_due_to_protocol_error(std::move(status)); // Throws -} - - -void Connection::receive_unbound_message(session_ident_type session_ident) -{ - Session* sess = find_and_validate_session(session_ident, "UNBOUND"); - if (REALM_UNLIKELY(!sess)) { - return; - } - - if (auto status = sess->receive_unbound_message(); !status.is_ok()) { - close_due_to_protocol_error(std::move(status)); // Throws - return; - } - - if (sess->m_state == Session::Deactivated) { - finish_session_deactivation(sess); - } -} - - -void Connection::receive_test_command_response(session_ident_type session_ident, request_ident_type request_ident, - std::string_view body) -{ - Session* sess = find_and_validate_session(session_ident, "TEST_COMMAND"); - if (REALM_UNLIKELY(!sess)) { - return; - } - - if (auto status = sess->receive_test_command_response(request_ident, body); !status.is_ok()) { - close_due_to_protocol_error(std::move(status)); - } -} - - -void Connection::receive_server_log_message(session_ident_type session_ident, util::Logger::Level level, - std::string_view message) -{ - if (session_ident != 0) { - if (auto sess = get_session(session_ident)) { - sess->logger.log(LogCategory::session, level, "Server log: %1", message); - return; - } - - logger.log(util::LogCategory::session, level, "Server log for unknown session %1: %2", session_ident, - message); - return; - } - - logger.log(level, "Server log: %1", message); -} - - -void Connection::receive_appservices_request_id(std::string_view coid) -{ - if (coid.empty() || !m_appservices_coid.empty()) { - return; - } - m_appservices_coid = coid; - logger.log(util::LogCategory::session, util::LogCategory::Level::info, - "Connected to app services with request id: \"%1\". Further log entries for this connection will be " - "prefixed with \"Connection[%2:%1]\" instead of \"Connection[%2]\"", - m_appservices_coid, m_ident); - logger.base_logger = make_logger(m_ident, m_appservices_coid, get_client().logger.base_logger); - - for (auto& [ident, sess] : m_sessions) { - sess->logger.base_logger = Session::make_logger(ident, logger.base_logger); - } -} - - -void Connection::handle_protocol_error(Status status) -{ - close_due_to_protocol_error(std::move(status)); -} - - -// Sessions are guaranteed to be granted the opportunity to send a message in -// the order that they enlist. Note that this is important to ensure -// nonoverlapping communication with the server for consecutive sessions -// associated with the same Realm file. -// -// CAUTION: The specified session may get destroyed before this function -// returns, but only if its Session::send_message() puts it into the Deactivated -// state. -void Connection::enlist_to_send(Session* sess) -{ - REALM_ASSERT_EX(m_state == ConnectionState::connected, m_state); - m_sessions_enlisted_to_send.push_back(sess); // Throws - if (!m_sending) - send_next_message(); // Throws -} - - -std::string Connection::get_active_appservices_connection_id() -{ - return m_appservices_coid; -} - -void Session::cancel_resumption_delay() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - - if (!m_suspended) - return; - - m_suspended = false; - - logger.debug("Resumed"); // Throws - - if (unbind_process_complete()) - initiate_rebind(); // Throws - - try { - process_pending_flx_bootstrap(); // throws - } - catch (const IntegrationException& error) { - on_integration_failure(error); - } - catch (...) { - on_integration_failure(IntegrationException(exception_to_status())); - } - - m_conn.one_more_active_unsuspended_session(); // Throws - if (m_try_again_activation_timer) { - m_try_again_activation_timer.reset(); - } - - on_resumed(); // Throws -} - - -void Session::gather_pending_compensating_writes(util::Span changesets, - std::vector* out) -{ - if (m_pending_compensating_write_errors.empty() || changesets.empty()) { - return; - } - -#ifdef REALM_DEBUG - REALM_ASSERT_DEBUG( - std::is_sorted(m_pending_compensating_write_errors.begin(), m_pending_compensating_write_errors.end(), - [](const ProtocolErrorInfo& lhs, const ProtocolErrorInfo& rhs) { - REALM_ASSERT_DEBUG(lhs.compensating_write_server_version.has_value()); - REALM_ASSERT_DEBUG(rhs.compensating_write_server_version.has_value()); - return *lhs.compensating_write_server_version < *rhs.compensating_write_server_version; - })); -#endif - - while (!m_pending_compensating_write_errors.empty() && - *m_pending_compensating_write_errors.front().compensating_write_server_version <= - changesets.back().version) { - auto& cur_error = m_pending_compensating_write_errors.front(); - REALM_ASSERT_3(*cur_error.compensating_write_server_version, >=, changesets.front().version); - out->push_back(std::move(cur_error)); - m_pending_compensating_write_errors.pop_front(); - } -} - - -void Session::integrate_changesets(const SyncProgress& progress, std::uint_fast64_t downloadable_bytes, - const ReceivedChangesets& received_changesets, VersionInfo& version_info, - DownloadBatchState download_batch_state) -{ - auto& history = get_history(); - if (received_changesets.empty()) { - if (download_batch_state == DownloadBatchState::MoreToCome) { - throw IntegrationException(ErrorCodes::SyncProtocolInvariantFailed, - "received empty download message that was not the last in batch", - ProtocolError::bad_progress); - } - history.set_sync_progress(progress, downloadable_bytes, version_info); // Throws - return; - } - - std::vector pending_compensating_write_errors; - auto transact = get_db()->start_read(); - history.integrate_server_changesets( - progress, downloadable_bytes, received_changesets, version_info, download_batch_state, logger, transact, - [&](const Transaction&, util::Span changesets) { - gather_pending_compensating_writes(changesets, &pending_compensating_write_errors); - }); // Throws - if (received_changesets.size() == 1) { - logger.debug("1 remote changeset integrated, producing client version %1", - version_info.sync_version.version); // Throws - } - else { - logger.debug("%2 remote changesets integrated, producing client version %1", - version_info.sync_version.version, received_changesets.size()); // Throws - } - - for (const auto& pending_error : pending_compensating_write_errors) { - logger.info("Reporting compensating write for client version %1 in server version %2: %3", - pending_error.compensating_write_rejected_client_version, - *pending_error.compensating_write_server_version, pending_error.message); - try { - on_connection_state_changed( - m_conn.get_state(), - SessionErrorInfo{pending_error, - protocol_error_to_status(static_cast(pending_error.raw_error_code), - pending_error.message)}); - } - catch (...) { - logger.error("Exception thrown while reporting compensating write: %1", exception_to_status()); - } - } -} - - -void Session::on_integration_failure(const IntegrationException& error) -{ - REALM_ASSERT_EX(m_state == Active, m_state); - REALM_ASSERT(!m_client_error && !m_error_to_send); - logger.error("Failed to integrate downloaded changesets: %1", error.to_status()); - - m_client_error = util::make_optional(error); - m_error_to_send = true; - SessionErrorInfo error_info{error.to_status(), IsFatal{false}}; - error_info.server_requests_action = ProtocolErrorInfo::Action::Warning; - // Surface the error to the user otherwise is lost. - on_connection_state_changed(m_conn.get_state(), std::move(error_info)); - - // Since the deactivation process has not been initiated, the UNBIND - // message cannot have been sent unless an ERROR message was received. - REALM_ASSERT(m_suspended || m_error_message_received || !m_unbind_message_sent); - if (m_ident_message_sent && !m_error_message_received && !m_suspended) { - ensure_enlisted_to_send(); // Throws - } -} - -void Session::on_changesets_integrated(version_type client_version, const SyncProgress& progress) -{ - REALM_ASSERT_EX(m_state == Active, m_state); - REALM_ASSERT_3(progress.download.server_version, >=, m_download_progress.server_version); - - m_download_progress = progress.download; - m_progress = progress; - - if (progress.upload.client_version > m_upload_progress.client_version) - m_upload_progress = progress.upload; - - do_recognize_sync_version(client_version); // Allows upload process to resume - - check_for_download_completion(); // Throws - - // If the client migrated from PBS to FLX, create subscriptions when new tables are received from server. - if (auto migration_store = get_migration_store(); migration_store && m_is_flx_sync_session) { - auto& flx_subscription_store = *get_flx_subscription_store(); - get_migration_store()->create_subscriptions(flx_subscription_store); - } - - // Since the deactivation process has not been initiated, the UNBIND - // message cannot have been sent unless an ERROR message was received. - REALM_ASSERT(m_suspended || m_error_message_received || !m_unbind_message_sent); - if (m_ident_message_sent && !m_error_message_received && !m_suspended) { - ensure_enlisted_to_send(); // Throws - } -} - - -Session::~Session() -{ - // REALM_ASSERT_EX(m_state == Unactivated || m_state == Deactivated, m_state); -} - - -std::shared_ptr Session::make_logger(session_ident_type ident, - std::shared_ptr base_logger) -{ - auto prefix = util::format("Session[%1]: ", ident); - return std::make_shared(util::LogCategory::session, std::move(prefix), - std::move(base_logger)); -} - -void Session::activate() -{ - REALM_ASSERT_EX(m_state == Unactivated, m_state); - - logger.debug("Activating"); // Throws - - if (REALM_LIKELY(!get_client().is_dry_run())) { - bool file_exists = util::File::exists(get_realm_path()); - - logger.info("client_reset_config = %1, Realm exists = %2, upload messages allowed = %3", - get_client_reset_config().has_value(), file_exists, upload_messages_allowed() ? "yes" : "no"); - get_history().get_status(m_last_version_available, m_client_file_ident, m_progress); // Throws - } - logger.debug("client_file_ident = %1, client_file_ident_salt = %2", m_client_file_ident.ident, - m_client_file_ident.salt); // Throws - m_upload_progress = m_progress.upload; - m_download_progress = m_progress.download; - REALM_ASSERT_3(m_last_version_available, >=, m_progress.upload.client_version); - init_progress_handler(); - - logger.debug("last_version_available = %1", m_last_version_available); // Throws - logger.debug("progress_download_server_version = %1", m_progress.download.server_version); // Throws - logger.debug("progress_download_client_version = %1", - m_progress.download.last_integrated_client_version); // Throws - logger.debug("progress_upload_server_version = %1", m_progress.upload.last_integrated_server_version); // Throws - logger.debug("progress_upload_client_version = %1", m_progress.upload.client_version); // Throws - - reset_protocol_state(); - m_state = Active; - - call_debug_hook(SyncClientHookEvent::SessionActivating); - - REALM_ASSERT(!m_suspended); - m_conn.one_more_active_unsuspended_session(); // Throws - - try { - process_pending_flx_bootstrap(); // throws - } - catch (const IntegrationException& error) { - on_integration_failure(error); - } - catch (...) { - on_integration_failure(IntegrationException(exception_to_status())); - } - - // Checks if there is a pending client reset - handle_pending_client_reset_acknowledgement(); -} - - -// The caller (Connection) must discard the session if the session has become -// deactivated upon return. -void Session::initiate_deactivation() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - - logger.debug("Initiating deactivation"); // Throws - - m_state = Deactivating; - - if (!m_suspended) - m_conn.one_less_active_unsuspended_session(); // Throws - - if (m_enlisted_to_send) { - REALM_ASSERT(!unbind_process_complete()); - return; - } - - // Deactivate immediately if the BIND message has not yet been sent and the - // session is not enlisted to send, or if the unbinding process has already - // completed. - if (!m_bind_message_sent || unbind_process_complete()) { - complete_deactivation(); // Throws - // Life cycle state is now Deactivated - return; - } - - // Ready to send the UNBIND message, if it has not already been sent - if (!m_unbind_message_sent) { - enlist_to_send(); // Throws - return; - } -} - - -void Session::complete_deactivation() -{ - REALM_ASSERT_EX(m_state == Deactivating, m_state); - m_state = Deactivated; - - logger.debug("Deactivation completed"); // Throws -} - - -// Called by the associated Connection object when this session is granted an -// opportunity to send a message. -// -// The caller (Connection) must discard the session if the session has become -// deactivated upon return. -void Session::send_message() -{ - REALM_ASSERT_EX(m_state == Active || m_state == Deactivating, m_state); - REALM_ASSERT(m_enlisted_to_send); - m_enlisted_to_send = false; - if (m_state == Deactivating || m_error_message_received || m_suspended) { - // Deactivation has been initiated. If the UNBIND message has not been - // sent yet, there is no point in sending it. Instead, we can let the - // deactivation process complete. - if (!m_bind_message_sent) { - return complete_deactivation(); // Throws - // Life cycle state is now Deactivated - } - - // Session life cycle state is Deactivating or the unbinding process has - // been initiated by a session specific ERROR message - if (!m_unbind_message_sent) - send_unbind_message(); // Throws - return; - } - - // Session life cycle state is Active and the unbinding process has - // not been initiated - REALM_ASSERT(!m_unbind_message_sent); - - if (!m_bind_message_sent) - return send_bind_message(); // Throws - - // Pending test commands can be sent any time after the BIND message is sent - const auto has_pending_test_command = std::any_of(m_pending_test_commands.begin(), m_pending_test_commands.end(), - [](const PendingTestCommand& command) { - return command.pending; - }); - if (has_pending_test_command) { - return send_test_command_message(); - } - - if (!m_ident_message_sent) { - if (have_client_file_ident()) - send_ident_message(); // Throws - return; - } - - if (m_error_to_send) - return send_json_error_message(); // Throws - - // Stop sending upload, mark and query messages when the client detects an error. - if (m_client_error) { - return; - } - - if (m_target_download_mark > m_last_download_mark_sent) - return send_mark_message(); // Throws - - auto is_upload_allowed = [&]() -> bool { - if (!m_is_flx_sync_session) { - return true; - } - - auto migration_store = get_migration_store(); - if (!migration_store) { - return true; - } - - auto sentinel_query_version = migration_store->get_sentinel_subscription_set_version(); - if (!sentinel_query_version) { - return true; - } - - // Do not allow upload if the last query sent is the sentinel one used by the migration store. - return m_last_sent_flx_query_version != *sentinel_query_version; - }; - - if (!is_upload_allowed()) { - return; - } - - auto check_pending_flx_version = [&]() -> bool { - if (!m_is_flx_sync_session) { - return false; - } - - if (m_delay_uploads) { - return false; - } - - m_pending_flx_sub_set = get_flx_subscription_store()->get_next_pending_version(m_last_sent_flx_query_version); - - if (!m_pending_flx_sub_set) { - return false; - } - - // Send QUERY messages when the upload progress client version reaches the snapshot version - // of a pending subscription - return m_upload_progress.client_version >= m_pending_flx_sub_set->snapshot_version; - }; - - if (check_pending_flx_version()) { - return send_query_change_message(); // throws - } - - if (!m_delay_uploads && (m_last_version_available > m_upload_progress.client_version)) { - return send_upload_message(); // Throws - } -} - - -void Session::send_bind_message() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - - session_ident_type session_ident = m_ident; - // Request an ident if we don't already have one and there isn't a pending client reset diff - // The file ident can be 0 when a client reset is being performed if a brand new local realm - // has been opened (or using Async open) and a FLX/PBS migration occurs when first connecting - // to the server. - bool need_client_file_ident = !have_client_file_ident() && !get_client_reset_config(); - const bool is_subserver = false; - - ClientProtocol& protocol = m_conn.get_client_protocol(); - int protocol_version = m_conn.get_negotiated_protocol_version(); - OutputBuffer& out = m_conn.get_output_buffer(); - // Discard the token since it's ignored by the server. - std::string empty_access_token; - if (m_is_flx_sync_session) { - nlohmann::json bind_json_data; - if (auto migrated_partition = get_migration_store()->get_migrated_partition()) { - bind_json_data["migratedPartition"] = *migrated_partition; - } - bind_json_data["sessionReason"] = static_cast(get_session_reason()); - auto schema_version = get_schema_version(); - // Send 0 if schema is not versioned. - bind_json_data["schemaVersion"] = schema_version != uint64_t(-1) ? schema_version : 0; - if (logger.would_log(util::Logger::Level::debug)) { - std::string json_data_dump; - if (!bind_json_data.empty()) { - json_data_dump = bind_json_data.dump(); - } - logger.debug( - "Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, json_data=\"%4\")", - session_ident, need_client_file_ident, is_subserver, json_data_dump); - } - protocol.make_flx_bind_message(protocol_version, out, session_ident, bind_json_data, empty_access_token, - need_client_file_ident, is_subserver); // Throws - } - else { - std::string server_path = get_virt_path(); - logger.debug("Sending: BIND(session_ident=%1, need_client_file_ident=%2, is_subserver=%3, server_path=%4)", - session_ident, need_client_file_ident, is_subserver, server_path); - protocol.make_pbs_bind_message(protocol_version, out, session_ident, server_path, empty_access_token, - need_client_file_ident, is_subserver); // Throws - } - m_conn.initiate_write_message(out, this); // Throws - - m_bind_message_sent = true; - call_debug_hook(SyncClientHookEvent::BindMessageSent); - - // If there is a pending client reset diff, process that when the BIND message has - // been sent successfully and wait before sending the IDENT message. Otherwise, - // ready to send the IDENT message if the file identifier pair is already available. - if (!need_client_file_ident) - enlist_to_send(); // Throws -} - - -void Session::send_ident_message() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - REALM_ASSERT(m_bind_message_sent); - REALM_ASSERT(!m_unbind_message_sent); - REALM_ASSERT(have_client_file_ident()); - - ClientProtocol& protocol = m_conn.get_client_protocol(); - OutputBuffer& out = m_conn.get_output_buffer(); - session_ident_type session_ident = m_ident; - - if (m_is_flx_sync_session) { - const auto active_query_set = get_flx_subscription_store()->get_active(); - const auto active_query_body = active_query_set.to_ext_json(); - logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " - "scan_server_version=%3, scan_client_version=%4, latest_server_version=%5, " - "latest_server_version_salt=%6, query_version=%7, query_size=%8, query=\"%9\")", - m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, - m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, - m_progress.latest_server_version.salt, active_query_set.version(), active_query_body.size(), - active_query_body); // Throws - protocol.make_flx_ident_message(out, session_ident, m_client_file_ident, m_progress, - active_query_set.version(), active_query_body); // Throws - m_last_sent_flx_query_version = active_query_set.version(); - } - else { - logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " - "scan_server_version=%3, scan_client_version=%4, latest_server_version=%5, " - "latest_server_version_salt=%6)", - m_client_file_ident.ident, m_client_file_ident.salt, m_progress.download.server_version, - m_progress.download.last_integrated_client_version, m_progress.latest_server_version.version, - m_progress.latest_server_version.salt); // Throws - protocol.make_pbs_ident_message(out, session_ident, m_client_file_ident, m_progress); // Throws - } - m_conn.initiate_write_message(out, this); // Throws - - m_ident_message_sent = true; - call_debug_hook(SyncClientHookEvent::IdentMessageSent); - - // Other messages may be waiting to be sent - enlist_to_send(); // Throws -} - -void Session::send_query_change_message() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - REALM_ASSERT(m_ident_message_sent); - REALM_ASSERT(!m_unbind_message_sent); - REALM_ASSERT(m_pending_flx_sub_set); - REALM_ASSERT_3(m_pending_flx_sub_set->query_version, >, m_last_sent_flx_query_version); - - if (REALM_UNLIKELY(get_client().is_dry_run())) { - return; - } - - auto sub_store = get_flx_subscription_store(); - auto latest_sub_set = sub_store->get_by_version(m_pending_flx_sub_set->query_version); - auto latest_queries = latest_sub_set.to_ext_json(); - logger.debug("Sending: QUERY(query_version=%1, query_size=%2, query=\"%3\", snapshot_version=%4)", - latest_sub_set.version(), latest_queries.size(), latest_queries, latest_sub_set.snapshot_version()); - - OutputBuffer& out = m_conn.get_output_buffer(); - session_ident_type session_ident = get_ident(); - ClientProtocol& protocol = m_conn.get_client_protocol(); - protocol.make_query_change_message(out, session_ident, latest_sub_set.version(), latest_queries); - m_conn.initiate_write_message(out, this); - - m_last_sent_flx_query_version = latest_sub_set.version(); - - request_download_completion_notification(); -} - -void Session::send_upload_message() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - REALM_ASSERT(m_ident_message_sent); - REALM_ASSERT(!m_unbind_message_sent); - - if (REALM_UNLIKELY(get_client().is_dry_run())) - return; - - version_type target_upload_version = m_last_version_available; - if (m_pending_flx_sub_set) { - REALM_ASSERT(m_is_flx_sync_session); - target_upload_version = m_pending_flx_sub_set->snapshot_version; - } - - bool server_version_to_ack = - m_upload_progress.last_integrated_server_version < m_download_progress.server_version; - - std::vector uploadable_changesets; - version_type locked_server_version = 0; - get_history().find_uploadable_changesets(m_upload_progress, target_upload_version, uploadable_changesets, - locked_server_version); // Throws - - if (uploadable_changesets.empty()) { - // Nothing more to upload right now if: - // 1. We need to limit upload up to some version other than the last client version - // available and there are no changes to upload - // 2. There are no changes to upload and no server version(s) to acknowledge - if (m_pending_flx_sub_set || !server_version_to_ack) { - logger.trace("Empty UPLOAD was skipped (progress_client_version=%1, progress_server_version=%2)", - m_upload_progress.client_version, m_upload_progress.last_integrated_server_version); - // Other messages may be waiting to be sent - return enlist_to_send(); // Throws - } - } - - if (m_pending_flx_sub_set && target_upload_version < m_last_version_available) { - logger.trace("Limiting UPLOAD message up to version %1 to send QUERY version %2", - m_pending_flx_sub_set->snapshot_version, m_pending_flx_sub_set->query_version); - } - - version_type progress_client_version = m_upload_progress.client_version; - version_type progress_server_version = m_upload_progress.last_integrated_server_version; - - if (!upload_messages_allowed()) { - logger.trace("UPLOAD not allowed (progress_client_version=%1, progress_server_version=%2, " - "locked_server_version=%3, num_changesets=%4)", - progress_client_version, progress_server_version, locked_server_version, - uploadable_changesets.size()); // Throws - // Other messages may be waiting to be sent - return enlist_to_send(); // Throws - } - - logger.debug("Sending: UPLOAD(progress_client_version=%1, progress_server_version=%2, " - "locked_server_version=%3, num_changesets=%4)", - progress_client_version, progress_server_version, locked_server_version, - uploadable_changesets.size()); // Throws - - ClientProtocol& protocol = m_conn.get_client_protocol(); - ClientProtocol::UploadMessageBuilder upload_message_builder = protocol.make_upload_message_builder(); // Throws - - for (const UploadChangeset& uc : uploadable_changesets) { - logger.debug(util::LogCategory::changeset, - "Fetching changeset for upload (client_version=%1, server_version=%2, " - "changeset_size=%3, origin_timestamp=%4, origin_file_ident=%5)", - uc.progress.client_version, uc.progress.last_integrated_server_version, uc.changeset.size(), - uc.origin_timestamp, uc.origin_file_ident); // Throws - if (logger.would_log(util::Logger::Level::trace)) { - BinaryData changeset_data = uc.changeset.get_first_chunk(); - if (changeset_data.size() < 1024) { - logger.trace(util::LogCategory::changeset, "Changeset: %1", - _impl::clamped_hex_dump(changeset_data)); // Throws - } - else { - logger.trace(util::LogCategory::changeset, "Changeset(comp): %1 %2", changeset_data.size(), - protocol.compressed_hex_dump(changeset_data)); - } - -#if REALM_DEBUG - ChunkedBinaryInputStream in{changeset_data}; - Changeset log; - try { - parse_changeset(in, log); - std::stringstream ss; - log.print(ss); - logger.trace(util::LogCategory::changeset, "Changeset (parsed):\n%1", ss.str()); - } - catch (const BadChangesetError& err) { - logger.error(util::LogCategory::changeset, "Unable to parse changeset: %1", err.what()); - } -#endif - } - - { - upload_message_builder.add_changeset(uc.progress.client_version, - uc.progress.last_integrated_server_version, uc.origin_timestamp, - uc.origin_file_ident, - uc.changeset); // Throws - } - } - - int protocol_version = m_conn.get_negotiated_protocol_version(); - OutputBuffer& out = m_conn.get_output_buffer(); - session_ident_type session_ident = get_ident(); - upload_message_builder.make_upload_message(protocol_version, out, session_ident, progress_client_version, - progress_server_version, - locked_server_version); // Throws - m_conn.initiate_write_message(out, this); // Throws - - call_debug_hook(SyncClientHookEvent::UploadMessageSent); - - // Other messages may be waiting to be sent - enlist_to_send(); // Throws -} - - -void Session::send_mark_message() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - REALM_ASSERT(m_ident_message_sent); - REALM_ASSERT(!m_unbind_message_sent); - REALM_ASSERT_3(m_target_download_mark, >, m_last_download_mark_sent); - - request_ident_type request_ident = m_target_download_mark; - logger.debug("Sending: MARK(request_ident=%1)", request_ident); // Throws - - ClientProtocol& protocol = m_conn.get_client_protocol(); - OutputBuffer& out = m_conn.get_output_buffer(); - session_ident_type session_ident = get_ident(); - protocol.make_mark_message(out, session_ident, request_ident); // Throws - m_conn.initiate_write_message(out, this); // Throws - - m_last_download_mark_sent = request_ident; - - // Other messages may be waiting to be sent - enlist_to_send(); // Throws -} - - -void Session::send_unbind_message() -{ - REALM_ASSERT_EX(m_state == Deactivating || m_error_message_received || m_suspended, m_state); - REALM_ASSERT(m_bind_message_sent); - REALM_ASSERT(!m_unbind_message_sent); - - logger.debug("Sending: UNBIND"); // Throws - - ClientProtocol& protocol = m_conn.get_client_protocol(); - OutputBuffer& out = m_conn.get_output_buffer(); - session_ident_type session_ident = get_ident(); - protocol.make_unbind_message(out, session_ident); // Throws - m_conn.initiate_write_message(out, this); // Throws - - m_unbind_message_sent = true; -} - - -void Session::send_json_error_message() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - REALM_ASSERT(m_ident_message_sent); - REALM_ASSERT(!m_unbind_message_sent); - REALM_ASSERT(m_error_to_send); - REALM_ASSERT(m_client_error); - - ClientProtocol& protocol = m_conn.get_client_protocol(); - OutputBuffer& out = m_conn.get_output_buffer(); - session_ident_type session_ident = get_ident(); - auto protocol_error = m_client_error->error_for_server; - - auto message = util::format("%1", m_client_error->to_status()); - logger.info("Sending: ERROR \"%1\" (error_code=%2, session_ident=%3)", message, static_cast(protocol_error), - session_ident); // Throws - - nlohmann::json error_body_json; - error_body_json["message"] = std::move(message); - protocol.make_json_error_message(out, session_ident, static_cast(protocol_error), - error_body_json.dump()); // Throws - m_conn.initiate_write_message(out, this); // Throws - - m_error_to_send = false; - enlist_to_send(); // Throws -} - - -void Session::send_test_command_message() -{ - REALM_ASSERT_EX(m_state == Active, m_state); - - auto it = std::find_if(m_pending_test_commands.begin(), m_pending_test_commands.end(), - [](const PendingTestCommand& command) { - return command.pending; - }); - REALM_ASSERT(it != m_pending_test_commands.end()); - - ClientProtocol& protocol = m_conn.get_client_protocol(); - OutputBuffer& out = m_conn.get_output_buffer(); - auto session_ident = get_ident(); - - logger.info("Sending: TEST_COMMAND \"%1\" (session_ident=%2, request_ident=%3)", it->body, session_ident, it->id); - protocol.make_test_command_message(out, session_ident, it->id, it->body); - - m_conn.initiate_write_message(out, this); // Throws; - it->pending = false; - - enlist_to_send(); -} - -bool Session::client_reset_if_needed() -{ - // Even if we end up not actually performing a client reset, consume the - // config to ensure that the resources it holds are released - auto client_reset_config = std::exchange(get_client_reset_config(), std::nullopt); - if (!client_reset_config) { - return false; - } - - // Save a copy of the status and action in case an error/exception occurs - Status cr_status = client_reset_config->error; - ProtocolErrorInfo::Action cr_action = client_reset_config->action; - - try { - // The file ident from the fresh realm will be copied over to the local realm - bool did_reset = client_reset::perform_client_reset(logger, *get_db(), std::move(*client_reset_config), - get_flx_subscription_store()); - - call_debug_hook(SyncClientHookEvent::ClientResetMergeComplete); - if (!did_reset) { - return false; - } - } - catch (const std::exception& e) { - auto err_msg = util::format("A fatal error occurred during '%1' client reset diff for %2: '%3'", cr_action, - cr_status, e.what()); - logger.error(err_msg.c_str()); - SessionErrorInfo err_info(Status{ErrorCodes::AutoClientResetFailed, err_msg}, IsFatal{true}); - suspend(err_info); - return false; - } - - // The fresh Realm has been used to reset the state - logger.debug("Client reset is completed, path = %1", get_realm_path()); // Throws - - // Update the version, file ident and progress info after the client reset diff is done - get_history().get_status(m_last_version_available, m_client_file_ident, m_progress); // Throws - // Print the version/progress information before performing the asserts - logger.debug("client_file_ident = %1, client_file_ident_salt = %2", m_client_file_ident.ident, - m_client_file_ident.salt); // Throws - logger.debug("last_version_available = %1", m_last_version_available); // Throws - logger.debug("upload_progress_client_version = %1, upload_progress_server_version = %2", - m_progress.upload.client_version, - m_progress.upload.last_integrated_server_version); // Throws - logger.debug("download_progress_client_version = %1, download_progress_server_version = %2", - m_progress.download.last_integrated_client_version, - m_progress.download.server_version); // Throws - - REALM_ASSERT_EX(m_progress.download.last_integrated_client_version == 0, - m_progress.download.last_integrated_client_version); - REALM_ASSERT_EX(m_progress.upload.client_version == 0, m_progress.upload.client_version); - - m_upload_progress = m_progress.upload; - m_download_progress = m_progress.download; - init_progress_handler(); - // In recovery mode, there may be new changesets to upload and nothing left to download. - // In FLX DiscardLocal mode, there may be new commits due to subscription handling. - // For both, we want to allow uploads again without needing external changes to download first. - m_delay_uploads = false; - - // Checks if there is a pending client reset - handle_pending_client_reset_acknowledgement(); - - // If a migration or rollback is in progress, mark it complete when client reset is completed. - if (auto migration_store = get_migration_store()) { - migration_store->complete_migration_or_rollback(); - } - - return true; -} - -Status Session::receive_ident_message(SaltedFileIdent client_file_ident) -{ - logger.debug("Received: IDENT(client_file_ident=%1, client_file_ident_salt=%2)", client_file_ident.ident, - client_file_ident.salt); // Throws - - // Ignore the message if the deactivation process has been initiated, - // because in that case, the associated Realm and SessionWrapper must - // not be accessed any longer. - if (m_state != Active) - return Status::OK(); // Success - - bool legal_at_this_time = (m_bind_message_sent && !have_client_file_ident() && !m_error_message_received && - !m_unbound_message_received); - if (REALM_UNLIKELY(!legal_at_this_time)) { - return {ErrorCodes::SyncProtocolInvariantFailed, "Received IDENT message when it was not legal"}; - } - if (REALM_UNLIKELY(client_file_ident.ident < 1)) { - return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier in IDENT message"}; - } - if (REALM_UNLIKELY(client_file_ident.salt == 0)) { - return {ErrorCodes::SyncProtocolInvariantFailed, "Bad client file identifier salt in IDENT message"}; - } - - m_client_file_ident = client_file_ident; - - if (REALM_UNLIKELY(get_client().is_dry_run())) { - // Ready to send the IDENT message - ensure_enlisted_to_send(); // Throws - return Status::OK(); // Success - } - - get_history().set_client_file_ident(client_file_ident, - m_fix_up_object_ids); // Throws - m_progress.download.last_integrated_client_version = 0; - m_progress.upload.client_version = 0; - - // Ready to send the IDENT message - ensure_enlisted_to_send(); // Throws - return Status::OK(); // Success -} - -Status Session::receive_download_message(const DownloadMessage& message) -{ - // Ignore the message if the deactivation process has been initiated, - // because in that case, the associated Realm and SessionWrapper must - // not be accessed any longer. - if (m_state != Active) - return Status::OK(); - - bool is_flx = m_conn.is_flx_sync_connection(); - int64_t query_version = is_flx ? *message.query_version : 0; - - if (!is_flx || query_version > 0) - enable_progress_notifications(); - - auto&& progress = message.progress; - if (is_flx) { - logger.debug("Received: DOWNLOAD(download_server_version=%1, download_client_version=%2, " - "latest_server_version=%3, latest_server_version_salt=%4, " - "upload_client_version=%5, upload_server_version=%6, progress_estimate=%7, " - "batch_state=%8, query_version=%9, num_changesets=%10, ...)", - progress.download.server_version, progress.download.last_integrated_client_version, - progress.latest_server_version.version, progress.latest_server_version.salt, - progress.upload.client_version, progress.upload.last_integrated_server_version, - message.downloadable.as_estimate(), message.batch_state, query_version, - message.changesets.size()); // Throws - } - else { - logger.debug("Received: DOWNLOAD(download_server_version=%1, download_client_version=%2, " - "latest_server_version=%3, latest_server_version_salt=%4, " - "upload_client_version=%5, upload_server_version=%6, " - "downloadable_bytes=%7, num_changesets=%8, ...)", - progress.download.server_version, progress.download.last_integrated_client_version, - progress.latest_server_version.version, progress.latest_server_version.salt, - progress.upload.client_version, progress.upload.last_integrated_server_version, - message.downloadable.as_bytes(), message.changesets.size()); // Throws - } - - // Ignore download messages when the client detects an error. This is to prevent transforming the same bad - // changeset over and over again. - if (m_client_error) { - logger.debug("Ignoring download message because the client detected an integration error"); - return Status::OK(); - } - - bool legal_at_this_time = (m_ident_message_sent && !m_error_message_received && !m_unbound_message_received); - if (REALM_UNLIKELY(!legal_at_this_time)) { - return {ErrorCodes::SyncProtocolInvariantFailed, "Received DOWNLOAD message when it was not legal"}; - } - if (auto status = check_received_sync_progress(progress); REALM_UNLIKELY(!status.is_ok())) { - logger.error("Bad sync progress received (%1)", status); - return status; - } - - version_type server_version = m_progress.download.server_version; - version_type last_integrated_client_version = m_progress.download.last_integrated_client_version; - for (const RemoteChangeset& changeset : message.changesets) { - // Check that per-changeset server version is strictly increasing, except in FLX sync where the server - // version must be increasing, but can stay the same during bootstraps. - bool good_server_version = m_is_flx_sync_session ? (changeset.remote_version >= server_version) - : (changeset.remote_version > server_version); - // Each server version cannot be greater than the one in the header of the download message. - good_server_version = good_server_version && (changeset.remote_version <= progress.download.server_version); - if (!good_server_version) { - return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Bad server version in changeset header (DOWNLOAD) (%1, %2, %3)", - changeset.remote_version, server_version, progress.download.server_version)}; - } - server_version = changeset.remote_version; - - // Check that per-changeset last integrated client version is "weakly" - // increasing. - bool good_client_version = - (changeset.last_integrated_local_version >= last_integrated_client_version && - changeset.last_integrated_local_version <= progress.download.last_integrated_client_version); - if (!good_client_version) { - return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Bad last integrated client version in changeset header (DOWNLOAD) " - "(%1, %2, %3)", - changeset.last_integrated_local_version, last_integrated_client_version, - progress.download.last_integrated_client_version)}; - } - last_integrated_client_version = changeset.last_integrated_local_version; - // Server shouldn't send our own changes, and zero is not a valid client - // file identifier. - bool good_file_ident = - (changeset.origin_file_ident > 0 && changeset.origin_file_ident != m_client_file_ident.ident); - if (!good_file_ident) { - return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Bad origin file identifier in changeset header (DOWNLOAD)", - changeset.origin_file_ident)}; - } - } - - auto hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageReceived, progress, query_version, - message.batch_state, message.changesets.size()); - if (hook_action == SyncClientHookAction::EarlyReturn) { - return Status::OK(); - } - REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); - - if (process_flx_bootstrap_message(message)) { - clear_resumption_delay_state(); - return Status::OK(); - } - - initiate_integrate_changesets(message.downloadable.as_bytes(), message.batch_state, progress, - message.changesets); // Throws - - hook_action = call_debug_hook(SyncClientHookEvent::DownloadMessageIntegrated, progress, query_version, - message.batch_state, message.changesets.size()); - if (hook_action == SyncClientHookAction::EarlyReturn) { - return Status::OK(); - } - REALM_ASSERT_EX(hook_action == SyncClientHookAction::NoAction, hook_action); - - // When we receive a DOWNLOAD message successfully, we can clear the backoff timer value used to reconnect - // after a retryable session error. - clear_resumption_delay_state(); - return Status::OK(); -} - -Status Session::receive_mark_message(request_ident_type request_ident) -{ - logger.debug("Received: MARK(request_ident=%1)", request_ident); // Throws - - // Ignore the message if the deactivation process has been initiated, - // because in that case, the associated Realm and SessionWrapper must - // not be accessed any longer. - if (m_state != Active) - return Status::OK(); // Success - - bool legal_at_this_time = (m_ident_message_sent && !m_error_message_received && !m_unbound_message_received); - if (REALM_UNLIKELY(!legal_at_this_time)) { - return {ErrorCodes::SyncProtocolInvariantFailed, "Received MARK message when it was not legal"}; - } - bool good_request_ident = - (request_ident <= m_last_download_mark_sent && request_ident > m_last_download_mark_received); - if (REALM_UNLIKELY(!good_request_ident)) { - return { - ErrorCodes::SyncProtocolInvariantFailed, - util::format( - "Received MARK message with invalid request identifer (last mark sent: %1 last mark received: %2)", - m_last_download_mark_sent, m_last_download_mark_received)}; - } - - m_server_version_at_last_download_mark = m_progress.download.server_version; - m_last_download_mark_received = request_ident; - check_for_download_completion(); // Throws - - return Status::OK(); // Success -} - - -// The caller (Connection) must discard the session if the session has become -// deactivated upon return. -Status Session::receive_unbound_message() -{ - logger.debug("Received: UNBOUND"); - - bool legal_at_this_time = (m_unbind_message_sent && !m_error_message_received && !m_unbound_message_received); - if (REALM_UNLIKELY(!legal_at_this_time)) { - return {ErrorCodes::SyncProtocolInvariantFailed, "Received UNBOUND message when it was not legal"}; - } - - // The fact that the UNBIND message has been sent, but an ERROR message has - // not been received, implies that the deactivation process must have been - // initiated, so this session must be in the Deactivating state or the session - // has been suspended because of a client side error. - REALM_ASSERT_EX(m_state == Deactivating || m_suspended, m_state); - - m_unbound_message_received = true; - - // Detect completion of the unbinding process - if (m_unbind_message_send_complete && m_state == Deactivating) { - // The deactivation process completes when the unbinding process - // completes. - complete_deactivation(); // Throws - // Life cycle state is now Deactivated - } - - return Status::OK(); // Success -} - - -void Session::receive_query_error_message(int error_code, std::string_view message, int64_t query_version) -{ - logger.info("Received QUERY_ERROR \"%1\" (error_code=%2, query_version=%3)", message, error_code, query_version); - on_flx_sync_error(query_version, message); // throws -} - -// The caller (Connection) must discard the session if the session has become -// deactivated upon return. -Status Session::receive_error_message(const ProtocolErrorInfo& info) -{ - logger.info("Received: ERROR \"%1\" (error_code=%2, is_fatal=%3, error_action=%4)", info.message, - info.raw_error_code, info.is_fatal, info.server_requests_action); // Throws - - bool legal_at_this_time = (m_bind_message_sent && !m_error_message_received && !m_unbound_message_received); - if (REALM_UNLIKELY(!legal_at_this_time)) { - return {ErrorCodes::SyncProtocolInvariantFailed, "Received ERROR message when it was not legal"}; - } - - auto protocol_error = static_cast(info.raw_error_code); - auto status = protocol_error_to_status(protocol_error, info.message); - if (status != ErrorCodes::UnknownError && REALM_UNLIKELY(!is_session_level_error(protocol_error))) { - return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received ERROR message for session with non-session-level error code %1", - info.raw_error_code)}; - } - - // Can't process debug hook actions once the Session is undergoing deactivation, since - // the SessionWrapper may not be available - if (m_state == Active) { - auto debug_action = call_debug_hook(SyncClientHookEvent::ErrorMessageReceived, &info); - if (debug_action == SyncClientHookAction::EarlyReturn) { - return Status::OK(); - } - } - - // For compensating write errors, we need to defer raising them to the SDK until after the server version - // containing the compensating write has appeared in a download message. - if (status == ErrorCodes::SyncCompensatingWrite) { - // If the client is not active, the compensating writes will not be processed now, but will be - // sent again the next time the client connects - if (m_state == Active) { - REALM_ASSERT(info.compensating_write_server_version.has_value()); - m_pending_compensating_write_errors.push_back(info); - } - return Status::OK(); - } - - if (protocol_error == ProtocolError::schema_version_changed) { - // Enable upload immediately if the session is still active. - if (m_state == Active) { - auto wt = get_db()->start_write(); - _impl::sync_schema_migration::track_sync_schema_migration(*wt, *info.previous_schema_version); - wt->commit(); - // Notify SyncSession a schema migration is required. - on_connection_state_changed(m_conn.get_state(), SessionErrorInfo{info}); - } - // Keep the session active to upload any unsynced changes. - return Status::OK(); - } - - m_error_message_received = true; - suspend(SessionErrorInfo{info, std::move(status)}); - return Status::OK(); -} - -void Session::suspend(const SessionErrorInfo& info) -{ - REALM_ASSERT(!m_suspended); - REALM_ASSERT_EX(m_state == Active || m_state == Deactivating, m_state); - logger.debug("Suspended"); // Throws - - m_suspended = true; - - // Detect completion of the unbinding process - if (m_unbind_message_send_complete && m_error_message_received) { - // The fact that the UNBIND message has been sent, but we are not being suspended because - // we received an ERROR message implies that the deactivation process must - // have been initiated, so this session must be in the Deactivating state. - REALM_ASSERT_EX(m_state == Deactivating, m_state); - - // The deactivation process completes when the unbinding process - // completes. - complete_deactivation(); // Throws - // Life cycle state is now Deactivated - } - - // Notify the application of the suspension of the session if the session is - // still in the Active state - if (m_state == Active) { - call_debug_hook(SyncClientHookEvent::SessionSuspended, &info); - m_conn.one_less_active_unsuspended_session(); // Throws - on_suspended(info); // Throws - } - - if (!info.is_fatal) { - begin_resumption_delay(info); - } - - // Ready to send the UNBIND message, if it has not been sent already - if (!m_unbind_message_sent) - ensure_enlisted_to_send(); // Throws -} - -Status Session::receive_test_command_response(request_ident_type ident, std::string_view body) -{ - logger.info("Received: TEST_COMMAND \"%1\" (session_ident=%2, request_ident=%3)", body, m_ident, ident); - auto it = std::find_if(m_pending_test_commands.begin(), m_pending_test_commands.end(), - [&](const PendingTestCommand& command) { - return command.id == ident; - }); - if (it == m_pending_test_commands.end()) { - return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received test command response for a non-existent ident %1", ident)}; - } - - it->promise.emplace_value(std::string{body}); - m_pending_test_commands.erase(it); - - return Status::OK(); -} - -void Session::begin_resumption_delay(const ProtocolErrorInfo& error_info) -{ - REALM_ASSERT(!m_try_again_activation_timer); - - m_try_again_delay_info.update(static_cast(error_info.raw_error_code), - error_info.resumption_delay_interval); - auto try_again_interval = m_try_again_delay_info.delay_interval(); - if (ProtocolError(error_info.raw_error_code) == ProtocolError::session_closed) { - // FIXME With compensating writes the server sends this error after completing a bootstrap. Doing the - // normal backoff behavior would result in waiting up to 5 minutes in between each query change which is - // not acceptable latency. So for this error code alone, we hard-code a 1 second retry interval. - try_again_interval = std::chrono::milliseconds{1000}; - } - logger.debug("Will attempt to resume session after %1 milliseconds", try_again_interval.count()); - m_try_again_activation_timer = get_client().create_timer(try_again_interval, [this](Status status) { - if (status == ErrorCodes::OperationAborted) - return; - else if (!status.is_ok()) - throw Exception(status); - - m_try_again_activation_timer.reset(); - cancel_resumption_delay(); - }); -} - -void Session::clear_resumption_delay_state() -{ - if (m_try_again_activation_timer) { - logger.debug("Clearing resumption delay state after successful download"); - m_try_again_delay_info.reset(); - } -} - -Status Session::check_received_sync_progress(const SyncProgress& progress) noexcept -{ - const SyncProgress& a = m_progress; - const SyncProgress& b = progress; - std::string message; - if (b.latest_server_version.version < a.latest_server_version.version) { - message = util::format("Latest server version in download messages must be weakly increasing throughout a " - "session (current: %1, received: %2)", - a.latest_server_version.version, b.latest_server_version.version); - } - if (b.upload.client_version < a.upload.client_version) { - message = util::format("Last integrated client version in download messages must be weakly increasing " - "throughout a session (current: %1, received: %2)", - a.upload.client_version, b.upload.client_version); - } - if (b.upload.client_version > m_last_version_available) { - message = util::format("Last integrated client version on server cannot be greater than the latest client " - "version in existence (current: %1, received: %2)", - m_last_version_available, b.upload.client_version); - } - if (b.download.server_version < a.download.server_version) { - message = - util::format("Download cursor must be weakly increasing throughout a session (current: %1, received: %2)", - a.download.server_version, b.download.server_version); - } - if (b.download.server_version > b.latest_server_version.version) { - message = util::format( - "Download cursor cannot be greater than the latest server version in existence (cursor: %1, latest: %2)", - b.download.server_version, b.latest_server_version.version); - } - if (b.download.last_integrated_client_version < a.download.last_integrated_client_version) { - message = util::format( - "Last integrated client version on the server at the position in the server's history of the download " - "cursor must be weakly increasing throughout a session (current: %1, received: %2)", - a.download.last_integrated_client_version, b.download.last_integrated_client_version); - } - if (b.download.last_integrated_client_version > b.upload.client_version) { - message = util::format("Last integrated client version on the server in the position at the server's history " - "of the download cursor cannot be greater than the latest client version integrated " - "on the server (download: %1, upload: %2)", - b.download.last_integrated_client_version, b.upload.client_version); - } - if (b.download.server_version < b.upload.last_integrated_server_version) { - message = util::format( - "The server version of the download cursor cannot be less than the server version integrated in the " - "latest client version acknowledged by the server (download: %1, upload: %2)", - b.download.server_version, b.upload.last_integrated_server_version); - } - - if (message.empty()) { - return Status::OK(); - } - return {ErrorCodes::SyncProtocolInvariantFailed, std::move(message)}; -} - - -void Session::check_for_download_completion() -{ - REALM_ASSERT_3(m_target_download_mark, >=, m_last_download_mark_received); - REALM_ASSERT_3(m_last_download_mark_received, >=, m_last_triggering_download_mark); - if (m_last_download_mark_received == m_last_triggering_download_mark) - return; - if (m_last_download_mark_received < m_target_download_mark) - return; - if (m_download_progress.server_version < m_server_version_at_last_download_mark) - return; - m_last_triggering_download_mark = m_target_download_mark; - if (REALM_UNLIKELY(m_delay_uploads)) { - // Activate the upload process now, and enable immediate reactivation - // after a subsequent fast reconnect. - m_delay_uploads = false; - ensure_enlisted_to_send(); // Throws - } - on_download_completion(); // Throws -} diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp deleted file mode 100644 index c6dd8f97ca3..00000000000 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ /dev/null @@ -1,1542 +0,0 @@ - -#ifndef REALM_NOINST_CLIENT_IMPL_BASE_HPP -#define REALM_NOINST_CLIENT_IMPL_BASE_HPP - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace realm::sync { - -// (protocol, address, port, user_id) -// -// `protocol` is included for convenience, even though it is not strictly part -// of an endpoint. -struct ServerEndpoint { - ProtocolEnvelope envelope; - std::string address; - network::Endpoint::port_type port; - std::string user_id; - SyncServerMode server_mode = SyncServerMode::PBS; - bool is_verified = false; - -private: - auto to_tuple() const - { - // Does not include server_mode because all endpoints for a single Client - // must have the same mode. is_verified is not part of an endpoint's identity. - return std::make_tuple(server_mode, envelope, std::ref(address), port, std::ref(user_id)); - } - -public: - friend inline bool operator==(const ServerEndpoint& lhs, const ServerEndpoint& rhs) - { - return lhs.to_tuple() == rhs.to_tuple(); - } - - friend inline bool operator<(const ServerEndpoint& lhs, const ServerEndpoint& rhs) - { - return lhs.to_tuple() < rhs.to_tuple(); - } -}; - -class SessionWrapper; - -class SessionWrapperStack { -public: - bool empty() const noexcept; - void push(util::bind_ptr) noexcept; - util::bind_ptr pop() noexcept; - void clear() noexcept; - bool erase(SessionWrapper*) noexcept; - SessionWrapperStack() noexcept = default; - ~SessionWrapperStack(); - -private: - SessionWrapper* m_back = nullptr; -}; - -template -struct ErrorBackoffState { - ErrorBackoffState() = default; - explicit ErrorBackoffState(ResumptionDelayInfo default_delay_info, RandomEngine& random_engine) - : default_delay_info(std::move(default_delay_info)) - , m_random_engine(random_engine) - { - } - - void update(ErrorType new_error, std::optional new_delay_info) - { - if (triggering_error && *triggering_error == new_error) { - return; - } - - delay_info = new_delay_info.value_or(default_delay_info); - cur_delay_interval = util::none; - triggering_error = new_error; - } - - void reset() - { - triggering_error = util::none; - cur_delay_interval = util::none; - delay_info = default_delay_info; - } - - std::chrono::milliseconds delay_interval() - { - if (!cur_delay_interval) { - cur_delay_interval = delay_info.resumption_delay_interval; - return jitter_value(*cur_delay_interval); - } - if (*cur_delay_interval == delay_info.max_resumption_delay_interval) { - return jitter_value(delay_info.max_resumption_delay_interval); - } - auto safe_delay_interval = cur_delay_interval->count(); - if (util::int_multiply_with_overflow_detect(safe_delay_interval, - delay_info.resumption_delay_backoff_multiplier)) { - *cur_delay_interval = delay_info.max_resumption_delay_interval; - } - else { - *cur_delay_interval = - std::min(delay_info.max_resumption_delay_interval, std::chrono::milliseconds(safe_delay_interval)); - } - return jitter_value(*cur_delay_interval); - } - - ResumptionDelayInfo default_delay_info; - ResumptionDelayInfo delay_info; - util::Optional cur_delay_interval; - util::Optional triggering_error; - -private: - std::chrono::milliseconds jitter_value(std::chrono::milliseconds value) - { - if (delay_info.delay_jitter_divisor == 0) { - return value; - } - const auto max_jitter = value.count() / delay_info.delay_jitter_divisor; - auto distr = std::uniform_int_distribution(0, max_jitter); - std::chrono::milliseconds randomized_deduction(distr(m_random_engine.get())); - return value - randomized_deduction; - } - - std::reference_wrapper m_random_engine; -}; - -class ClientImpl { -public: - enum class ConnectionTerminationReason; - class Connection; - class Session; - - using port_type = network::Endpoint::port_type; - using OutputBuffer = util::ResettableExpandableBufferOutputStream; - using ClientProtocol = _impl::ClientProtocol; - using RandomEngine = std::mt19937_64; - - /// Per-server endpoint information used to determine reconnect delays. - class ReconnectInfo { - public: - explicit ReconnectInfo(ReconnectMode mode, ResumptionDelayInfo default_delay_info, RandomEngine& random) - : m_reconnect_mode(mode) - , m_backoff_state(default_delay_info, random) - { - } - - void reset() noexcept; - void update(ConnectionTerminationReason reason, std::optional new_delay_info); - std::chrono::milliseconds delay_interval(); - - const std::optional reason() const noexcept - { - return m_backoff_state.triggering_error; - } - - // Set this flag to true to schedule a postponed invocation of reset(). See - // Connection::cancel_reconnect_delay() for details and rationale. - // - // Will be set back to false when a PONG message arrives, and the - // corresponding PING message was sent while `m_scheduled_reset` was - // true. See receive_pong(). - bool scheduled_reset = false; - - private: - ReconnectMode m_reconnect_mode; - ErrorBackoffState m_backoff_state; - }; - - static constexpr milliseconds_type default_connect_timeout = 120000; // 2 minutes - static constexpr milliseconds_type default_connection_linger_time = 30000; // 30 seconds - static constexpr milliseconds_type default_ping_keepalive_period = 60000; // 1 minute - static constexpr milliseconds_type default_pong_keepalive_timeout = 120000; // 2 minutes - static constexpr milliseconds_type default_fast_reconnect_limit = 60000; // 1 minute - - class ForwardingLogger : public util::Logger { - public: - ForwardingLogger(std::shared_ptr logger) - : Logger(logger->get_category(), *logger) - , base_logger(std::move(logger)) - { - } - - void do_log(const util::LogCategory& cat, Level level, const std::string& msg) final - { - Logger::do_log(*base_logger, cat, level, msg); - } - - std::shared_ptr base_logger; - }; - - ForwardingLogger logger; - - ClientImpl(ClientConfig); - ~ClientImpl(); - - static constexpr int get_oldest_supported_protocol_version() noexcept; - - void shutdown() noexcept REQUIRES(!m_mutex, !m_drain_mutex); - - void shutdown_and_wait() REQUIRES(!m_mutex, !m_drain_mutex); - - const std::string& get_user_agent_string() const noexcept; - ReconnectMode get_reconnect_mode() const noexcept; - bool is_dry_run() const noexcept; - - // Functions to post onto the event loop and create an event loop timer using the - // SyncSocketProvider - void post(SyncSocketProvider::FunctionHandler&& handler) REQUIRES(!m_drain_mutex); - void post(util::UniqueFunction&& handler) REQUIRES(!m_drain_mutex); - SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, - SyncSocketProvider::FunctionHandler&& handler) - REQUIRES(!m_drain_mutex); - using SyncTrigger = std::unique_ptr>; - SyncTrigger create_trigger(SyncSocketProvider::FunctionHandler&& handler); - - RandomEngine& get_random() noexcept; - - /// Returns false if the specified URL is invalid. - bool decompose_server_url(const std::string& url, ProtocolEnvelope& protocol, std::string& address, - port_type& port, std::string& path) const; - - void cancel_reconnect_delay() REQUIRES(!m_drain_mutex); - bool wait_for_session_terminations_or_client_stopped() REQUIRES(!m_mutex, !m_drain_mutex); - // Async version of wait_for_session_terminations_or_client_stopped(). - util::Future notify_session_terminated() REQUIRES(!m_drain_mutex); - void voluntary_disconnect_all_connections() REQUIRES(!m_drain_mutex); - -private: - using connection_ident_type = std::int_fast64_t; - - const ReconnectMode m_reconnect_mode; // For testing purposes only - const milliseconds_type m_connect_timeout; - const milliseconds_type m_connection_linger_time; - const milliseconds_type m_ping_keepalive_period; - const milliseconds_type m_pong_keepalive_timeout; - const milliseconds_type m_fast_reconnect_limit; - const ResumptionDelayInfo m_reconnect_backoff_info; - const bool m_disable_upload_activation_delay; - const bool m_dry_run; // For testing purposes only - const bool m_enable_default_port_hack; - const bool m_fix_up_object_ids; - const std::function m_roundtrip_time_handler; - const std::string m_user_agent_string; - std::shared_ptr m_socket_provider; - ClientProtocol m_client_protocol; - session_ident_type m_prev_session_ident = 0; - const bool m_one_connection_per_session; - - RandomEngine m_random; - SyncTrigger m_actualize_and_finalize; - - // Note: There is one server slot per server endpoint (hostname, port, - // user_id), and it survives from one connection object to the next, which - // is important because it carries information about a possible reconnect - // delay applying to the new connection object (server hammering - // protection). - struct ServerSlot { - explicit ServerSlot(ReconnectInfo reconnect_info); - ~ServerSlot(); - - ReconnectInfo reconnect_info; // Applies exclusively to `connection`. - std::unique_ptr connection; - - // Used instead of `connection` when `m_one_connection_per_session` is - // true. - std::map> alt_connections; - }; - - // Must be accessed only by event loop thread - std::map m_server_slots; - - // Must be accessed only by event loop thread - connection_ident_type m_prev_connection_ident = 0; - - util::CheckedMutex m_drain_mutex; - std::condition_variable m_drain_cv; - bool m_drained GUARDED_BY(m_drain_mutex) = false; - uint64_t m_outstanding_posts GUARDED_BY(m_drain_mutex) = 0; - uint64_t m_num_connections GUARDED_BY(m_drain_mutex) = 0; - - util::CheckedMutex m_mutex; - - bool m_stopped GUARDED_BY(m_mutex) = false; - bool m_sessions_terminated GUARDED_BY(m_mutex) = false; - - // The set of session wrappers that are not yet wrapping a session object, - // and are not yet abandoned (still referenced by the application). - SessionWrapperStack m_unactualized_session_wrappers GUARDED_BY(m_mutex); - - // The set of session wrappers that were successfully actualized, but are - // now abandoned (no longer referenced by the application), and have not yet - // been finalized. Order in queue is immaterial. - SessionWrapperStack m_abandoned_session_wrappers GUARDED_BY(m_mutex); - - // Used with m_mutex - std::condition_variable m_wait_or_client_stopped_cond; - - void register_unactualized_session_wrapper(SessionWrapper*) REQUIRES(!m_mutex); - void register_abandoned_session_wrapper(util::bind_ptr) noexcept REQUIRES(!m_mutex); - void actualize_and_finalize_session_wrappers() REQUIRES(!m_mutex); - - // Get or create a connection. If a connection exists for the specified - // endpoint, it will be returned, otherwise a new connection will be - // created. If `m_one_connection_per_session` is true (testing only), a new - // connection will be created every time. - // - // Must only be accessed from event loop thread. - // - // FIXME: Passing these SSL parameters here is confusing at best, since they - // are ignored if a connection is already available for the specified - // endpoint. Also, there is no way to check that all the specified SSL - // parameters are in agreement with a preexisting connection. A better - // approach would be to allow for per-endpoint SSL parameters to be - // specifiable through public member functions of ClientImpl from where they - // could then be picked up as new connections are created on demand. - ClientImpl::Connection& get_connection(ServerEndpoint, const std::string& authorization_header_name, - const std::map& custom_http_headers, - bool verify_servers_ssl_certificate, - util::Optional ssl_trust_certificate_path, - std::function, - util::Optional, bool& was_created) - REQUIRES(!m_drain_mutex); - - // Destroys the specified connection. - void remove_connection(ClientImpl::Connection&) noexcept REQUIRES(!m_drain_mutex); - - void drain_connections(); - void drain_connections_on_loop() REQUIRES(!m_drain_mutex); - - void incr_outstanding_posts() REQUIRES(!m_drain_mutex); - void decr_outstanding_posts() REQUIRES(!m_drain_mutex); - - session_ident_type get_next_session_ident() noexcept; - - friend class ClientImpl::Connection; - friend class SessionWrapper; -}; - -constexpr int ClientImpl::get_oldest_supported_protocol_version() noexcept -{ - // See get_current_protocol_version() for information about the - // individual protocol versions. - return 2; -} - -static_assert(ClientImpl::get_oldest_supported_protocol_version() >= 1, ""); -static_assert(ClientImpl::get_oldest_supported_protocol_version() <= get_current_protocol_version(), ""); - - -/// Information about why a connection (or connection initiation attempt) was -/// terminated. This is used to determinte the delay until the next connection -/// initiation attempt. -enum class ClientImpl::ConnectionTerminationReason { - connect_operation_failed, ///< Failure during connect operation - closed_voluntarily, ///< Voluntarily closed or connection operation canceled - read_or_write_error, ///< Read/write error after successful TCP connect operation - ssl_certificate_rejected, ///< Client rejected the SSL certificate of the server - ssl_protocol_violation, ///< A violation of the SSL protocol - websocket_protocol_violation, ///< A violation of the WebSocket protocol - http_response_says_fatal_error, ///< Status code in HTTP response says "fatal error" - http_response_says_nonfatal_error, ///< Status code in HTTP response says "nonfatal error" - bad_headers_in_http_response, ///< Missing or bad headers in HTTP response - sync_protocol_violation, ///< Client received a bad message from the server - sync_connect_timeout, ///< Sync connection was not fully established in time - server_said_try_again_later, ///< Client received ERROR message with try_again=yes - server_said_do_not_reconnect, ///< Client received ERROR message with try_again=no - pong_timeout, ///< Client did not receive PONG after PING - - /// The application requested a feature that is unavailable in the - /// negotiated protocol version. - missing_protocol_feature, -}; - -/// All use of connection objects, including construction and destruction, must -/// occur on behalf of the event loop thread of the associated client object. - -// TODO: The parent will be updated to WebSocketObserver once the WebSocket integration is complete -class ClientImpl::Connection { -public: - using connection_ident_type = std::int_fast64_t; - using SSLVerifyCallback = SyncConfig::SSLVerifyCallback; - using ProxyConfig = SyncConfig::ProxyConfig; - using ReconnectInfo = ClientImpl::ReconnectInfo; - using DownloadMessage = ClientProtocol::DownloadMessage; - - ForwardingLogger logger; - - ClientImpl& get_client() noexcept; - ReconnectInfo get_reconnect_info() const noexcept; - ClientProtocol& get_client_protocol() noexcept; - - /// Activate this connection object. No attempt is made to establish a - /// connection before the connection object is activated. - void activate(); - - /// Activate the specified session. - /// - /// Prior to being activated, no messages will be sent or received on behalf - /// of this session, and the associated Realm file will not be accessed, - /// i.e., `Session::get_db()` will not be called. - /// - /// If activation is successful, the connection keeps the session alive - /// until the application calls initiated_session_deactivation() or until - /// the application destroys the connection object, whichever comes first. - void activate_session(std::unique_ptr); - - /// Initiate the deactivation process which eventually (or immediately) - /// leads to destruction of this session object. - /// - /// IMPORTANT: The session object may get destroyed before this function - /// returns. - /// - /// The deactivation process must be considered initiated even if this - /// function throws. - /// - /// The deactivation process is guaranteed to not be initiated until the - /// application calls this function. So from the point of view of the - /// application, after successful activation, a pointer to a session object - /// remains valid until the application calls - /// initiate_session_deactivation(). - /// - /// After the initiation of the deactivation process, the associated Realm - /// file will no longer be accessed, i.e., `get_db()` will not be called - /// again, and a previously returned reference will also not be accessed - /// again. - /// - /// The initiation of the deactivation process must be preceded by a - /// successful invocation of activate_session(). It is an error to call - /// initiate_session_deactivation() twice. - void initiate_session_deactivation(Session*); - - /// Cancel the reconnect delay for this connection, if one is currently in - /// effect. If a reconnect delay is not currently in effect, ensure that the - /// delay before the next reconnection attempt will be canceled. This is - /// necessary as an apparently established connection, or ongoing connection - /// attempt can be about to fail for a reason that precedes the invocation - /// of this function. - /// - /// It is an error to call this function before the connection has been - /// activated. - void cancel_reconnect_delay(); - - void force_close(); - - /// Returns zero until the HTTP response is received. After that point in - /// time, it returns the negotiated protocol version, which is based on the - /// contents of the `Sec-WebSocket-Protocol` header in the HTTP - /// response. The negotiated protocol version is guaranteed to be greater - /// than or equal to get_oldest_supported_protocol_version(), and be less - /// than or equal to get_current_protocol_version(). - int get_negotiated_protocol_version() noexcept; - - // Methods from WebSocketObserver interface for websockets from the Socket Provider - void websocket_connected_handler(const std::string& protocol); - bool websocket_binary_message_received(util::Span data); - void websocket_error_handler(); - bool websocket_closed_handler(bool, websocket::WebSocketError, std::string_view msg); - - connection_ident_type get_ident() const noexcept; - const ServerEndpoint& get_server_endpoint() const noexcept; - ConnectionState get_state() const noexcept; - SyncServerMode get_sync_server_mode() const noexcept; - bool is_flx_sync_connection() const noexcept; - - void update_connect_info(const std::string& http_request_path_prefix, const std::string& signed_access_token); - - void resume_active_sessions(); - - void voluntary_disconnect(); - - std::string get_active_appservices_connection_id(); - - Connection(ClientImpl&, connection_ident_type, ServerEndpoint, const std::string& authorization_header_name, - const std::map& custom_http_headers, bool verify_servers_ssl_certificate, - util::Optional ssl_trust_certificate_path, std::function, - util::Optional, ReconnectInfo); - - ~Connection(); - -private: - struct LifecycleSentinel : public util::AtomicRefCountBase { - bool destroyed = false; - }; - struct WebSocketObserverShim; - - using ReceivedChangesets = ClientProtocol::ReceivedChangesets; - - template - void for_each_active_session(H handler); - - /// \brief Called when the connection becomes idle. - /// - /// The connection is considered idle when all of the following conditions - /// are true: - /// - /// - The connection is activated. - /// - /// - The connection has no sessions in the Active state. - /// - /// - The connection is closed (in the disconnected state). - /// - /// From the point of view of this class, an overriding function is allowed - /// to commit suicide (`delete this`). - /// - /// The default implementation of this function does nothing. - /// - /// This function is always called by the event loop thread of the - /// associated client object. - void on_idle(); - - std::string get_http_request_path() const; - - void initiate_reconnect_wait(); - void handle_reconnect_wait(Status status); - void initiate_reconnect(); - void initiate_connect_wait(); - void handle_connect_wait(Status status); - - void handle_connection_established(); - void schedule_urgent_ping(); - void initiate_ping_delay(milliseconds_type now); - void handle_ping_delay(); - void initiate_pong_timeout(); - void handle_pong_timeout(); - void initiate_write_message(const OutputBuffer&, Session*); - void handle_write_message(); - void send_next_message(); - void send_ping(); - void initiate_write_ping(const OutputBuffer&); - void handle_write_ping(); - void handle_message_received(util::Span data); - void initiate_disconnect_wait(); - void handle_disconnect_wait(Status status); - void close_due_to_protocol_error(Status status); - void close_due_to_client_side_error(Status, IsFatal is_fatal, ConnectionTerminationReason reason); - void close_due_to_transient_error(Status status, ConnectionTerminationReason reason); - void close_due_to_server_side_error(ProtocolError, const ProtocolErrorInfo& info); - void involuntary_disconnect(const SessionErrorInfo& info, ConnectionTerminationReason reason); - void disconnect(const SessionErrorInfo& info); - void change_state_to_disconnected() noexcept; - // These are only called from ClientProtocol class. - void receive_pong(milliseconds_type timestamp); - void receive_error_message(const ProtocolErrorInfo& info, session_ident_type); - void receive_query_error_message(int error_code, std::string_view message, int64_t query_version, - session_ident_type); - void receive_ident_message(session_ident_type, SaltedFileIdent); - void receive_download_message(session_ident_type, const DownloadMessage& message); - - void receive_mark_message(session_ident_type, request_ident_type); - void receive_unbound_message(session_ident_type); - void receive_test_command_response(session_ident_type, request_ident_type, std::string_view body); - void receive_server_log_message(session_ident_type, util::Logger::Level, std::string_view body); - void receive_appservices_request_id(std::string_view coid); - void handle_protocol_error(Status status); - - // These are only called from Session class. - void enlist_to_send(Session*); - void one_more_active_unsuspended_session(); - void one_less_active_unsuspended_session(); - void finish_session_deactivation(Session* sess); - - OutputBuffer& get_output_buffer() noexcept; - Session* get_session(session_ident_type) const noexcept; - Session* find_and_validate_session(session_ident_type session_ident, std::string_view message) noexcept; - static bool was_voluntary(ConnectionTerminationReason) noexcept; - - static std::shared_ptr make_logger(connection_ident_type ident, - std::optional coid, - std::shared_ptr base_logger); - - void report_connection_state_change(ConnectionState, util::Optional error_info = util::none); - - friend ClientProtocol; - friend class Session; - - ClientImpl& m_client; - util::bind_ptr m_websocket_sentinel; - std::unique_ptr m_websocket; - - /// DEPRECATED - These will be removed in a future release - const bool m_verify_servers_ssl_certificate; - const util::Optional m_ssl_trust_certificate_path; - const std::function m_ssl_verify_callback; - const util::Optional m_proxy_config; - - ReconnectInfo m_reconnect_info; - int m_negotiated_protocol_version = 0; - - ConnectionState m_state = ConnectionState::disconnected; - - std::size_t m_num_active_unsuspended_sessions = 0; - std::size_t m_num_active_sessions = 0; - ClientImpl::SyncTrigger m_on_idle; - - // activate() has been called - bool m_activated = false; - - // A reconnect delay is in progress - bool m_reconnect_delay_in_progress = false; - - // Has no meaning when m_reconnect_delay_in_progress is false. - bool m_nonzero_reconnect_delay = false; - - // A disconnect (linger) delay is in progress. This is for keeping the - // connection open for a while after there are no more active unsuspended - // sessions. - bool m_disconnect_delay_in_progress = false; - - bool m_disconnect_has_occurred = false; - - // A message is currently being sent, i.e., the sending of a message has - // been initiated, but not yet completed. - bool m_sending = false; - - bool m_ping_delay_in_progress = false; - bool m_waiting_for_pong = false; - bool m_send_ping = false; - bool m_minimize_next_ping_delay = false; - bool m_ping_after_scheduled_reset_of_reconnect_info = false; - - // At least one PING message was sent since connection was established - bool m_ping_sent = false; - - bool m_websocket_error_received = false; - - bool m_force_closed = false; - - // The timer will be constructed on demand, and will only be destroyed when - // canceling a reconnect or disconnect delay. - // - // It is necessary to destroy and recreate the timer when canceling a wait - // operation, because the next wait operation might need to be initiated - // before the completion handler of the previous canceled wait operation - // starts executing. Such an overlap is not allowed for wait operations on - // the same timer instance. - SyncSocketProvider::SyncTimer m_reconnect_disconnect_timer; - - // Timer for connect operation watchdog. For why this timer is optional, see - // `m_reconnect_disconnect_timer`. - SyncSocketProvider::SyncTimer m_connect_timer; - - // This timer is used to schedule the sending of PING messages, and as a - // watchdog for timely reception of PONG messages. For why this timer is - // optional, see `m_reconnect_disconnect_timer`. - SyncSocketProvider::SyncTimer m_heartbeat_timer; - - milliseconds_type m_pong_wait_started_at = 0; - milliseconds_type m_last_ping_sent_at = 0; - - // Round-trip time, in milliseconds, for last PING message for which a PONG - // message has been received, or zero if no PONG message has been received. - milliseconds_type m_previous_ping_rtt = 0; - - // Only valid when `m_disconnect_has_occurred` is true. - milliseconds_type m_disconnect_time = 0; - - // The set of sessions associated with this connection. A session becomes - // associated with a connection when it is activated. - std::map> m_sessions; - // Keep track of previously used sessions idents to see if a stale message was - // received for a closed session - std::unordered_set m_session_history; - - // A queue of sessions that have enlisted for an opportunity to send a - // message to the server. Sessions will be served in the order that they - // enlist. A session is only allowed to occur once in this queue. If the - // connection is open, and the queue is not empty, and no message is - // currently being written, the first session is taken out of the queue, and - // then granted an opportunity to send a message. - std::deque m_sessions_enlisted_to_send; - - Session* m_sending_session = nullptr; - - std::unique_ptr m_input_body_buffer; - OutputBuffer m_output_buffer; - - const connection_ident_type m_ident; - ServerEndpoint m_server_endpoint; - std::string m_appservices_coid; - - /// DEPRECATED - These will be removed in a future release - const std::string m_authorization_header_name; - const std::map m_custom_http_headers; - - std::string m_http_request_path_prefix; - std::string m_signed_access_token; -}; - -/// A synchronization session between a local and a remote Realm file. -/// -/// All use of session objects, including construction and destruction, must -/// occur on the event loop thread of the associated client object. -class ClientImpl::Session { -public: - using ReceivedChangesets = ClientProtocol::ReceivedChangesets; - using DownloadMessage = ClientProtocol::DownloadMessage; - - ForwardingLogger logger; - - ClientImpl& get_client() noexcept; - Connection& get_connection() noexcept; - session_ident_type get_ident() const noexcept; - - /// Inform this client about new changesets in the history. - /// - /// The type of the version specified here is the one that identifies an - /// entry in the sync history. Whether this is the same as the snapshot - /// version of the Realm depends on the history implementation. - /// - /// The application is supposed to call this function to inform the client - /// about a new version produced by a transaction that was not performed on - /// behalf of this client. If the application does not call this function, - /// the client will not discover and upload new changesets in a timely - /// manner. - /// - /// It is an error to call this function before activation of the session, - /// or after initiation of deactivation. - void recognize_sync_version(version_type); - - /// \brief Request notification when all changesets currently avaialble on - /// the server have been downloaded. - /// - /// When downloading completes, on_download_completion() will be called by - /// the thread that processes the event loop (as long as such a thread - /// exists). - /// - /// If request_download_completion_notification() is called while a - /// previously requested completion notification has not yet occurred, the - /// previous request is canceled and the corresponding notification will - /// never occur. This ensure that there is no ambiguity about the meaning of - /// each completion notification. - /// - /// The application must be prepared for "spurious" invocations of - /// on_download_completion() before the client's first invocation of - /// request_download_completion_notification(), or after a previous - /// invocation of on_download_completion(), as long as it is before the - /// subsequent invocation by the client of - /// request_download_completion_notification(). This is possible because the - /// client reserves the right to request download completion notifications - /// internally. - /// - /// Download is considered complete when all changesets in the server-side - /// history, that are supposed to be downloaded, and that precede - /// `current_server_version`, have been downloaded and integrated into the - /// local history. `current_server_version` is the version that refers to - /// the last changeset in the server-side history at the time the server - /// receives the first MARK message that is sent by the client after the - /// invocation of request_download_completion_notification(). - /// - /// Every invocation of request_download_completion_notification() will - /// cause a new MARK message to be sent to the server, to redetermine - /// `current_server_version`. - /// - /// It is an error to call this function before activation of the session, - /// or after initiation of deactivation. - void request_download_completion_notification(); - - /// \brief Gets the subscription store associated with this Session. - SubscriptionStore* get_flx_subscription_store(); - - /// \brief Gets the migration store associated with this Session. - MigrationStore* get_migration_store(); - - /// If this session is currently suspended, resume it immediately. - /// - /// It is an error to call this function before activation of the session, - /// or after initiation of deactivation. - void cancel_resumption_delay(); - - /// To be used in connection with implementations of - /// initiate_integrate_changesets(). - void integrate_changesets(const SyncProgress&, std::uint_fast64_t downloadable_bytes, const ReceivedChangesets&, - VersionInfo&, DownloadBatchState batch_state); - - /// It is an error to call this function before activation of the session - /// (Connection::activate_session()), or after initiation of deactivation - /// (Connection::initiate_session_deactivation()). - void on_changesets_integrated(version_type client_version, const SyncProgress& progress); - - void on_integration_failure(const IntegrationException& e); - - void on_connection_state_changed(ConnectionState, const util::Optional&); - - /// The application must ensure that the new session object is either - /// activated (Connection::activate_session()) or destroyed before the - /// specified connection object is destroyed. - /// - /// The specified transaction reporter (via the config object) is guaranteed - /// to not be called before activation, and also not after initiation of - /// deactivation. - Session(SessionWrapper&, ClientImpl::Connection&); - ~Session(); - - void force_close(); - - util::Future send_test_command(std::string body); - -private: - struct PendingTestCommand { - request_ident_type id; - std::string body; - util::Promise promise; - bool pending = true; - }; - - /// Fetch a reference to the remote virtual path of the Realm associated - /// with this session. - /// - /// This function is always called by the event loop thread of the - /// associated client object. - /// - /// This function is guaranteed to not be called before activation, and also - /// not after initiation of deactivation. - const std::string& get_virt_path() const noexcept; - - const std::string& get_realm_path() const noexcept; - - // Can only be called if the session is active or being activated - DBRef get_db() const noexcept; - ClientReplication& get_repl() const noexcept; - ClientHistory& get_history() const noexcept; - - // client_reset_config() returns the config for client - // reset. If it returns none, ordinary sync is used. If it returns a - // Config::ClientReset, the session will be initiated with a state Realm - // transfer from the server. - util::Optional& get_client_reset_config() noexcept; - - // Get the reason a synchronization session is used for (regular sync or client reset) - // - Client reset state means the session is going to be used to download a fresh realm. - SessionReason get_session_reason() noexcept; - - /// Returns the schema version the synchronization session connects with to the server. - uint64_t get_schema_version() noexcept; - - // Returns false if this session is not allowed to send UPLOAD messages to the server to - // update the cursor info, such as during a client reset fresh realm download - bool upload_messages_allowed() noexcept; - - /// \brief Initiate the integration of downloaded changesets. - /// - /// This function must provide for the passed changesets (if any) to - /// eventually be integrated, and without unnecessary delay. If no - /// changesets are passed, the purpose of this function reduces to causing - /// the current synchronization progress (SyncProgress) to be persisted. - /// - /// When all changesets have been integrated, and the synchronization - /// progress has been persisted, this function must provide for - /// on_changesets_integrated() to be called without unnecessary delay, - /// although never after initiation of session deactivation. - /// - /// The implementation is allowed, but not obliged to aggregate changesets - /// from multiple invocations of initiate_integrate_changesets() and pass - /// them to ClientReplication::integrate_server_changesets() at once. - /// - /// The synchronization progress passed to - /// ClientReplication::integrate_server_changesets() must be obtained - /// by calling get_status(), and that call must occur after the last - /// invocation of initiate_integrate_changesets() whose changesets are - /// included in what is passed to - /// ClientReplication::integrate_server_changesets(). - /// - /// The download cursor passed to on_changesets_integrated() must be - /// SyncProgress::download of the synchronization progress passed to the - /// last invocation of - /// ClientReplication::integrate_server_changesets(). - /// - /// The default implementation integrates the specified changesets and calls - /// on_changesets_integrated() immediately (i.e., from the event loop thread - /// of the associated client object, and before - /// initiate_integrate_changesets() returns), and via the history accessor - /// made available by access_realm(). - /// - /// This function is always called by the event loop thread of the - /// associated client object, and on_changesets_integrated() must always be - /// called by that thread too. - /// - /// This function is guaranteed to not be called before activation, and also - /// not after initiation of deactivation. - void initiate_integrate_changesets(std::uint_fast64_t downloadable_bytes, DownloadBatchState batch_state, - const SyncProgress& progress, const ReceivedChangesets&); - - /// See request_download_completion_notification(). - void on_download_completion(); - - //@{ - /// These are called as the state of the session changes between - /// "suspended" and "resumed". The initial state is - /// always "resumed". - /// - /// A switch to the suspended state only happens when an error occurs, - /// and information about that error is passed to on_suspended(). - /// - /// These functions are always called by the event loop thread of the - /// associated client object. - /// - /// These functions are guaranteed to not be called before activation, and also - /// not after initiation of deactivation. - void on_suspended(const SessionErrorInfo& error_info); - void on_resumed(); - //@} - - void on_flx_sync_error(int64_t version, std::string_view err_msg); - - // Processes an FLX download message, if it's a bootstrap message. If it's not a bootstrap - // message then this is a noop and will return false. Otherwise this will return true - // and no further processing of the download message should take place. - bool process_flx_bootstrap_message(const DownloadMessage& message); - - // Processes any pending FLX bootstraps, if one exists. Otherwise this is a noop. - void process_pending_flx_bootstrap(); - - bool client_reset_if_needed(); - void handle_pending_client_reset_acknowledgement(); - - void gather_pending_compensating_writes(util::Span changesets, std::vector* out); - - void begin_resumption_delay(const ProtocolErrorInfo& error_info); - void clear_resumption_delay_state(); - -private: - Connection& m_conn; - const session_ident_type m_ident; - - // The states only transition in one direction, from left to right. - // The transition to Active happens very soon after construction, as soon as - // it is registered with the Connection. - // The transition from Deactivating to Deactivated state happens when the - // unbinding process completes (unbind_process_complete()). - enum State { Unactivated, Active, Deactivating, Deactivated }; - State m_state = Unactivated; - - bool m_suspended = false; - - SyncSocketProvider::SyncTimer m_try_again_activation_timer; - ErrorBackoffState m_try_again_delay_info; - - // Set to false when download completion is reached. Set to true after a - // slow reconnect, such that UPLOAD and QUERY messages will not be sent until - // download completion is reached again. This feature can be disabled (always - // false) if ClientConfig::disable_upload_activation_delay is true. - bool m_delay_uploads = true; - - bool m_is_flx_sync_session = false; - - bool m_fix_up_object_ids = false; - - // These are reset when the session is activated, and again whenever the - // connection is lost or the rebinding process is initiated. - bool m_enlisted_to_send; - bool m_bind_message_sent; // Sending of BIND message has been initiated - bool m_ident_message_sent; // Sending of IDENT message has been initiated - bool m_unbind_message_sent; // Sending of UNBIND message has been initiated - bool m_unbind_message_send_complete; // Sending of UNBIND message has been completed - bool m_error_message_received; // Session specific ERROR message received - bool m_unbound_message_received; // UNBOUND message received - bool m_error_to_send; - - // True when there is a new FLX sync query we need to send to the server. - util::Optional m_pending_flx_sub_set; - int64_t m_last_sent_flx_query_version = 0; - - std::deque m_pending_compensating_write_errors; - - util::Optional m_client_error; - - // `ident == 0` means unassigned. - SaltedFileIdent m_client_file_ident = {0, 0}; - - // The latest sync progress reported by the server via a DOWNLOAD - // message. See struct SyncProgress for a description. The values stored in - // `m_progress` either are persisted, or are about to be. - // - // Initialized by way of ClientHistory::get_status() at session - // activation time. - // - // `m_progress.upload.client_version` is the client-side sync version - // produced by the latest local changeset that has been acknowledged as - // integrated by the server. - SyncProgress m_progress; - - // In general, the local version produced by the last changeset in the local - // history. The changeset that produced this version may, or may not - // contain changes of local origin. - // - // It is set to the current version of the local Realm at session activation - // time (although always zero for the initial empty Realm - // state). Thereafter, it is updated when the application calls - // recognize_sync_version(), when changesets are received from the server - // and integrated locally, and when the uploading process discovers newer - // versions. - // - // INVARIANT: m_progress.upload.client_version <= m_last_version_available - version_type m_last_version_available = 0; - - // In general, this is the position in the history reached while scanning - // for changesets to be uploaded. - // - // Set to `m_progress.upload` at session activation time and whenever the - // connection to the server is lost. When the connection is established, the - // scanning for changesets to be uploaded then progresses from there towards - // `m_last_version_available`. - // - // INVARIANT: m_progress.upload.client_version <= m_upload_progress.client_version - UploadCursor m_upload_progress = {0, 0}; - - // Same as `m_progress.download` but is updated only as the progress gets - // persisted. - DownloadCursor m_download_progress = {0, 0}; - - // Used to implement download completion notifications. Set equal to - // `m_progress.download.server_version` when a MARK message is received. Set - // back to zero when `m_download_progress.server_version` becomes greater - // than, or equal to `m_server_version_at_last_download_mark`. For further - // details, see check_for_download_completion(). - version_type m_server_version_at_last_download_mark = 0; - - // The serial number to attach to the next download MARK message. A new MARK - // message will be sent when `m_target_download_mark > - // m_last_download_mark_sent`. To cause a new MARK message to be sent, - // simply increment `m_target_download_mark`. - request_ident_type m_target_download_mark = 0; - - // Set equal to `m_target_download_mark` as the sending of each MARK message - // is initiated. Must be set equal to `m_last_download_mark_received` when - // the connection to the server is lost. - request_ident_type m_last_download_mark_sent = 0; - - // Updated when a MARK message is received. See see - // check_for_download_completion() for how details on how it participates in - // the detection of download completion. - request_ident_type m_last_download_mark_received = 0; - - // Updated when a download completion is detected, to avoid multiple - // triggerings after reception of a single MARK message. See see - // check_for_download_completion() for how details on how it participates in - // the detection of download completion. - request_ident_type m_last_triggering_download_mark = 0; - - SessionWrapper& m_wrapper; - - request_ident_type m_last_pending_test_command_ident = 0; - std::list m_pending_test_commands; - - static std::shared_ptr make_logger(session_ident_type, std::shared_ptr base_logger); - - Session(SessionWrapper& wrapper, Connection&, session_ident_type); - - bool do_recognize_sync_version(version_type) noexcept; - - bool have_client_file_ident() const noexcept; - - // The unbinding process completes when both of the following become true: - // - // - The sending of the UNBIND message has been completed - // (m_unbind_message_sent_2). - // - // - A session specific ERROR, or the UNBOUND message has been received - // (m_error_message_received || m_unbond_message_received). - // - // Rebinding (sending of a new BIND message) can only be initiated while the - // session is in the Active state, and the unbinding process has completed - // (unbind_process_complete()). - bool unbind_process_complete() const noexcept; - - void activate(); - void initiate_deactivation(); - void complete_deactivation(); - void connection_established(bool fast_reconnect); - void suspend(const SessionErrorInfo& session_error); - void connection_lost(); - void send_message(); - void message_sent(); - void send_bind_message(); - void send_ident_message(); - void send_upload_message(); - void send_mark_message(); - void send_alloc_message(); - void send_unbind_message(); - void send_query_change_message(); - void send_json_error_message(); - void send_test_command_message(); - Status receive_ident_message(SaltedFileIdent); - Status receive_download_message(const DownloadMessage& message); - Status receive_mark_message(request_ident_type); - Status receive_unbound_message(); - Status receive_error_message(const ProtocolErrorInfo& info); - void receive_query_error_message(int error_code, std::string_view message, int64_t query_version); - Status receive_test_command_response(request_ident_type, std::string_view body); - - void initiate_rebind(); - void reset_protocol_state() noexcept; - void ensure_enlisted_to_send(); - void enlist_to_send(); - Status check_received_sync_progress(const SyncProgress&) noexcept; - void check_for_download_completion(); - - SyncClientHookAction call_debug_hook(SyncClientHookEvent event, const SyncProgress&, int64_t, DownloadBatchState, - size_t); - SyncClientHookAction call_debug_hook(SyncClientHookEvent event, const ProtocolErrorInfo* = nullptr); - SyncClientHookAction call_debug_hook(const SyncClientHookData& data); - - void init_progress_handler(); - void enable_progress_notifications(); - - friend class Connection; -}; - - -// Implementation - -inline const std::string& ClientImpl::get_user_agent_string() const noexcept -{ - return m_user_agent_string; -} - -inline auto ClientImpl::get_reconnect_mode() const noexcept -> ReconnectMode -{ - return m_reconnect_mode; -} - -inline bool ClientImpl::is_dry_run() const noexcept -{ - return m_dry_run; -} - -inline ClientImpl::RandomEngine& ClientImpl::get_random() noexcept -{ - return m_random; -} - -inline auto ClientImpl::get_next_session_ident() noexcept -> session_ident_type -{ - return ++m_prev_session_ident; -} - - -inline ClientImpl& ClientImpl::Connection::get_client() noexcept -{ - return m_client; -} - -inline ConnectionState ClientImpl::Connection::get_state() const noexcept -{ - return m_state; -} - -inline ClientImpl::ServerSlot::ServerSlot(ReconnectInfo reconnect_info) - : reconnect_info(std::move(reconnect_info)) -{ -} - -inline ClientImpl::ServerSlot::~ServerSlot() = default; - -inline SyncServerMode ClientImpl::Connection::get_sync_server_mode() const noexcept -{ - return m_server_endpoint.server_mode; -} - -inline auto ClientImpl::Connection::get_reconnect_info() const noexcept -> ReconnectInfo -{ - return m_reconnect_info; -} - -inline auto ClientImpl::Connection::get_client_protocol() noexcept -> ClientProtocol& -{ - return m_client.m_client_protocol; -} - -inline int ClientImpl::Connection::get_negotiated_protocol_version() noexcept -{ - return m_negotiated_protocol_version; -} - -template -void ClientImpl::Connection::for_each_active_session(H handler) -{ - for (auto& p : m_sessions) { - Session& sess = *p.second; - if (sess.m_state == Session::Active) - handler(sess); // Throws - } -} - -inline void ClientImpl::Connection::voluntary_disconnect() -{ - if (m_state == ConnectionState::disconnected) { - return; - } - m_reconnect_info.update(ConnectionTerminationReason::closed_voluntarily, std::nullopt); - SessionErrorInfo error_info{Status{ErrorCodes::ConnectionClosed, "Connection closed"}, IsFatal{false}}; - error_info.server_requests_action = ProtocolErrorInfo::Action::Transient; - - disconnect(std::move(error_info)); // Throws -} - -inline void ClientImpl::Connection::involuntary_disconnect(const SessionErrorInfo& info, - ConnectionTerminationReason reason) -{ - REALM_ASSERT(!was_voluntary(reason)); - m_reconnect_info.update(reason, info.resumption_delay_interval); - disconnect(info); // Throws -} - -inline void ClientImpl::Connection::change_state_to_disconnected() noexcept -{ - REALM_ASSERT(m_on_idle); - REALM_ASSERT(m_state != ConnectionState::disconnected); - m_state = ConnectionState::disconnected; - - if (m_num_active_sessions == 0) - m_on_idle->trigger(); - - REALM_ASSERT(!m_reconnect_delay_in_progress); - if (m_disconnect_delay_in_progress) { - m_reconnect_disconnect_timer.reset(); - m_disconnect_delay_in_progress = false; - } -} - -inline void ClientImpl::Connection::one_more_active_unsuspended_session() -{ - if (m_num_active_unsuspended_sessions++ != 0) - return; - // Rose from zero to one - if (m_state == ConnectionState::disconnected && !m_reconnect_delay_in_progress && m_activated) - initiate_reconnect(); // Throws -} - -inline void ClientImpl::Connection::one_less_active_unsuspended_session() -{ - REALM_ASSERT(m_num_active_unsuspended_sessions); - if (--m_num_active_unsuspended_sessions != 0) - return; - - // Dropped from one to zero - if (m_state != ConnectionState::disconnected) - initiate_disconnect_wait(); // Throws -} - -// Sessions, and the connection, should get the output_buffer and insert a message, -// after which they call initiate_write_output_buffer(Session* sess). -inline auto ClientImpl::Connection::get_output_buffer() noexcept -> OutputBuffer& -{ - m_output_buffer.reset(); - return m_output_buffer; -} - -inline auto ClientImpl::Connection::get_session(session_ident_type ident) const noexcept -> Session* -{ - auto i = m_sessions.find(ident); - bool found = (i != m_sessions.end()); - return found ? i->second.get() : nullptr; -} - -inline bool ClientImpl::Connection::was_voluntary(ConnectionTerminationReason reason) noexcept -{ - switch (reason) { - case ConnectionTerminationReason::closed_voluntarily: - return true; - case ConnectionTerminationReason::connect_operation_failed: - case ConnectionTerminationReason::read_or_write_error: - case ConnectionTerminationReason::ssl_certificate_rejected: - case ConnectionTerminationReason::ssl_protocol_violation: - case ConnectionTerminationReason::websocket_protocol_violation: - case ConnectionTerminationReason::http_response_says_fatal_error: - case ConnectionTerminationReason::http_response_says_nonfatal_error: - case ConnectionTerminationReason::bad_headers_in_http_response: - case ConnectionTerminationReason::sync_protocol_violation: - case ConnectionTerminationReason::sync_connect_timeout: - case ConnectionTerminationReason::server_said_try_again_later: - case ConnectionTerminationReason::server_said_do_not_reconnect: - case ConnectionTerminationReason::pong_timeout: - case ConnectionTerminationReason::missing_protocol_feature: - break; - } - return false; -} - -inline ClientImpl& ClientImpl::Session::get_client() noexcept -{ - return m_conn.get_client(); -} - -inline auto ClientImpl::Session::get_connection() noexcept -> Connection& -{ - return m_conn; -} - -inline auto ClientImpl::Session::get_ident() const noexcept -> session_ident_type -{ - return m_ident; -} - -inline void ClientImpl::Session::recognize_sync_version(version_type version) -{ - REALM_ASSERT(m_state == Active); - - bool resume_upload = do_recognize_sync_version(version); - if (REALM_LIKELY(resume_upload)) { - // Don't attempt to send any updates before the IDENT message has been - // sent or after the UNBIND message has been sent or an error message - // was received. - if (m_ident_message_sent && !m_error_message_received && !m_unbind_message_sent) - ensure_enlisted_to_send(); // Throws - } -} - -inline void ClientImpl::Session::request_download_completion_notification() -{ - REALM_ASSERT(m_state == Active); - - ++m_target_download_mark; - - // Since the deactivation process has not been initiated, the UNBIND message - // cannot have been sent unless an ERROR message was received. - REALM_ASSERT(m_error_message_received || !m_unbind_message_sent); - if (m_ident_message_sent && !m_error_message_received) - ensure_enlisted_to_send(); // Throws -} - -inline ClientImpl::Session::Session(SessionWrapper& wrapper, Connection& conn) - : Session{wrapper, conn, conn.get_client().get_next_session_ident()} // Throws -{ -} - -inline ClientImpl::Session::Session(SessionWrapper& wrapper, Connection& conn, session_ident_type ident) - : logger{make_logger(ident, conn.logger.base_logger)} // Throws - , m_conn{conn} - , m_ident{ident} - , m_try_again_delay_info(conn.get_client().m_reconnect_backoff_info, conn.get_client().get_random()) - , m_is_flx_sync_session(conn.is_flx_sync_connection()) - , m_fix_up_object_ids(get_client().m_fix_up_object_ids) - , m_wrapper{wrapper} -{ - if (get_client().m_disable_upload_activation_delay) - m_delay_uploads = false; -} - -inline bool ClientImpl::Session::do_recognize_sync_version(version_type version) noexcept -{ - if (REALM_LIKELY(version > m_last_version_available)) { - m_last_version_available = version; - return true; - } - return false; -} - -inline bool ClientImpl::Session::have_client_file_ident() const noexcept -{ - return (m_client_file_ident.ident != 0); -} - -inline bool ClientImpl::Session::unbind_process_complete() const noexcept -{ - return (m_unbind_message_send_complete && (m_error_message_received || m_unbound_message_received)); -} - -inline void ClientImpl::Session::connection_established(bool fast_reconnect) -{ - REALM_ASSERT(m_state == Active); - - if (!fast_reconnect && !get_client().m_disable_upload_activation_delay) { - // Disallow immediate activation of the upload process, even if download - // completion was reached during an earlier period of connectivity. - m_delay_uploads = true; - } - - if (m_delay_uploads) { - // Request download completion notification - ++m_target_download_mark; - } - - // Notify the debug hook of the SessionConnected event before sending - // the bind messsage - call_debug_hook(SyncClientHookEvent::SessionConnected); - - if (!m_suspended) { - // Ready to send BIND message - enlist_to_send(); // Throws - } -} - -// The caller (Connection) must discard the session if the session has become -// deactivated upon return. -inline void ClientImpl::Session::connection_lost() -{ - REALM_ASSERT(m_state == Active || m_state == Deactivating); - // If the deactivation process has been initiated, it can now be immediately - // completed. - if (m_state == Deactivating) { - complete_deactivation(); // Throws - REALM_ASSERT(m_state == Deactivated); - return; - } - reset_protocol_state(); -} - -// The caller (Connection) must discard the session if the session has become -// deactivated upon return. -inline void ClientImpl::Session::message_sent() -{ - // Note that it is possible for this function to get called after the client - // has received a message sent by the server in reposnse to the message that - // the client has just finished sending. - - REALM_ASSERT(m_state == Active || m_state == Deactivating); - - // No message will be sent after the UNBIND message - REALM_ASSERT(!m_unbind_message_send_complete); - - // If the client reset config structure is populated, then try to perform - // the client reset diff once the BIND message has been sent successfully - if (m_bind_message_sent && m_state == Active && get_client_reset_config()) { - client_reset_if_needed(); - // Ready to send the IDENT message - ensure_enlisted_to_send(); // Throws - } - - if (m_unbind_message_sent) { - REALM_ASSERT(!m_enlisted_to_send); - - // If the sending of the UNBIND message has been initiated, this must be - // the time when the sending of that message completes. - m_unbind_message_send_complete = true; - - // Detect the completion of the unbinding process - if (m_error_message_received || m_unbound_message_received) { - // If the deactivation process has been initiated, it can now be - // immediately completed. - if (m_state == Deactivating) { - // Life cycle state is Deactivating - complete_deactivation(); // Throws - // Life cycle state is now Deactivated - return; - } - - // The session is still in the Active state, so initiate the - // rebinding process if the session is no longer suspended. - if (!m_suspended) - initiate_rebind(); // Throws - } - } -} - -inline void ClientImpl::Session::initiate_rebind() -{ - // Life cycle state must be Active - REALM_ASSERT(m_state == Active); - - REALM_ASSERT(!m_suspended); - REALM_ASSERT(!m_enlisted_to_send); - - reset_protocol_state(); - - // Notify the debug hook of the SessionResumed event before sending - // the bind messsage - call_debug_hook(SyncClientHookEvent::SessionResumed); - - // Ready to send BIND message - enlist_to_send(); // Throws -} - -inline void ClientImpl::Session::reset_protocol_state() noexcept -{ - m_enlisted_to_send = false; - m_bind_message_sent = false; - m_error_to_send = false; - m_ident_message_sent = false; - m_unbind_message_sent = false; - m_unbind_message_send_complete = false; - m_error_message_received = false; - m_unbound_message_received = false; - m_client_error = util::none; - m_pending_compensating_write_errors.clear(); - - m_upload_progress = m_progress.upload; - m_last_download_mark_sent = m_last_download_mark_received; -} - -inline void ClientImpl::Session::ensure_enlisted_to_send() -{ - if (!m_enlisted_to_send) - enlist_to_send(); // Throws -} - -// This function will never "commit suicide" despite the fact that it may -// involve an invocation of send_message(), which in certain cases can lead to -// the completion of the deactivation process, and if that did happen, it would -// cause Connection::send_next_message() to destroy this session, but it does -// not happen. -// -// If the session is already in the Deactivating state, send_message() will -// complete the deactivation process immediately when, and only when the BIND -// message has not already been sent. -// -// Note however, that this function gets called when the establishment of the -// connection completes, but at that time, the session cannot be in the -// Deactivating state, because until the BIND message is sent, the deactivation -// process will complete immediately. So the first invocation of this function -// after establishemnt of the connection will not commit suicide. -// -// Note then, that the session will stay enlisted to send, until it gets to send -// the BIND message, and since the and enlist_to_send() must not be called while -// the session is enlisted, the next invocation of this function will be after -// the BIND message has been sent, but then the deactivation process will no -// longer be completed by send_message(). -inline void ClientImpl::Session::enlist_to_send() -{ - REALM_ASSERT(m_state == Active || m_state == Deactivating); - REALM_ASSERT(!m_unbind_message_sent); - REALM_ASSERT(!m_enlisted_to_send); - m_enlisted_to_send = true; - m_conn.enlist_to_send(this); // Throws -} - -} // namespace realm::sync - -#endif // REALM_NOINST_CLIENT_IMPL_BASE_HPP diff --git a/src/realm/sync/noinst/client_reset.cpp b/src/realm/sync/noinst/client_reset.cpp deleted file mode 100644 index 6a9e875e345..00000000000 --- a/src/realm/sync/noinst/client_reset.cpp +++ /dev/null @@ -1,561 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// 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 -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -using namespace realm; -using namespace _impl; -using namespace sync; - -namespace realm { - -std::ostream& operator<<(std::ostream& os, const ClientResyncMode& mode) -{ - switch (mode) { - case ClientResyncMode::Manual: - os << "Manual"; - break; - case ClientResyncMode::DiscardLocal: - os << "DiscardLocal"; - break; - case ClientResyncMode::Recover: - os << "Recover"; - break; - case ClientResyncMode::RecoverOrDiscard: - os << "RecoverOrDiscard"; - break; - } - return os; -} - -namespace _impl::client_reset { - -static inline bool should_skip_table(const Transaction& group, TableKey key) -{ - return !group.table_is_public(key); -} - -void transfer_group(const Transaction& group_src, Transaction& group_dst, util::Logger& logger, - bool allow_schema_additions) -{ - logger.debug(util::LogCategory::reset, - "transfer_group, src size = %1, dst size = %2, allow_schema_additions = %3", group_src.size(), - group_dst.size(), allow_schema_additions); - - // Turn off the sync history tracking during state transfer since it will be thrown - // away immediately after anyways. This reduces the memory footprint of a client reset. - ClientReplication* client_repl = dynamic_cast(group_dst.get_replication()); - REALM_ASSERT_RELEASE(client_repl); - TempShortCircuitReplication sync_history_guard(*client_repl); - - // Find all tables in dst that should be removed. - std::set tables_to_remove; - for (auto table_key : group_dst.get_table_keys()) { - if (should_skip_table(group_dst, table_key)) - continue; - StringData table_name = group_dst.get_table_name(table_key); - logger.debug(util::LogCategory::reset, "key = %1, table_name = %2", table_key.value, table_name); - ConstTableRef table_src = group_src.get_table(table_name); - if (!table_src) { - logger.debug(util::LogCategory::reset, "Table '%1' will be removed", table_name); - tables_to_remove.insert(table_name); - continue; - } - // Check whether the table type is the same. - TableRef table_dst = group_dst.get_table(table_key); - auto pk_col_src = table_src->get_primary_key_column(); - auto pk_col_dst = table_dst->get_primary_key_column(); - bool has_pk_src = bool(pk_col_src); - bool has_pk_dst = bool(pk_col_dst); - if (has_pk_src != has_pk_dst) { - throw ClientResetFailed(util::format("Client reset requires a primary key column in %1 table '%2'", - (has_pk_src ? "dest" : "source"), table_name)); - } - if (!has_pk_src) - continue; - - // Now the tables both have primary keys. Check type. - if (pk_col_src.get_type() != pk_col_dst.get_type()) { - throw ClientResetFailed( - util::format("Client reset found incompatible primary key types (%1 vs %2) on '%3'", - pk_col_src.get_type(), pk_col_dst.get_type(), table_name)); - } - // Check collection type, nullability etc. but having an index doesn't matter; - ColumnAttrMask pk_col_src_attr = pk_col_src.get_attrs(); - ColumnAttrMask pk_col_dst_attr = pk_col_dst.get_attrs(); - pk_col_src_attr.reset(ColumnAttr::col_attr_Indexed); - pk_col_dst_attr.reset(ColumnAttr::col_attr_Indexed); - if (pk_col_src_attr != pk_col_dst_attr) { - throw ClientResetFailed( - util::format("Client reset found incompatible primary key attributes (%1 vs %2) on '%3'", - pk_col_src.value, pk_col_dst.value, table_name)); - } - // Check name. - StringData pk_col_name_src = table_src->get_column_name(pk_col_src); - StringData pk_col_name_dst = table_dst->get_column_name(pk_col_dst); - if (pk_col_name_src != pk_col_name_dst) { - throw ClientResetFailed( - util::format("Client reset requires equal pk column names but '%1' != '%2' on '%3'", pk_col_name_src, - pk_col_name_dst, table_name)); - } - // The table survives. - logger.debug(util::LogCategory::reset, "Table '%1' will remain", table_name); - } - - // If there have been any tables marked for removal stop. - // We consider two possible options for recovery: - // 1: Remove the tables. But this will generate destructive schema - // schema changes that the local Realm cannot advance through. - // Since this action will fail down the line anyway, give up now. - // 2: Keep the tables locally and ignore them. But the local app schema - // still has these classes and trying to modify anything in them will - // create sync instructions on tables that sync doesn't know about. - // As an exception in recovery mode, we assume that the corresponding - // additive schema changes will be part of the recovery upload. If they - // are present, then the server can choose to allow them (if in dev mode). - // If they are not present, then the server will emit an error the next time - // a value is set on the unknown property. - if (!allow_schema_additions && !tables_to_remove.empty()) { - std::string names_list; - for (const std::string& table_name : tables_to_remove) { - names_list += Group::table_name_to_class_name(table_name); - names_list += ", "; - } - if (names_list.size() > 2) { - // remove the final ", " - names_list = names_list.substr(0, names_list.size() - 2); - } - throw ClientResetFailed( - util::format("Client reset cannot recover when classes have been removed: {%1}", names_list)); - } - - // Create new tables in dst if needed. - for (auto table_key : group_src.get_table_keys()) { - if (should_skip_table(group_src, table_key)) - continue; - ConstTableRef table_src = group_src.get_table(table_key); - StringData table_name = table_src->get_name(); - auto pk_col_src = table_src->get_primary_key_column(); - TableRef table_dst = group_dst.get_table(table_name); - if (!table_dst) { - // Create the table. - if (table_src->is_embedded()) { - REALM_ASSERT(!pk_col_src); - group_dst.add_table(table_name, Table::Type::Embedded); - } - else { - REALM_ASSERT(pk_col_src); // a sync table will have a pk - auto pk_col_src = table_src->get_primary_key_column(); - DataType pk_type = DataType(pk_col_src.get_type()); - StringData pk_col_name = table_src->get_column_name(pk_col_src); - group_dst.add_table_with_primary_key(table_name, pk_type, pk_col_name, pk_col_src.is_nullable(), - table_src->get_table_type()); - } - } - } - - // Now the class tables are identical. - size_t num_tables; - { - size_t num_tables_src = 0; - for (auto table_key : group_src.get_table_keys()) { - if (!should_skip_table(group_src, table_key)) - ++num_tables_src; - } - size_t num_tables_dst = 0; - for (auto table_key : group_dst.get_table_keys()) { - if (!should_skip_table(group_dst, table_key)) - ++num_tables_dst; - } - REALM_ASSERT_EX(allow_schema_additions || num_tables_src == num_tables_dst, num_tables_src, num_tables_dst); - num_tables = num_tables_src; - } - logger.debug(util::LogCategory::reset, "The number of tables is %1", num_tables); - - // Remove columns in dst if they are absent in src. - for (auto table_key : group_src.get_table_keys()) { - if (should_skip_table(group_src, table_key)) - continue; - ConstTableRef table_src = group_src.get_table(table_key); - StringData table_name = table_src->get_name(); - TableRef table_dst = group_dst.get_table(table_name); - REALM_ASSERT(table_dst); - std::vector columns_to_remove; - for (ColKey col_key : table_dst->get_column_keys()) { - StringData col_name = table_dst->get_column_name(col_key); - ColKey col_key_src = table_src->get_column_key(col_name); - if (!col_key_src) { - columns_to_remove.push_back(col_name); - continue; - } - } - if (!allow_schema_additions && !columns_to_remove.empty()) { - std::string columns_list; - for (const std::string& col_name : columns_to_remove) { - columns_list += col_name; - columns_list += ", "; - } - throw ClientResetFailed( - util::format("Client reset cannot recover when columns have been removed from '%1': {%2}", table_name, - columns_list)); - } - } - - // Add columns in dst if present in src and absent in dst. - for (auto table_key : group_src.get_table_keys()) { - if (should_skip_table(group_src, table_key)) - continue; - ConstTableRef table_src = group_src.get_table(table_key); - StringData table_name = table_src->get_name(); - TableRef table_dst = group_dst.get_table(table_name); - REALM_ASSERT(table_dst); - for (ColKey col_key : table_src->get_column_keys()) { - StringData col_name = table_src->get_column_name(col_key); - ColKey col_key_dst = table_dst->get_column_key(col_name); - if (!col_key_dst) { - DataType col_type = table_src->get_column_type(col_key); - bool nullable = col_key.is_nullable(); - auto search_index_type = table_src->search_index_type(col_key); - logger.trace(util::LogCategory::reset, - "Create column, table = %1, column name = %2, " - " type = %3, nullable = %4, search_index = %5", - table_name, col_name, col_key.get_type(), nullable, search_index_type); - ColKey col_key_dst; - if (Table::is_link_type(col_key.get_type())) { - ConstTableRef target_src = table_src->get_link_target(col_key); - TableRef target_dst = group_dst.get_table(target_src->get_name()); - if (col_key.is_list()) { - col_key_dst = table_dst->add_column_list(*target_dst, col_name); - } - else if (col_key.is_set()) { - col_key_dst = table_dst->add_column_set(*target_dst, col_name); - } - else if (col_key.is_dictionary()) { - DataType key_type = table_src->get_dictionary_key_type(col_key); - col_key_dst = table_dst->add_column_dictionary(*target_dst, col_name, key_type); - } - else { - REALM_ASSERT(!col_key.is_collection()); - col_key_dst = table_dst->add_column(*target_dst, col_name); - } - } - else if (col_key.is_list()) { - col_key_dst = table_dst->add_column_list(col_type, col_name, nullable); - } - else if (col_key.is_set()) { - col_key_dst = table_dst->add_column_set(col_type, col_name, nullable); - } - else if (col_key.is_dictionary()) { - DataType key_type = table_src->get_dictionary_key_type(col_key); - col_key_dst = table_dst->add_column_dictionary(col_type, col_name, nullable, key_type); - } - else { - REALM_ASSERT(!col_key.is_collection()); - col_key_dst = table_dst->add_column(col_type, col_name, nullable); - } - - if (search_index_type != IndexType::None) - table_dst->add_search_index(col_key_dst, search_index_type); - } - else { - // column preexists in dest, make sure the types match - if (col_key.get_type() != col_key_dst.get_type()) { - throw ClientResetFailed(util::format( - "Incompatible column type change detected during client reset for '%1.%2' (%3 vs %4)", - table_name, col_name, col_key.get_type(), col_key_dst.get_type())); - } - ColumnAttrMask src_col_attrs = col_key.get_attrs(); - ColumnAttrMask dst_col_attrs = col_key_dst.get_attrs(); - src_col_attrs.reset(ColumnAttr::col_attr_Indexed); - dst_col_attrs.reset(ColumnAttr::col_attr_Indexed); - // make sure the attributes such as collection type, nullability etc. match - // but index equality doesn't matter here. - if (src_col_attrs != dst_col_attrs) { - throw ClientResetFailed(util::format( - "Incompatable column attribute change detected during client reset for '%1.%2' (%3 vs %4)", - table_name, col_name, col_key.value, col_key_dst.value)); - } - } - } - } - - // Now the schemas are identical. - - // Remove objects in dst that are absent in src. - for (auto table_key : group_src.get_table_keys()) { - if (should_skip_table(group_src, table_key)) - continue; - auto table_src = group_src.get_table(table_key); - // There are no primary keys in embedded tables but this is ok, because - // embedded objects are tied to the lifetime of top level objects. - if (table_src->is_embedded()) - continue; - StringData table_name = table_src->get_name(); - logger.debug(util::LogCategory::reset, "Removing objects in '%1'", table_name); - auto table_dst = group_dst.get_table(table_name); - - auto pk_col = table_dst->get_primary_key_column(); - REALM_ASSERT_DEBUG(pk_col); // sync realms always have a pk - std::vector> objects_to_remove; - for (auto obj : *table_dst) { - auto pk = obj.get_any(pk_col); - if (!table_src->find_primary_key(pk)) { - objects_to_remove.emplace_back(pk, obj.get_key()); - } - } - for (auto& pair : objects_to_remove) { - logger.debug(util::LogCategory::reset, " removing '%1'", pair.first); - table_dst->remove_object(pair.second); - } - } - - // We must re-create any missing objects that are absent in dst before trying to copy - // their properties because creating them may re-create any dangling links which would - // otherwise cause inconsistencies when re-creating lists of links. - for (auto table_key : group_src.get_table_keys()) { - ConstTableRef table_src = group_src.get_table(table_key); - auto table_name = table_src->get_name(); - if (should_skip_table(group_src, table_key) || table_src->is_embedded()) - continue; - TableRef table_dst = group_dst.get_table(table_name); - auto pk_col = table_src->get_primary_key_column(); - REALM_ASSERT(pk_col); - logger.debug(util::LogCategory::reset, - "Creating missing objects for table '%1', number of rows = %2, " - "primary_key_col = %3, primary_key_type = %4", - table_name, table_src->size(), pk_col.get_index().val, pk_col.get_type()); - for (const Obj& src : *table_src) { - bool created = false; - table_dst->create_object_with_primary_key(src.get_primary_key(), &created); - if (created) { - logger.debug(util::LogCategory::reset, " created %1", src.get_primary_key()); - } - } - } - - converters::EmbeddedObjectConverter embedded_tracker; - // Now src and dst have identical schemas and all the top level objects are created. - // What is left to do is to diff all properties of the existing objects. - // Embedded objects are created on the fly. - for (auto table_key : group_src.get_table_keys()) { - if (should_skip_table(group_src, table_key)) - continue; - ConstTableRef table_src = group_src.get_table(table_key); - // Embedded objects don't have a primary key, so they are handled - // as a special case when they are encountered as a link value. - if (table_src->is_embedded()) - continue; - StringData table_name = table_src->get_name(); - TableRef table_dst = group_dst.get_table(table_name); - REALM_ASSERT_EX(allow_schema_additions || table_src->get_column_count() == table_dst->get_column_count(), - allow_schema_additions, table_src->get_column_count(), table_dst->get_column_count()); - auto pk_col = table_src->get_primary_key_column(); - REALM_ASSERT(pk_col); - logger.debug(util::LogCategory::reset, - "Updating values for table '%1', number of rows = %2, " - "number of columns = %3, primary_key_col = %4, " - "primary_key_type = %5", - table_name, table_src->size(), table_src->get_column_count(), pk_col.get_index().val, - pk_col.get_type()); - - converters::InterRealmObjectConverter converter(table_src, table_dst, &embedded_tracker); - - for (const Obj& src : *table_src) { - auto src_pk = src.get_primary_key(); - // create the object - it should have been created above. - auto dst = table_dst->get_object_with_primary_key(src_pk); - REALM_ASSERT(dst); - - bool updated = false; - converter.copy(src, dst, &updated); - if (updated) { - logger.debug(util::LogCategory::reset, " updating %1", src_pk); - } - } - embedded_tracker.process_pending(); - } -} - -static ClientResyncMode reset_precheck_guard(const TransactionRef& wt_local, ClientResyncMode mode, - PendingReset::Action action, const Status& error, util::Logger& logger) -{ - if (auto previous_reset = sync::PendingResetStore::has_pending_reset(*wt_local)) { - logger.info(util::LogCategory::reset, "Found a previous %1", *previous_reset); - if (action != previous_reset->action) { - // IF a different client reset is being performed, cler the pending client reset and start over. - logger.info(util::LogCategory::reset, - "New '%1' client reset of type: '%2' is incompatible - clearing previous reset", action, - mode); - sync::PendingResetStore::clear_pending_reset(*wt_local); - } - else { - switch (previous_reset->mode) { - case ClientResyncMode::Manual: - REALM_UNREACHABLE(); - case ClientResyncMode::DiscardLocal: - throw ClientResetFailed(util::format("A previous '%1' mode reset from %2 did not succeed, " - "giving up on '%3' mode to prevent a cycle", - previous_reset->mode, previous_reset->time, mode)); - case ClientResyncMode::Recover: - switch (mode) { - case ClientResyncMode::Recover: - throw ClientResetFailed( - util::format("A previous '%1' mode reset from %2 did not succeed, " - "giving up on '%3' mode to prevent a cycle", - previous_reset->mode, previous_reset->time, mode)); - case ClientResyncMode::RecoverOrDiscard: - mode = ClientResyncMode::DiscardLocal; - logger.info( - util::LogCategory::reset, - "A previous '%1' mode reset from %2 downgrades this mode ('%3') to DiscardLocal", - previous_reset->mode, previous_reset->time, mode); - sync::PendingResetStore::clear_pending_reset(*wt_local); - break; - case ClientResyncMode::DiscardLocal: - sync::PendingResetStore::clear_pending_reset(*wt_local); - // previous mode Recover and this mode is Discard, this is not a cycle yet - break; - case ClientResyncMode::Manual: - REALM_UNREACHABLE(); - } - break; - case ClientResyncMode::RecoverOrDiscard: - throw ClientResetFailed(util::format("Unexpected previous '%1' mode reset from %2 did not " - "succeed, giving up on '%3' mode to prevent a cycle", - previous_reset->mode, previous_reset->time, mode)); - } - } - } - if (action == PendingReset::Action::ClientResetNoRecovery) { - if (mode == ClientResyncMode::Recover) { - throw ClientResetFailed( - "Client reset mode is set to 'Recover' but the server does not allow recovery for this client"); - } - else if (mode == ClientResyncMode::RecoverOrDiscard) { - logger.info(util::LogCategory::reset, - "Client reset in 'RecoverOrDiscard' is choosing 'DiscardLocal' because the server does not " - "permit recovery for this client"); - mode = ClientResyncMode::DiscardLocal; - } - } - sync::PendingResetStore::track_reset(*wt_local, mode, action, error); - // Ensure we save the tracker object even if we encounter an error and roll - // back the client reset later - wt_local->commit_and_continue_writing(); - return mode; -} - -bool perform_client_reset_diff(DB& db_local, sync::ClientReset& reset_config, util::Logger& logger, - sync::SubscriptionStore* sub_store) -{ - DB& db_remote = *reset_config.fresh_copy; - auto wt_local = db_local.start_write(); - auto actual_mode = - reset_precheck_guard(wt_local, reset_config.mode, reset_config.action, reset_config.error, logger); - bool recover_local_changes = - actual_mode == ClientResyncMode::Recover || actual_mode == ClientResyncMode::RecoverOrDiscard; - - auto& repl_local = dynamic_cast(*db_local.get_replication()); - auto& history_local = repl_local.get_history(); - history_local.ensure_updated(wt_local->get_version()); - VersionID old_version_local = wt_local->get_version_of_current_transaction(); - - auto& repl_remote = dynamic_cast(*db_remote.get_replication()); - auto& history_remote = repl_remote.get_history(); - - sync::SaltedVersion fresh_server_version = {0, 0}; - sync::SaltedFileIdent fresh_file_ident = {0, 0}; - { - SyncProgress remote_progress; - sync::version_type remote_version_unused; - history_remote.get_status(remote_version_unused, fresh_file_ident, remote_progress); - fresh_server_version = remote_progress.latest_server_version; - } - - logger.info(util::LogCategory::reset, - "Client reset: path_local = %1, fresh_file_ident = (ident: %2, salt: %3), " - "fresh_server_version = (ident: %4, salt: %5), remote_path = %6, requested_mode = %7, action = %8, " - "actual_mode = %9, will_recover = %10, originating_error = %11", - db_local.get_path(), fresh_file_ident.ident, fresh_file_ident.salt, fresh_server_version.version, - fresh_server_version.salt, db_remote.get_path(), reset_config.mode, reset_config.action, actual_mode, - recover_local_changes, reset_config.error); - - TransactionRef tr_remote; - std::vector recovered; - if (recover_local_changes) { - auto frozen_pre_local_state = db_local.start_frozen(); - auto local_changes = history_local.get_local_changes(wt_local->get_version()); - logger.info("Local changesets to recover: %1", local_changes.size()); - - tr_remote = db_remote.start_write(); - recovered = process_recovered_changesets(*tr_remote, *frozen_pre_local_state, logger, local_changes); - } - else { - tr_remote = db_remote.start_read(); - } - - // transform the local Realm such that all public tables become identical to the remote Realm - transfer_group(*tr_remote, *wt_local, logger, false); - - // now that the state of the fresh and local Realms are identical, - // reset the local sync history and steal the fresh Realm's ident - history_local.set_history_adjustments(logger, wt_local->get_version(), fresh_file_ident, fresh_server_version, - recovered); - - if (sub_store) { - if (recover_local_changes) { - sub_store->mark_active_as_complete(*wt_local); - } - else { - sub_store->set_active_as_latest(*wt_local); - } - } - - wt_local->commit_and_continue_as_read(); - - VersionID new_version_local = wt_local->get_version_of_current_transaction(); - logger.info(util::LogCategory::reset, - "perform_client_reset_diff is done: old_version = (version: %1, index: %2), " - "new_version = (version: %3, index: %4)", - old_version_local.version, old_version_local.index, new_version_local.version, - new_version_local.index); - - return recover_local_changes; -} - -} // namespace _impl::client_reset -} // namespace realm diff --git a/src/realm/sync/noinst/client_reset.hpp b/src/realm/sync/noinst/client_reset.hpp deleted file mode 100644 index d28714bb21f..00000000000 --- a/src/realm/sync/noinst/client_reset.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_NOINST_CLIENT_RESET_HPP -#define REALM_NOINST_CLIENT_RESET_HPP - -#include -#include -#include -#include - -#include - -namespace realm { - -std::ostream& operator<<(std::ostream& os, const ClientResyncMode& mode); - -namespace sync { -class SubscriptionStore; - -// The reset fails if there seems to be conflict between the -// instructions and state. -// -// After failure the processing stops and the client reset will -// drop all local changes. -// -// Failure is triggered by: -// 1. Destructive schema changes. -// 2. Creation of an already existing table with another type. -// 3. Creation of an already existing column with another type. -struct ClientResetFailed : public std::runtime_error { - using std::runtime_error::runtime_error; -}; - -} // namespace sync - -namespace _impl::client_reset { - -// transfer_group() transfers all tables, columns, objects and values from the src -// group to the dst group and deletes everything in the dst group that is absent in -// the src group. An update is only performed when a comparison shows that a -// change is needed. In this way, the continuous transaction history of changes -// is minimal. -// -// The result is that src group is unchanged and the dst group is equal to src -// when this function returns. -void transfer_group(const Transaction& tr_src, Transaction& tr_dst, util::Logger& logger, - bool allow_schema_additions); - -// preform_client_reset_diff() takes the Realm performs a client reset on -// the Realm in 'path_local' given the Realm 'path_fresh' as the source of truth. -// If the fresh path is not provided, discard mode is assumed and all data in the local -// Realm is removed. -// If the fresh path is provided, the local Realm is changed such that its state is equal -// to the fresh Realm. Then the local Realm will have its client file ident set to -// the file ident from the fresh realm -bool perform_client_reset_diff(DB& db, sync::ClientReset& reset_config, util::Logger& logger, - sync::SubscriptionStore* sub_store); - -} // namespace _impl::client_reset -} // namespace realm - -#endif // REALM_NOINST_CLIENT_RESET_HPP diff --git a/src/realm/sync/noinst/client_reset_operation.cpp b/src/realm/sync/noinst/client_reset_operation.cpp deleted file mode 100644 index 98d752e64b7..00000000000 --- a/src/realm/sync/noinst/client_reset_operation.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/************************************************************************* - * - * Copyright 2021 Realm Inc. - * - * 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 - -#include -#include -#include -#include -#include - -namespace realm::_impl::client_reset { - -namespace { - -constexpr static std::string_view c_fresh_suffix(".fresh"); - -} // namespace - -std::string get_fresh_path_for(const std::string& path) -{ - const size_t suffix_len = c_fresh_suffix.size(); - REALM_ASSERT(path.length()); - REALM_ASSERT_DEBUG_EX( - path.size() < suffix_len || path.substr(path.size() - suffix_len, suffix_len) != c_fresh_suffix, path); - return path + c_fresh_suffix.data(); -} - -bool is_fresh_path(const std::string& path) -{ - const size_t suffix_len = c_fresh_suffix.size(); - REALM_ASSERT(path.length()); - if (path.size() < suffix_len) { - return false; - } - return path.substr(path.size() - suffix_len, suffix_len) == c_fresh_suffix; -} - -bool perform_client_reset(util::Logger& logger, DB& db, sync::ClientReset&& reset_config, - sync::SubscriptionStore* sub_store) -{ - REALM_ASSERT(reset_config.mode != ClientResyncMode::Manual); - REALM_ASSERT(reset_config.fresh_copy); - logger.debug(util::LogCategory::reset, - "Possibly beginning client reset operation: realm_path = %1, mode = %2, action = %3, error = %4", - db.get_path(), reset_config.mode, reset_config.action, reset_config.error); - - auto always_try_clean_up = util::make_scope_exit([&]() noexcept { - std::string path_to_clean = reset_config.fresh_copy->get_path(); - try { - reset_config.fresh_copy->close(); - constexpr bool delete_lockfile = true; - DB::delete_files(path_to_clean, nullptr, delete_lockfile); - } - catch (const std::exception& err) { - logger.warn(util::LogCategory::reset, - "In ClientResetOperation::finalize, the fresh copy '%1' could not be cleaned up due to " - "an exception: '%2'", - path_to_clean, err.what()); - // ignored, this is just a best effort - } - }); - - // only do the reset if there is data to reset - // if there is nothing in this Realm, then there is nothing to reset and - // sync should be able to continue as normal - auto latest_version = db.get_version_id_of_latest_snapshot(); - bool local_realm_exists = latest_version.version > 1; - if (!local_realm_exists) { - logger.debug(util::LogCategory::reset, - "Local Realm file has never been written to, so skipping client reset."); - return false; - } - - auto notify_before = std::move(reset_config.notify_before_client_reset); - auto notify_after = std::move(reset_config.notify_after_client_reset); - - VersionID frozen_before_state_version = notify_before ? notify_before() : latest_version; - - // If m_notify_after is set, pin the previous state to keep it around. - TransactionRef previous_state; - if (notify_after) { - previous_state = db.start_frozen(frozen_before_state_version); - } - bool did_recover = client_reset::perform_client_reset_diff(db, reset_config, logger, sub_store); // throws - - if (notify_after) { - notify_after(previous_state->get_version_of_current_transaction(), did_recover); - } - - return true; -} - -} // namespace realm::_impl::client_reset diff --git a/src/realm/sync/noinst/client_reset_operation.hpp b/src/realm/sync/noinst/client_reset_operation.hpp deleted file mode 100644 index 5ed44f0e325..00000000000 --- a/src/realm/sync/noinst/client_reset_operation.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/************************************************************************* - * - * Copyright 2021 Realm Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef REALM_NOINST_CLIENT_RESET_OPERATION_HPP -#define REALM_NOINST_CLIENT_RESET_OPERATION_HPP - -#include -#include -#include -#include -#include -#include - -namespace realm::sync { -class SubscriptionStore; -} - -namespace realm::_impl::client_reset { -using CallbackBeforeType = util::UniqueFunction; -using CallbackAfterType = util::UniqueFunction; - -std::string get_fresh_path_for(const std::string& realm_path); -bool is_fresh_path(const std::string& realm_path); - -bool perform_client_reset(util::Logger& logger, DB& db, sync::ClientReset&& reset_config, - sync::SubscriptionStore* sub_store); - -} // namespace realm::_impl::client_reset - -#endif // REALM_NOINST_CLIENT_RESET_OPERATION_HPP diff --git a/src/realm/sync/noinst/client_reset_recovery.cpp b/src/realm/sync/noinst/client_reset_recovery.cpp deleted file mode 100644 index 3eb0a5ee966..00000000000 --- a/src/realm/sync/noinst/client_reset_recovery.cpp +++ /dev/null @@ -1,1218 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// 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 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace realm; -using namespace realm::_impl; -using namespace realm::sync; - -namespace { - -// State tracking of operations on list indices. All list operations in a recovered changeset -// must apply to a "known" index. An index is known if the element at that position was added -// by the recovery itself. If any operation applies to an "unknown" index, the list will go into -// a requires_manual_copy state which means that all further operations on the list are ignored -// and the entire list is copied over verbatim at the end. -struct ListTracker { - struct CrossListIndex { - uint32_t local; - uint32_t remote; - }; - - util::Optional insert(uint32_t local_index, size_t remote_list_size); - util::Optional update(uint32_t index); - void clear(); - bool move(uint32_t from, uint32_t to, size_t lst_size, uint32_t& remote_from_out, uint32_t& remote_to_out); - bool remove(uint32_t index, uint32_t& remote_index_out); - bool requires_manual_copy() const; - void queue_for_manual_copy(); - void mark_as_copied(); - -private: - std::vector m_indices_allowed; - bool m_requires_manual_copy = false; - bool m_has_been_copied = false; -}; - -struct InternDictKey { - bool is_null() const - { - return m_pos == realm::npos && m_size == realm::npos; - } - constexpr bool operator==(const InternDictKey& other) const noexcept - { - return m_pos == other.m_pos && m_size == other.m_size; - } - constexpr bool operator!=(const InternDictKey& other) const noexcept - { - return !operator==(other); - } - constexpr bool operator<(const InternDictKey& other) const noexcept - { - if (m_pos < other.m_pos) { - return true; - } - else if (m_pos == other.m_pos) { - return m_size < other.m_size; - } - return false; - } - -private: - friend struct InterningBuffer; - size_t m_pos = realm::npos; - size_t m_size = realm::npos; -}; - -struct InterningBuffer { - std::string_view get_key(const InternDictKey& key) const; - InternDictKey get_or_add(const std::string_view& str); - -private: - std::string m_dict_keys_buffer; - std::vector m_dict_keys; -}; - -// A wrapper around a PathInstruction which enables storing this path in a -// FlatMap or other container. The advantage of using this instead of a PathInstruction -// is the use of ColKey instead of column names and that because it is not possible to use -// the InternStrings of a PathInstruction because they are tied to a specific Changeset, -// while the ListPath can be used across multiple Changesets. -struct ListPath { - ListPath(TableKey table_key, ObjKey obj_key); - - struct Element { - explicit Element(const InternDictKey& str); - explicit Element(ColKey key); - union { - InternDictKey intern_key; - size_t index; - ColKey col_key; - }; - enum class Type { - InternKey, - ListIndex, - ColumnKey, - } type; - - bool operator<(const Element& other) const noexcept; - }; - - void append(const Element& item); - bool operator<(const ListPath& other) const noexcept; - std::string path_to_string(Transaction& remote, const InterningBuffer& buffer); - - using const_iterator = typename std::vector::const_iterator; - using iterator = typename std::vector::iterator; - const_iterator begin() const noexcept - { - return m_path.begin(); - } - const_iterator end() const noexcept - { - return m_path.end(); - } - TableKey table_key() const noexcept - { - return m_table_key; - } - ObjKey obj_key() const noexcept - { - return m_obj_key; - } - -private: - std::vector m_path; - TableKey m_table_key; - ObjKey m_obj_key; -}; - -struct RecoverLocalChangesetsHandler : public sync::InstructionApplier { - RecoverLocalChangesetsHandler(Transaction& dest_wt, Transaction& frozen_pre_local_state, util::Logger& logger); - util::AppendBuffer process_changeset(const ChunkedBinaryData& changeset); - -private: - using Instruction = sync::Instruction; - using ListPathCallback = util::UniqueFunction; - - struct RecoveryResolver : public InstructionApplier::PathResolver { - RecoveryResolver(RecoverLocalChangesetsHandler* applier, Instruction::PathInstruction& instr, - const std::string_view& instr_name); - Status on_property(Obj&, ColKey) override; - void on_list(LstBase&) override; - Status on_list_index(LstBase&, uint32_t) override; - void on_dictionary(Dictionary&) override; - Status on_dictionary_key(Dictionary&, Mixed) override; - void on_set(SetBase&) override; - void on_error(const std::string&) override; - Status on_mixed_type_changed(const std::string&) override; - void on_column_advance(ColKey) override; - void on_dict_key_advance(StringData) override; - Status on_list_index_advance(uint32_t) override; - Status on_null_link_advance(StringData, StringData) override; - Status on_dict_key_not_found(StringData, StringData, StringData) override; - Status on_begin(const util::Optional&) override; - void on_finish() override {} - - void update_path_index(uint32_t ndx); - - ListPath m_list_path; - Instruction::PathInstruction& m_mutable_instr; - RecoverLocalChangesetsHandler* m_recovery_applier; - }; - - REALM_NORETURN void handle_error(const std::string& message) const; - void copy_lists_with_unrecoverable_changes(); - - bool resolve_path(ListPath& path, Obj remote_obj, Obj local_obj, - util::UniqueFunction callback); - bool resolve(ListPath& path, util::UniqueFunction callback); - -#define REALM_DECLARE_INSTRUCTION_HANDLER(X) void operator()(const Instruction::X&) override; - REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DECLARE_INSTRUCTION_HANDLER) -#undef REALM_DECLARE_INSTRUCTION_HANDLER - friend struct sync::Instruction; // to allow visitor - -private: - Transaction& m_frozen_pre_local_state; - // Keeping the member variable reference to a logger since the lifetime of this class is - // only within the function that created it. - util::Logger& m_logger; - InterningBuffer m_intern_keys; - // Track any recovered operations on lists to make sure that they are allowed. - // If not, the lists here will be copied verbatim from the local state to the remote. - util::FlatMap m_lists; - Replication* m_replication; -}; - -util::Optional ListTracker::insert(uint32_t local_index, size_t remote_list_size) -{ - if (m_requires_manual_copy) { - return util::none; - } - uint32_t remote_index = local_index; - if (remote_index > remote_list_size) { - remote_index = static_cast(remote_list_size); - } - for (auto& ndx : m_indices_allowed) { - if (ndx.local >= local_index) { - ++ndx.local; - ++ndx.remote; - } - } - ListTracker::CrossListIndex inserted{local_index, remote_index}; - m_indices_allowed.push_back(inserted); - return inserted; -} - -util::Optional ListTracker::update(uint32_t index) -{ - if (m_requires_manual_copy) { - return util::none; - } - for (auto& ndx : m_indices_allowed) { - if (ndx.local == index) { - return ndx; - } - } - queue_for_manual_copy(); - return util::none; -} - -void ListTracker::clear() -{ - // any local operations to a list after a clear are - // strictly on locally added elements so no need to continue tracking - m_requires_manual_copy = false; - m_indices_allowed.clear(); -} - -bool ListTracker::move(uint32_t from, uint32_t to, size_t lst_size, uint32_t& remote_from_out, - uint32_t& remote_to_out) -{ - if (m_requires_manual_copy) { - return false; - } - remote_from_out = from; - remote_to_out = to; - - // Only allow move operations that operate on known indices. - // This requires that both local elements 'from' and 'to' are known. - auto target_from = m_indices_allowed.end(); - auto target_to = m_indices_allowed.end(); - for (auto it = m_indices_allowed.begin(); it != m_indices_allowed.end(); ++it) { - if (it->local == from) { - REALM_ASSERT(target_from == m_indices_allowed.end()); - target_from = it; - } - else if (it->local == to) { - REALM_ASSERT(target_to == m_indices_allowed.end()); - target_to = it; - } - } - if (target_from == m_indices_allowed.end() || target_to == m_indices_allowed.end()) { - queue_for_manual_copy(); - return false; - } - REALM_ASSERT_EX(target_from->remote <= lst_size, from, to, target_from->remote, target_to->remote, lst_size); - REALM_ASSERT_EX(target_to->remote <= lst_size, from, to, target_from->remote, target_to->remote, lst_size); - - if (from < to) { - for (auto it = m_indices_allowed.begin(); it != m_indices_allowed.end(); ++it) { - if (it->local > from && it->local <= to) { - REALM_ASSERT(it->local != 0); - REALM_ASSERT(it->remote != 0); - --it->local; - --it->remote; - } - } - remote_from_out = target_from->remote; - remote_to_out = target_to->remote + 1; - target_from->local = target_to->local + 1; - target_from->remote = target_to->remote + 1; - return true; - } - else if (from > to) { - for (auto it = m_indices_allowed.begin(); it != m_indices_allowed.end(); ++it) { - if (it->local < from && it->local >= to) { - REALM_ASSERT_EX(it->remote + 1 < lst_size, it->remote, lst_size); - ++it->local; - ++it->remote; - } - } - remote_from_out = target_from->remote; - remote_to_out = target_to->remote - 1; - target_from->local = target_to->local - 1; - target_from->remote = target_to->remote - 1; - return true; - } - // from == to - // we shouldn't be generating an instruction for this case, but it is a no-op - return true; // LCOV_EXCL_LINE -} - -bool ListTracker::remove(uint32_t index, uint32_t& remote_index_out) -{ - if (m_requires_manual_copy) { - return false; - } - remote_index_out = index; - bool found = false; - for (auto it = m_indices_allowed.begin(); it != m_indices_allowed.end();) { - if (it->local == index) { - found = true; - remote_index_out = it->remote; - it = m_indices_allowed.erase(it); - continue; - } - else if (it->local > index) { - --it->local; - --it->remote; - } - ++it; - } - if (!found) { - queue_for_manual_copy(); - return false; - } - return true; -} - -bool ListTracker::requires_manual_copy() const -{ - // We only ever need to copy a list once as we go straight to the final state - return m_requires_manual_copy && !m_has_been_copied; -} - -void ListTracker::queue_for_manual_copy() -{ - m_requires_manual_copy = true; - m_indices_allowed.clear(); -} - -void ListTracker::mark_as_copied() -{ - m_has_been_copied = true; -} - -std::string_view InterningBuffer::get_key(const InternDictKey& key) const -{ - if (key.is_null()) { - return {}; - } - if (key.m_size == 0) { - return ""; - } - REALM_ASSERT(key.m_pos < m_dict_keys_buffer.size()); - REALM_ASSERT(key.m_pos + key.m_size <= m_dict_keys_buffer.size()); - return std::string_view{m_dict_keys_buffer.data() + key.m_pos, key.m_size}; -} - -InternDictKey InterningBuffer::get_or_add(const std::string_view& str) -{ - for (auto& key : m_dict_keys) { - std::string_view existing = get_key(key); - if (existing == str) { - return key; - } - } - InternDictKey new_key{}; - if (str.data() == nullptr) { - m_dict_keys.push_back(new_key); - } - else { - size_t next_pos = m_dict_keys_buffer.size(); - new_key.m_pos = next_pos; - new_key.m_size = str.size(); - m_dict_keys_buffer.append(str); - m_dict_keys.push_back(new_key); - } - return new_key; -} - -ListPath::Element::Element(const InternDictKey& str) - : intern_key(str) - , type(Type::InternKey) -{ -} - -ListPath::Element::Element(ColKey key) - : col_key(key) - , type(Type::ColumnKey) -{ -} - -bool ListPath::Element::operator<(const Element& other) const noexcept -{ - if (type < other.type) { - return true; - } - if (type == other.type) { - switch (type) { - case Type::InternKey: - return intern_key < other.intern_key; - case Type::ListIndex: - return index < other.index; - case Type::ColumnKey: - return col_key < other.col_key; - } - } - return false; -} - -ListPath::ListPath(TableKey table_key, ObjKey obj_key) - : m_table_key(table_key) - , m_obj_key(obj_key) -{ -} - -void ListPath::append(const Element& item) -{ - m_path.push_back(item); -} - -bool ListPath::operator<(const ListPath& other) const noexcept -{ - if (m_table_key < other.m_table_key || m_obj_key < other.m_obj_key || m_path.size() < other.m_path.size()) { - return true; - } - return std::lexicographical_compare(m_path.begin(), m_path.end(), other.m_path.begin(), other.m_path.end()); -} - -std::string ListPath::path_to_string(Transaction& remote, const InterningBuffer& buffer) -{ - TableRef remote_table = remote.get_table(m_table_key); - - std::string path = util::format("%1", remote_table->get_name()); - if (Obj base_obj = remote_table->try_get_object(m_obj_key)) { - path += util::format(".pk=%1", base_obj.get_primary_key()); - } - else { - path += util::format(".%1(removed)", m_obj_key); - } - for (auto& e : m_path) { - switch (e.type) { - case Element::Type::ColumnKey: - path += util::format(".%1", remote_table->get_column_name(e.col_key)); - remote_table = remote_table->get_link_target(e.col_key); - break; - case Element::Type::ListIndex: - path += util::format("[%1]", e.index); - break; - case Element::Type::InternKey: - path += util::format("[key='%1']", buffer.get_key(e.intern_key)); - break; - } - } - return path; -} - -RecoverLocalChangesetsHandler::RecoverLocalChangesetsHandler(Transaction& dest_wt, - Transaction& frozen_pre_local_state, - util::Logger& logger) - : InstructionApplier(dest_wt) - , m_frozen_pre_local_state{frozen_pre_local_state} - , m_logger{logger} - , m_replication{dest_wt.get_replication()} -{ -} - -REALM_NORETURN void RecoverLocalChangesetsHandler::handle_error(const std::string& message) const -{ - std::string full_message = - util::format("Unable to automatically recover local changes during client reset: '%1'", message); - m_logger.error(util::LogCategory::reset, full_message.c_str()); - throw realm::sync::ClientResetFailed(full_message); -} - -util::AppendBuffer RecoverLocalChangesetsHandler::process_changeset(const ChunkedBinaryData& changeset) -{ - ChunkedBinaryInputStream in{changeset}; - size_t decompressed_size; - auto decompressed = util::compression::decompress_nonportable_input_stream(in, decompressed_size); - if (!decompressed) - return {}; - - sync::Changeset parsed_changeset; - sync::parse_changeset(*decompressed, parsed_changeset); // Throws -#if REALM_DEBUG - if (m_logger.would_log(util::Logger::Level::trace)) { - std::stringstream dumped_changeset; - parsed_changeset.print(dumped_changeset); - m_logger.trace(util::LogCategory::reset, "Recovering changeset: %1", dumped_changeset.str()); - } -#endif - - InstructionApplier::begin_apply(parsed_changeset); - for (auto instr : parsed_changeset) { - if (!instr) - continue; - instr->visit(*this); // Throws - } - InstructionApplier::end_apply(); - - copy_lists_with_unrecoverable_changes(); - - auto& repl = static_cast(*m_replication); - auto buffer = repl.get_instruction_encoder().release(); - repl.reset(); - return buffer; -} - -void RecoverLocalChangesetsHandler::copy_lists_with_unrecoverable_changes() -{ - // Any modifications, moves or deletes to list elements which were not also created in the recovery - // cannot be reliably applied because there is no way to know if the indices on the server have - // shifted without a reliable server side history. For these lists, create a consistent state by - // copying over the entire list from the recovering client's state. This does create a "last recovery wins" - // scenario for modifications to lists, but this is only a best effort. - // For example, consider a list [A,B]. - // Now the server has been reset, and applied an ArrayMove from a different client producing [B,A] - // A client being reset tries to recover the instruction ArrayErase(index=0) intending to erase A. - // But if this instruction were to be applied to the server's array, element B would be erased which is wrong. - // So to prevent this, upon discovery of this type of instruction, replace the entire array to the client's - // final state which would be [B]. - // IDEA: if a unique id were associated with each list element, we could recover lists correctly because - // we would know where list elements ended up or if they were deleted by the server. - using namespace realm::converters; - EmbeddedObjectConverter embedded_object_tracker; - for (auto& [path, tracker] : m_lists) { - if (!tracker.requires_manual_copy()) - continue; - - std::string path_str = path.path_to_string(m_transaction, m_intern_keys); - bool did_translate = resolve(path, [&](LstBase& remote_list, LstBase& local_list) { - ConstTableRef local_table = local_list.get_table(); - ConstTableRef remote_table = remote_list.get_table(); - ColKey local_col_key = local_list.get_col_key(); - ColKey remote_col_key = remote_list.get_col_key(); - InterRealmValueConverter value_converter(local_table, local_col_key, remote_table, remote_col_key, - &embedded_object_tracker); - m_logger.debug(util::LogCategory::reset, "Recovery overwrites list for '%1' size: %2 -> %3", path_str, - remote_list.size(), local_list.size()); - value_converter.copy_list(local_list, remote_list); - embedded_object_tracker.process_pending(); - }); - if (did_translate) { - tracker.mark_as_copied(); - } - else { - // object no longer exists in the local state, ignore and continue - m_logger.warn(util::LogCategory::reset, - "Discarding a list recovery made to an object which could not be resolved. " - "remote_path='%1'", - path_str); - } - } - embedded_object_tracker.process_pending(); -} - -bool RecoverLocalChangesetsHandler::resolve_path(ListPath& path, Obj remote_obj, Obj local_obj, - util::UniqueFunction callback) -{ - DictionaryPtr local_dict, remote_dict; - for (auto it = path.begin(); it != path.end();) { - if (!remote_obj || !local_obj) { - return false; - } - REALM_ASSERT(it->type != ListPath::Element::Type::ListIndex); - - if (it->type == ListPath::Element::Type::InternKey) { - StringData dict_key = m_intern_keys.get_key(it->intern_key); - // At least one dictionary does not contain the key. - if (!local_dict->contains(dict_key) || !remote_dict->contains(dict_key)) - return false; - auto local_any = local_dict->get(dict_key); - auto remote_any = remote_dict->get(dict_key); - // Type mismatch. - if (local_any != remote_any) - return false; - if (local_any.is_type(type_Link, type_TypedLink)) { - local_obj = local_dict->get_object(dict_key); - remote_obj = remote_dict->get_object(dict_key); - } - else if (local_any.is_type(type_Dictionary)) { - local_dict = local_dict->get_dictionary(dict_key); - remote_dict = remote_dict->get_dictionary(dict_key); - } - else if (local_any.is_type(type_List)) { - ++it; - REALM_ASSERT(it == path.end()); - auto local_list = local_dict->get_list(dict_key); - auto remote_list = remote_dict->get_list(dict_key); - callback(*remote_list, *local_list); - return true; - } - else { - return false; - } - ++it; - continue; - } - - REALM_ASSERT(it->type == ListPath::Element::Type::ColumnKey); - ColKey col = it->col_key; - REALM_ASSERT(col); - ColKey local_col = local_obj.get_table()->get_column_key(remote_obj.get_table()->get_column_name(col)); - REALM_ASSERT(local_col); - if (col.is_list()) { - ++it; - // A list is copied verbatim when there is an operation on an ambiguous index - // (includes accessing elements). An index is considered ambiguous if it was - // not just inserted. - // Once the list is marked to be copied, any access to nested collections - // or embedded objects through that list is stopped. - REALM_ASSERT(it == path.end()); - auto remote_list = remote_obj.get_listbase_ptr(col); - auto local_list = local_obj.get_listbase_ptr(local_col); - callback(*remote_list, *local_list); - return true; - } - else if (col.is_dictionary()) { - remote_dict = remote_obj.get_dictionary_ptr(col); - local_dict = local_obj.get_dictionary_ptr(local_col); - ++it; - } - else if (col.get_type() == col_type_Mixed) { - auto local_any = local_obj.get_any(local_col); - auto remote_any = remote_obj.get_any(col); - - if (local_any.is_type(type_List) && remote_any.is_type(type_List)) { - ++it; - REALM_ASSERT(it == path.end()); - Lst local_list{local_obj, local_col}; - Lst remote_list{remote_obj, col}; - callback(remote_list, local_list); - return true; - } - else if (local_any.is_type(type_Dictionary) && remote_any.is_type(type_Dictionary)) { - remote_dict = remote_obj.get_dictionary_ptr(col); - local_dict = local_obj.get_dictionary_ptr(local_col); - ++it; - } - else { - return false; - } - } - else { - // single link to embedded object - // Neither embedded object sets nor Mixed(TypedLink) to embedded objects are supported. - REALM_ASSERT_EX(!col.is_collection(), col); - REALM_ASSERT_EX(col.get_type() == col_type_Link, col); - StringData col_name = remote_obj.get_table()->get_column_name(col); - remote_obj = remote_obj.get_linked_object(col); - local_obj = local_obj.get_linked_object(col_name); - ++it; - } - } - return false; -} - -bool RecoverLocalChangesetsHandler::resolve(ListPath& path, util::UniqueFunction callback) -{ - auto remote_table = m_transaction.get_table(path.table_key()); - if (!remote_table) - return false; - - auto local_table = m_frozen_pre_local_state.get_table(remote_table->get_name()); - if (!local_table) - return false; - - auto remote_obj = remote_table->try_get_object(path.obj_key()); - if (!remote_obj) - return false; - - auto local_obj_key = local_table->find_primary_key(remote_obj.get_primary_key()); - if (!local_obj_key) - return false; - - return resolve_path(path, remote_obj, local_table->get_object(local_obj_key), std::move(callback)); -} - -RecoverLocalChangesetsHandler::RecoveryResolver::RecoveryResolver(RecoverLocalChangesetsHandler* applier, - Instruction::PathInstruction& instr, - const std::string_view& instr_name) - : InstructionApplier::PathResolver(applier, instr, instr_name) - , m_list_path(TableKey{}, ObjKey{}) - , m_mutable_instr(instr) - , m_recovery_applier(applier) -{ -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_property(Obj&, ColKey) -{ - m_recovery_applier->handle_error(util::format("Invalid path for %1 (object, column)", m_instr_name)); - return Status::DidNotResolve; -} - -void RecoverLocalChangesetsHandler::RecoveryResolver::on_list(LstBase&) -{ - m_recovery_applier->handle_error(util::format("Invalid path for %1 (list)", m_instr_name)); -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_list_index(LstBase&, uint32_t) -{ - m_recovery_applier->handle_error(util::format("Invalid path for %1 (list, index)", m_instr_name)); - return Status::DidNotResolve; -} - -void RecoverLocalChangesetsHandler::RecoveryResolver::on_dictionary(Dictionary&) -{ - m_recovery_applier->handle_error(util::format("Invalid path for %1 (dictionary)", m_instr_name)); -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_dictionary_key(Dictionary&, Mixed) -{ - m_recovery_applier->handle_error(util::format("Invalid path for %1 (dictionary, key)", m_instr_name)); - return Status::DidNotResolve; -} - -void RecoverLocalChangesetsHandler::RecoveryResolver::on_set(SetBase&) -{ - m_recovery_applier->handle_error(util::format("Invalid path for %1 (set)", m_instr_name)); -} - -void RecoverLocalChangesetsHandler::RecoveryResolver::on_error(const std::string& err_msg) -{ - m_recovery_applier->handle_error(err_msg); -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_mixed_type_changed(const std::string& err_msg) -{ - std::string full_message = - util::format("Discarding a local %1 made to a collection which no longer exists along path. Error: %2", - m_instr_name, err_msg); - m_recovery_applier->m_logger.warn(full_message.c_str()); - return Status::DidNotResolve; // discard the instruction because the type of a property of collection item changed -} - -void RecoverLocalChangesetsHandler::RecoveryResolver::on_column_advance(ColKey col) -{ - m_list_path.append(ListPath::Element(col)); -} - -void RecoverLocalChangesetsHandler::RecoveryResolver::on_dict_key_advance(StringData string_key) -{ - InternDictKey translated_key = m_recovery_applier->m_intern_keys.get_or_add(std::string_view(string_key)); - m_list_path.append(ListPath::Element(translated_key)); -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_list_index_advance(uint32_t index) -{ - if (m_recovery_applier->m_lists.count(m_list_path) != 0) { - auto& list_tracker = m_recovery_applier->m_lists.at(m_list_path); - auto cross_ndx = list_tracker.update(index); - if (!cross_ndx) { - return Status::DidNotResolve; // not allowed to modify this list item - } - REALM_ASSERT(cross_ndx->remote != uint32_t(-1)); - - update_path_index(cross_ndx->remote); // translate the index of the path - - // At this point, the first part of a path has been allowed. - // This implies that all parts of the rest of the path are also allowed - // so the index translation is not necessary because instructions are - // operating on local only operations. - return Status::Success; - } - // no record of this base list so far, track it for verbatim copy - m_recovery_applier->m_lists.at(m_list_path).queue_for_manual_copy(); - return Status::DidNotResolve; -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_null_link_advance(StringData table_name, StringData link_name) -{ - m_recovery_applier->m_logger.warn( - util::LogCategory::reset, - "Discarding a local %1 made to an embedded object which no longer exists along path '%2.%3'", m_instr_name, - table_name, link_name); - return Status::DidNotResolve; // discard this instruction as it operates over a null link -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_dict_key_not_found(StringData table_name, StringData field_name, - StringData key) -{ - m_recovery_applier->m_logger.warn( - util::LogCategory::reset, - "Discarding a local %1 because the key '%2' does not exist in a dictionary along path '%2.%3'", m_instr_name, - key, table_name, field_name); - return Status::DidNotResolve; // discard this instruction as its path cannot be resolved -} - -RecoverLocalChangesetsHandler::RecoveryResolver::Status -RecoverLocalChangesetsHandler::RecoveryResolver::on_begin(const util::Optional& obj) -{ - if (!obj) { - m_recovery_applier->m_logger.warn(util::LogCategory::reset, - "Cannot recover '%1' which operates on a deleted object", m_instr_name); - return Status::DidNotResolve; - } - m_list_path = ListPath(obj->get_table()->get_key(), obj->get_key()); - return Status::Pending; -} - -void RecoverLocalChangesetsHandler::RecoveryResolver::update_path_index(uint32_t ndx) -{ - REALM_ASSERT(m_it_begin != m_path_instr.path.begin()); - size_t distance = (m_it_begin - m_path_instr.path.begin()) - 1; - REALM_ASSERT_EX(distance < m_path_instr.path.size(), distance, m_path_instr.path.size()); - REALM_ASSERT(mpark::holds_alternative(m_path_instr.path[distance])); - m_mutable_instr.path[distance] = ndx; -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::AddTable& instr) -{ - // Rely on InstructionApplier to validate existing tables - StringData class_name = get_string(instr.table); - try { - auto table = table_for_class_name(class_name); - InstructionApplier::operator()(instr); - - // if the table already existed then no instruction was - // added to the history so we need to add one now - if (m_replication && table) { - if (table->is_embedded()) { - m_replication->add_class(table->get_key(), table->get_name(), table->get_table_type()); - } - else { - ColKey pk_col = table->get_primary_key_column(); - REALM_ASSERT_EX(pk_col, class_name); - m_replication->add_class_with_primary_key(table->get_key(), table->get_name(), - DataType(pk_col.get_type()), table->get_column_name(pk_col), - pk_col.is_nullable(), table->get_table_type()); - } - } - } - catch (const std::runtime_error& err) { - handle_error(util::format( - "While recovering from a client reset, an AddTable instruction for '%1' could not be applied: '%2'", - class_name, err.what())); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::EraseTable& instr) -{ - // Destructive schema changes are not allowed by the resetting client. - StringData class_name = get_string(instr.table); - handle_error(util::format("Types cannot be erased during client reset recovery: '%1'", class_name)); -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::CreateObject& instr) -{ - // This should always succeed, and no path translation is needed because Create operates on top level objects. - InstructionApplier::operator()(instr); -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::EraseObject& instr) -{ - if (auto obj = get_top_object(instr, "EraseObject")) { - // The InstructionApplier uses obj->invalidate() rather than remove(). It should have the same net - // effect, but that is not the case. Notably when erasing an object which has links from a Lst the - // list size does not decrease because there is no hiding the unresolved (null) element. - // To avoid dangling links, just remove the object here rather than using the InstructionApplier. - obj->remove(); - } - // if the object doesn't exist, a local delete is a no-op. -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::Update& instr) -{ - struct UpdateResolver : public RecoveryResolver { - UpdateResolver(RecoverLocalChangesetsHandler* applier, Instruction::Update& instr, - const std::string_view& instr_name) - : RecoveryResolver(applier, instr, instr_name) - , m_instr(instr) - { - } - Status on_dictionary_key(Dictionary& dict, Mixed key) override - { - if (m_instr.value.type == instr::Payload::Type::Erased && dict.find(key) == dict.end()) { - // removing a dictionary value on a key that no longer exists is ignored - return Status::DidNotResolve; - } - return Status::Pending; - } - Status on_list_index(LstBase& list, uint32_t index) override - { - auto cross_index = m_recovery_applier->m_lists.at(m_list_path).update(index); - if (!cross_index) { - return Status::DidNotResolve; - } - m_instr.prior_size = static_cast(list.size()); - m_instr.path.back() = cross_index->remote; - return Status::Pending; - } - Status on_property(Obj&, ColKey) override - { - return Status::Pending; - } - - private: - Instruction::Update& m_instr; - }; - static constexpr std::string_view instr_name("Update"); - Instruction::Update instr_copy = instr; - - if (UpdateResolver(this, instr_copy, instr_name).resolve() == RecoveryResolver::Status::Success) { - if (!check_links_exist(instr_copy.value)) { - if (!allows_null_links(instr_copy, instr_name)) { - m_logger.warn(util::LogCategory::reset, "Discarding an update which links to a deleted object"); - return; - } - instr_copy.value = {}; - } - InstructionApplier::operator()(instr_copy); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::AddInteger& instr) -{ - struct AddIntegerResolver : public RecoveryResolver { - AddIntegerResolver(RecoverLocalChangesetsHandler* applier, Instruction::AddInteger& instr) - : RecoveryResolver(applier, instr, "AddInteger") - { - } - Status on_property(Obj& obj, ColKey key) override - { - // AddInteger only applies to a property - auto old_value = obj.get_any(key); - if (old_value.is_type(type_Int) && !obj.is_null(key)) { - return Status::Pending; - } - return Status::DidNotResolve; - } - }; - Instruction::AddInteger instr_copy = instr; - if (AddIntegerResolver(this, instr_copy).resolve() == RecoveryResolver::Status::Success) { - InstructionApplier::operator()(instr_copy); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::Clear& instr) -{ - struct ClearResolver : public RecoveryResolver { - ClearResolver(RecoverLocalChangesetsHandler* applier, Instruction::Clear& instr) - : RecoveryResolver(applier, instr, "Clear") - { - switch (instr.collection_type) { - case Instruction::CollectionType::Single: - break; - case Instruction::CollectionType::List: - m_collection_type = CollectionType::List; - break; - case Instruction::CollectionType::Dictionary: - m_collection_type = CollectionType::Dictionary; - break; - case Instruction::CollectionType::Set: - m_collection_type = CollectionType::Set; - break; - } - } - void on_list(LstBase&) override - { - m_recovery_applier->m_lists.at(m_list_path).clear(); - } - Status on_list_index(LstBase&, uint32_t index) override - { - Status list_status = on_list_index_advance(index); - return list_status; - // There is no need to clear the potential list at 'index' because that's - // one level deeper than the current list. - } - void on_set(SetBase&) override {} - void on_dictionary(Dictionary&) override {} - Status on_dictionary_key(Dictionary& dict, Mixed key) override - { - on_dict_key_advance(key.get_string()); - // Create the collection if the key does not exist. - if (dict.find(key) == dict.end()) { - dict.insert_collection(key.get_string(), m_collection_type); - } - else if (m_collection_type == CollectionType::List) { - m_recovery_applier->m_lists.at(m_list_path).clear(); - } - return Status::Pending; - } - Status on_property(Obj&, ColKey) override - { - if (m_collection_type == CollectionType::List) { - m_recovery_applier->m_lists.at(m_list_path).clear(); - } - return Status::Pending; - } - - private: - CollectionType m_collection_type; - }; - Instruction::Clear instr_copy = instr; - if (ClearResolver(this, instr_copy).resolve() == RecoveryResolver::Status::Success) { - InstructionApplier::operator()(instr_copy); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::AddColumn& instr) -{ - // Rather than duplicating a bunch of validation, use the existing type checking - // that happens when adding a preexisting column and if there is a problem catch - // the BadChangesetError and stop recovery - try { - const TableRef table = get_table(instr, "AddColumn"); - auto col_name = get_string(instr.field); - ColKey col_key = table->get_column_key(col_name); - - InstructionApplier::operator()(instr); - - // if the column already existed then no instruction was - // added to the history so we need to add one now - if (m_replication && col_key) { - REALM_ASSERT(col_key); - TableRef linked_table = table->get_opposite_table(col_key); - DataType new_type = get_data_type(instr.type); - m_replication->insert_column(table.unchecked_ptr(), col_key, new_type, col_name, - linked_table.unchecked_ptr()); // Throws - } - } - catch (const BadChangesetError& err) { - handle_error( - util::format("While recovering during client reset, an AddColumn instruction could not be applied: '%1'", - err.reason())); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::EraseColumn&) -{ - // Destructive schema changes are not allowed by the resetting client. - handle_error("Properties cannot be erased during client reset recovery"); -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::ArrayInsert& instr) -{ - struct ArrayInsertResolver : public RecoveryResolver { - ArrayInsertResolver(RecoverLocalChangesetsHandler* applier, Instruction::ArrayInsert& instr, - const std::string_view& instr_name) - : RecoveryResolver(applier, instr, instr_name) - , m_instr(instr) - { - } - Status on_list_index(LstBase& list, uint32_t index) override - { - REALM_ASSERT(index != uint32_t(-1)); - size_t list_size = list.size(); - auto cross_index = m_recovery_applier->m_lists.at(m_list_path).insert(index, list_size); - if (cross_index) { - m_instr.path.back() = cross_index->remote; - m_instr.prior_size = static_cast(list_size); - return Status::Pending; - } - return Status::DidNotResolve; - } - - private: - Instruction::ArrayInsert& m_instr; - }; - - static constexpr std::string_view instr_name("ArrayInsert"); - if (!check_links_exist(instr.value)) { - m_logger.warn(util::LogCategory::reset, "Discarding %1 which links to a deleted object", instr_name); - return; - } - Instruction::ArrayInsert instr_copy = instr; - if (ArrayInsertResolver(this, instr_copy, instr_name).resolve() == RecoveryResolver::Status::Success) { - InstructionApplier::operator()(instr_copy); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::ArrayMove& instr) -{ - struct ArrayMoveResolver : public RecoveryResolver { - ArrayMoveResolver(RecoverLocalChangesetsHandler* applier, Instruction::ArrayMove& instr) - : RecoveryResolver(applier, instr, "ArrayMove") - , m_instr(instr) - { - } - Status on_list_index(LstBase& list, uint32_t index) override - { - REALM_ASSERT(index != uint32_t(-1)); - size_t lst_size = list.size(); - uint32_t translated_from, translated_to; - bool allowed_to_move = - m_recovery_applier->m_lists.at(m_list_path) - .move(static_cast(index), m_instr.ndx_2, lst_size, translated_from, translated_to); - if (allowed_to_move) { - m_instr.prior_size = static_cast(lst_size); - m_instr.path.back() = translated_from; - m_instr.ndx_2 = translated_to; - return Status::Pending; - } - return Status::DidNotResolve; - } - - private: - Instruction::ArrayMove& m_instr; - }; - Instruction::ArrayMove instr_copy = instr; - if (ArrayMoveResolver(this, instr_copy).resolve() == RecoveryResolver::Status::Success) { - InstructionApplier::operator()(instr_copy); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::ArrayErase& instr) -{ - struct ArrayEraseResolver : public RecoveryResolver { - ArrayEraseResolver(RecoverLocalChangesetsHandler* applier, Instruction::ArrayErase& instr) - : RecoveryResolver(applier, instr, "ArrayErase") - , m_instr(instr) - { - } - Status on_list_index(LstBase& list, uint32_t index) override - { - uint32_t translated_index; - bool allowed_to_delete = - m_recovery_applier->m_lists.at(m_list_path).remove(static_cast(index), translated_index); - if (allowed_to_delete) { - m_instr.prior_size = static_cast(list.size()); - m_instr.path.back() = translated_index; - return Status::Pending; - } - return Status::DidNotResolve; - } - - private: - Instruction::ArrayErase& m_instr; - }; - Instruction::ArrayErase instr_copy = instr; - if (ArrayEraseResolver(this, instr_copy).resolve() == RecoveryResolver::Status::Success) { - InstructionApplier::operator()(instr_copy); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::SetInsert& instr) -{ - struct SetInsertResolver : public RecoveryResolver { - SetInsertResolver(RecoverLocalChangesetsHandler* applier, Instruction::SetInsert& instr, - const std::string_view& instr_name) - : RecoveryResolver(applier, instr, instr_name) - { - } - void on_set(SetBase&) {} - }; - static constexpr std::string_view instr_name("SetInsert"); - if (!check_links_exist(instr.value)) { - m_logger.warn(util::LogCategory::reset, "Discarding a %1 which links to a deleted object", instr_name); - return; - } - Instruction::SetInsert instr_copy = instr; - if (SetInsertResolver(this, instr_copy, instr_name).resolve() == RecoveryResolver::Status::Success) { - InstructionApplier::operator()(instr_copy); - } -} - -void RecoverLocalChangesetsHandler::operator()(const Instruction::SetErase& instr) -{ - struct SetEraseResolver : public RecoveryResolver { - SetEraseResolver(RecoverLocalChangesetsHandler* applier, Instruction::SetErase& instr) - : RecoveryResolver(applier, instr, "SetErase") - { - } - void on_set(SetBase&) override {} - }; - Instruction::SetErase instr_copy = instr; - if (SetEraseResolver(this, instr_copy).resolve() == RecoveryResolver::Status::Success) { - InstructionApplier::operator()(instr_copy); - } -} - -} // anonymous namespace - -std::vector -client_reset::process_recovered_changesets(Transaction& dest_tr, Transaction& pre_reset_state, util::Logger& logger, - const std::vector& local_changes) -{ - RecoverLocalChangesetsHandler handler(dest_tr, pre_reset_state, logger); - std::vector encoded; - for (auto& local_change : local_changes) { - encoded.push_back({handler.process_changeset(local_change.changeset), local_change.version}); - } - return encoded; -} diff --git a/src/realm/sync/noinst/client_reset_recovery.hpp b/src/realm/sync/noinst/client_reset_recovery.hpp deleted file mode 100644 index 626089af7e9..00000000000 --- a/src/realm/sync/noinst/client_reset_recovery.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_NOINST_CLIENT_RESET_RECOVERY_HPP -#define REALM_NOINST_CLIENT_RESET_RECOVERY_HPP - -#include -#include -#include - -namespace realm::_impl::client_reset { -struct RecoveredChange { - util::AppendBuffer encoded_changeset; - sync::ClientHistory::version_type version; -}; - -std::vector -process_recovered_changesets(Transaction& dest_tr, Transaction& pre_reset_state, util::Logger& logger, - const std::vector& changesets); -} // namespace realm::_impl::client_reset - -#endif // REALM_NOINST_CLIENT_RESET_RECOVERY_HPP diff --git a/src/realm/sync/noinst/integer_codec.hpp b/src/realm/sync/noinst/integer_codec.hpp deleted file mode 100644 index cf378ed3096..00000000000 --- a/src/realm/sync/noinst/integer_codec.hpp +++ /dev/null @@ -1,238 +0,0 @@ -#ifndef REALM_NOINST_INTEGER_CODEC_HPP -#define REALM_NOINST_INTEGER_CODEC_HPP - -#include -#include -#include - -#include -#include -#include - - -namespace realm { -namespace _impl { - -struct Bid128 { - uint64_t w[2]; -}; - -/// The maximum number of bytes that can be consumed by encode_int() for an -/// integer of the same type, \a T. -template -constexpr std::size_t encode_int_max_bytes(); - -/// The size of the specified buffer must be at least what is returned by -/// encode_int_max_bytes(). -/// -/// Returns the number of bytes used to hold the encoded value. -template -std::size_t encode_int(T value, char* buffer) noexcept; - -/// If decoding succeeds, the decoded value is assigned to \a value and the -/// number of consumed bytes is returned (which will always be at least -/// one). Otherwise \a value is left unmodified, and zero is returned. -template -std::size_t decode_int(const char* buffer, std::size_t size, T& value) noexcept; - -/// \tparam I Must have member function `bool read_char(char&)`. -/// -/// If decoding succeeds, the decoded value is assigned to \a value and `true` -/// is returned. Otherwise \a value is left unmodified, and `false` is returned. -template -bool decode_int(I& input, T& value) noexcept(noexcept(std::declval().read_char(std::declval()))); - - -// Implementation - -// Work-around for insufficient constexpr support from GCC 4.9. -template -struct EncodeIntMaxBytesHelper { - // One sign bit plus number of value bits - static const int num_bits = 1 + std::numeric_limits::digits; - // Only the first 7 bits are available per byte. Had it not been - // for the fact that maximum guaranteed bit width of a char is 8, - // this value could have been increased to 15 (one less than the - // number of value bits in 'unsigned'). - static const int bits_per_byte = 7; - static const int max_bytes = (num_bits + (bits_per_byte - 1)) / bits_per_byte; -}; - -template -constexpr std::size_t encode_int_max_bytes() -{ - return std::size_t(EncodeIntMaxBytesHelper::max_bytes); -} - -template -std::size_t encode_int(char* buffer, T value) -{ - REALM_DIAG_PUSH(); - REALM_DIAG_IGNORE_UNSIGNED_MINUS(); - static_assert(std::numeric_limits::is_integer, "Integer required"); - T value_2 = value; - bool negative = false; - if constexpr (std::is_signed_v) { - if (value < 0) { - // The following conversion is guaranteed by C++11 to never overflow - // (contrast this with "-value_2" which indeed could overflow). See - // C99+TC3 section 6.2.6.2 paragraph 2. - value_2 = -(value_2 + 1); - negative = true; - } - } - // At this point 'value_2' is always a positive number or 0. Also, small negative - // numbers have been converted to small positive numbers. - REALM_ASSERT(value_2 >= 0); - int bits_per_byte = 7; // See encode_int_max_bytes() - constexpr int max_bytes = int(encode_int_max_bytes()); - using uchar = unsigned char; - char* ptr = buffer; - // An explicit constant maximum number of iterations is specified - // in the hope that it will help the optimizer (to do loop - // unrolling, for example). - for (int i = 0; i < max_bytes; ++i) { - if (value_2 >> (bits_per_byte - 1) == 0) - break; - *reinterpret_cast(ptr) = - uchar((1U << bits_per_byte) | unsigned(value_2 & ((1U << bits_per_byte) - 1))); - ++ptr; - value_2 >>= bits_per_byte; - } - *reinterpret_cast(ptr) = uchar(negative ? (1U << (bits_per_byte - 1)) | unsigned(value_2) : value_2); - ++ptr; - return std::size_t(ptr - buffer); - REALM_DIAG_POP(); -} - -template <> -inline std::size_t encode_int(char* buffer, Bid128 value) -{ - auto value_0 = value.w[0]; - auto value_1 = value.w[1]; - constexpr int bits_per_byte = 7; - using uchar = unsigned char; - char* ptr = buffer; - while (value_0 >> (bits_per_byte - 1) || value_1 != 0) { - unsigned c = unsigned(value_0 & ((1U << bits_per_byte) - 1)); - *reinterpret_cast(ptr) = uchar((1U << bits_per_byte) | c); - ++ptr; - - value_0 >>= bits_per_byte; - if (value_1) { - uint64_t tmp = value_1 & ((1U << bits_per_byte) - 1); - value_1 >>= bits_per_byte; - value_0 |= (tmp << (64 - bits_per_byte)); - } - } - *reinterpret_cast(ptr) = uchar(value_0); - ++ptr; - return std::size_t(ptr - buffer); -} - - -template -bool decode_int(I& input, T& value) noexcept(noexcept(std::declval().read_char(std::declval()))) -{ - REALM_DIAG_PUSH(); - REALM_DIAG_IGNORE_UNSIGNED_MINUS(); - T value_2 = 0; - int part = 0; - constexpr int max_bytes = int(encode_int_max_bytes()); - for (int i = 0; i != max_bytes; ++i) { - char c; - if (!input.read_char(c)) - return false; // Failure: Premature end of input - using uchar = unsigned char; - part = uchar(c); - if (0xFF < part) - return false; // Failure: Only the first 8 bits may be used in each byte - if ((part & 0x80) == 0) { - T p = part & 0x3F; - if (util::int_shift_left_with_overflow_detect(p, i * 7)) - return false; // Failure: Encoded value too large for `T` - value_2 |= p; - break; - } - if (i == max_bytes - 1) - return false; // Failure: Too many bytes - value_2 |= T(part & 0x7F) << (i * 7); - } - if (part & 0x40) { - // The real value is negative. Because 'value_2' is positive at this - // point, the following negation is guaranteed by C++11 to never - // overflow. See C99+TC3 section 6.2.6.2 paragraph 2. - value_2 = -value_2; - if (util::int_subtract_with_overflow_detect(value_2, 1)) - return false; // Failure: Encoded value too large for `T` - } - value = value_2; - return true; // Success - REALM_DIAG_POP(); -} - -template -bool decode_int(I& input, _impl::Bid128& value) noexcept -{ - uint64_t value_0 = 0; - uint64_t value_1 = 0; - constexpr int max_bytes = 17; // 113 bits / 7 - - int i = 0; - for (;;) { - char c; - if (!input.read_char(c)) - return false; // Failure: Premature end of input - uint64_t part = c & 0x7F; - if (i < 9) { - value_0 |= part << (7 * i); - } - else if (i == 9) { - value_0 |= part << 63; - value_1 |= part >> 1; - } - else if (i < max_bytes) { - value_1 |= part << ((7 * i) - 64); - } - else { - return false; // Failure: Too many bytes - } - if ((c & 0x80) == 0) { - break; - } - i++; - } - - value.w[0] = value_0; - value.w[1] = value_1; - - return true; // Success -} - -template -std::size_t decode_int(const char* buffer, std::size_t size, T& value) noexcept -{ - struct Input { - const char* ptr; - const char* end; - bool read_char(char& c) noexcept - { - if (REALM_LIKELY(ptr != end)) { - c = *ptr++; - return true; - } - return false; - } - }; - Input input{buffer, buffer + size}; - if (REALM_LIKELY(decode_int(input, value))) { - REALM_ASSERT(input.ptr > buffer); - return std::size_t(input.ptr - buffer); // Success - } - return 0; // Failure -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_NOINST_INTEGER_CODEC_HPP diff --git a/src/realm/sync/noinst/migration_store.cpp b/src/realm/sync/noinst/migration_store.cpp deleted file mode 100644 index 6f8e3d10d99..00000000000 --- a/src/realm/sync/noinst/migration_store.cpp +++ /dev/null @@ -1,371 +0,0 @@ -#include - -#include -#include - -namespace realm::sync { -namespace { -constexpr static int c_schema_version = 1; -constexpr static std::string_view c_flx_migration_table("flx_migration"); -constexpr static std::string_view c_flx_migration_started_at("started_at"); -constexpr static std::string_view c_flx_migration_completed_at("completed_at"); -constexpr static std::string_view c_flx_migration_state("state"); -constexpr static std::string_view c_flx_migration_query_string("query_string"); -constexpr static std::string_view c_flx_migration_original_partition("original_partition"); -constexpr static std::string_view - c_flx_migration_sentinel_subscription_set_version("sentinel_subscription_set_version"); -constexpr static std::string_view c_flx_subscription_name_prefix("flx_migrated_"); - -} // namespace - -MigrationStoreRef MigrationStore::create(DBRef db) -{ - return std::make_shared(Private(), std::move(db)); -} - -MigrationStore::MigrationStore(Private, DBRef db) - : m_db(std::move(db)) - , m_state(MigrationState::NotMigrated) -{ - load_data(true); // read_only, default to NotMigrated if table is not initialized -} - -bool MigrationStore::load_data(bool read_only) -{ - std::unique_lock lock{m_mutex}; - - if (m_migration_table) { - return true; // already initialized - } - - std::vector internal_tables{ - {&m_migration_table, - c_flx_migration_table, - { - {&m_migration_started_at, c_flx_migration_started_at, type_Timestamp}, - {&m_migration_completed_at, c_flx_migration_completed_at, type_Timestamp, true}, - {&m_migration_state, c_flx_migration_state, type_Int}, - {&m_migration_query_str, c_flx_migration_query_string, type_String}, - {&m_migration_partition, c_flx_migration_original_partition, type_String}, - {&m_sentinel_query_version, c_flx_migration_sentinel_subscription_set_version, type_Int, true}, - }}, - }; - - auto tr = m_db->start_read(); - // Start with a reader so it doesn't try to write until we are ready - SyncMetadataSchemaVersionsReader schema_versions_reader(tr); - if (auto schema_version = - schema_versions_reader.get_version_for(tr, internal_schema_groups::c_flx_migration_store)) { - if (*schema_version != c_schema_version) { - throw RuntimeError(ErrorCodes::UnsupportedFileFormatVersion, - "Invalid schema version for flexible sync migration store metadata"); - } - load_sync_metadata_schema(*tr, &internal_tables); - } - else { - if (read_only) { - // Writing is disabled - return false; // Either table is not initialized or version does not exist - } - tr->promote_to_write(); - // Ensure the schema versions table is initialized (may add its own commit) - SyncMetadataSchemaVersions schema_versions(tr); - // Create the metadata schema and set the version (in the same commit) - schema_versions.set_version_for(tr, internal_schema_groups::c_flx_migration_store, c_schema_version); - create_sync_metadata_schema(*tr, &internal_tables); - tr->commit_and_continue_as_read(); - } - REALM_ASSERT(m_migration_table); - - // Read the migration object if exists, or default to not migrated - if (auto migration_table = tr->get_table(m_migration_table); !migration_table->is_empty()) { - auto migration_store_obj = migration_table->get_object(0); - m_state = static_cast(migration_store_obj.get(m_migration_state)); - m_query_string = migration_store_obj.get(m_migration_query_str); - m_migrated_partition = migration_store_obj.get(m_migration_partition); - m_sentinel_subscription_set_version = - migration_store_obj.get>(m_sentinel_query_version); - } - else { - m_state = MigrationState::NotMigrated; - m_query_string.reset(); - m_migrated_partition.reset(); - m_sentinel_subscription_set_version.reset(); - } - return true; -} - -bool MigrationStore::is_migration_in_progress() -{ - std::lock_guard lock{m_mutex}; - return m_state == MigrationState::InProgress; -} - -bool MigrationStore::is_migrated() -{ - std::lock_guard lock{m_mutex}; - return m_state == MigrationState::Migrated; -} - -bool MigrationStore::is_rollback_in_progress() -{ - std::lock_guard lock{m_mutex}; - return m_state == MigrationState::RollbackInProgress; -} - -void MigrationStore::complete_migration_or_rollback() -{ - // Ensure the migration table has been initialized - bool loaded = load_data(); - REALM_ASSERT(loaded); - - std::unique_lock lock{m_mutex}; - if (m_state != MigrationState::InProgress && m_state != MigrationState::RollbackInProgress) { - return; - } - - // Complete rollback. - if (m_state == MigrationState::RollbackInProgress) { - clear(std::move(lock)); // releases the lock - return; - } - - // Complete migration. - m_state = MigrationState::Migrated; - - auto tr = m_db->start_write(); - auto migration_table = tr->get_table(m_migration_table); - REALM_ASSERT(!migration_table->is_empty()); - auto migration_store_obj = migration_table->get_object(0); - migration_store_obj.set(m_migration_state, int64_t(m_state)); - migration_store_obj.set(m_migration_completed_at, Timestamp{std::chrono::system_clock::now()}); - tr->commit(); -} - -std::optional MigrationStore::get_migrated_partition() -{ - std::lock_guard lock{m_mutex}; - // This will be valid if migration in progress or complete - return m_migrated_partition; -} - -std::optional MigrationStore::get_query_string() -{ - std::lock_guard lock{m_mutex}; - // This will be valid if migration in progress or complete - return m_query_string; -} - -std::shared_ptr MigrationStore::convert_sync_config(std::shared_ptr config) -{ - REALM_ASSERT(config); - // If load data failed in the constructor, m_state defaults to NotMigrated - - std::unique_lock lock{m_mutex}; - if (config->flx_sync_requested || m_state == MigrationState::NotMigrated || - m_state == MigrationState::RollbackInProgress) { - return config; - } - - // Once in the migrated state, the partition value cannot change for the same realm file - if (m_state == MigrationState::Migrated && m_migrated_partition && - m_migrated_partition != config->partition_value) { - throw LogicError( - ErrorCodes::IllegalOperation, - util::format("Partition value cannot be changed for migrated realms\n - original: %1\n - config: %2", - m_migrated_partition, config->partition_value)); - } - - return convert_sync_config_to_flx(std::move(config)); -} - -std::shared_ptr -MigrationStore::convert_sync_config_to_flx(std::shared_ptr config) -{ - if (config->flx_sync_requested) { - return config; - } - - auto flx_config = std::make_shared(*config); // deep copy - flx_config->partition_value = ""; - flx_config->flx_sync_requested = true; - - return flx_config; -} - -void MigrationStore::migrate_to_flx(std::string_view rql_query_string, std::string_view partition_value) -{ - REALM_ASSERT(!rql_query_string.empty()); - - // Ensure the migration table has been initialized - bool loaded = load_data(); - REALM_ASSERT(loaded); - - std::unique_lock lock{m_mutex}; - // Can call migrate_to_flx multiple times if migration has not completed. - REALM_ASSERT(m_state != MigrationState::Migrated); - m_state = MigrationState::InProgress; - m_query_string.emplace(rql_query_string); - m_migrated_partition.emplace(partition_value); - - auto tr = m_db->start_read(); - auto migration_table = tr->get_table(m_migration_table); - // A migration object may exist if the migration was started in a previous session. - if (migration_table->is_empty()) { - tr->promote_to_write(); - auto migration_store_obj = migration_table->create_object(); - migration_store_obj.set(m_migration_query_str, *m_query_string); - migration_store_obj.set(m_migration_state, int64_t(m_state)); - migration_store_obj.set(m_migration_partition, *m_migrated_partition); - migration_store_obj.set(m_migration_started_at, Timestamp{std::chrono::system_clock::now()}); - tr->commit(); - } - else { - auto migration_store_obj = migration_table->get_object(0); - auto state = static_cast(migration_store_obj.get(m_migration_state)); - auto query_string = migration_store_obj.get(m_migration_query_str); - auto migrated_partition = migration_store_obj.get(m_migration_partition); - REALM_ASSERT(m_state == state); - REALM_ASSERT(m_query_string == query_string); - REALM_ASSERT(m_migrated_partition == migrated_partition); - } -} - -void MigrationStore::rollback_to_pbs() -{ - // Ensure the migration table has been initialized - bool loaded = load_data(); - REALM_ASSERT(loaded); - - std::unique_lock lock{m_mutex}; - // Can call rollback_to_pbs multiple times if rollback has not completed. - REALM_ASSERT(m_state != MigrationState::NotMigrated); - m_state = MigrationState::RollbackInProgress; - - auto tr = m_db->start_write(); - auto migration_table = tr->get_table(m_migration_table); - REALM_ASSERT(!migration_table->is_empty()); - auto migration_store_obj = migration_table->get_object(0); - migration_store_obj.set(m_migration_state, int64_t(m_state)); - tr->commit(); -} - -void MigrationStore::cancel_migration() -{ - // Ensure the migration table has been initialized - bool loaded = load_data(); - REALM_ASSERT(loaded); - - // Clear the migration state - std::unique_lock lock{m_mutex}; - REALM_ASSERT(m_state == MigrationState::Migrated); - clear(std::move(lock)); // releases the lock -} - -void MigrationStore::clear(std::unique_lock) -{ - // Make sure the migration table has been initialized before calling clear() - REALM_ASSERT(m_migration_table); - - auto tr = m_db->start_read(); - auto migration_table = tr->get_table(m_migration_table); - if (migration_table->is_empty()) { - return; // already cleared - } - - m_state = MigrationState::NotMigrated; - m_query_string.reset(); - m_migrated_partition.reset(); - m_sentinel_subscription_set_version.reset(); - tr->promote_to_write(); - migration_table->clear(); - tr->commit(); -} - -Subscription MigrationStore::make_subscription(const std::string& object_class_name, - const std::string& rql_query_string) -{ - REALM_ASSERT(!object_class_name.empty()); - - std::string subscription_name = c_flx_subscription_name_prefix.data() + object_class_name; - return Subscription{subscription_name, object_class_name, rql_query_string}; -} - -void MigrationStore::create_subscriptions(SubscriptionStore& subs_store) -{ - std::unique_lock lock{m_mutex}; - if (m_state != MigrationState::Migrated) { - return; - } - - REALM_ASSERT(m_query_string); - create_subscriptions(subs_store, *m_query_string); -} - -void MigrationStore::create_subscriptions(SubscriptionStore& subs_store, const std::string& rql_query_string) -{ - if (rql_query_string.empty()) { - return; - } - - auto mut_sub = subs_store.get_latest().make_mutable_copy(); - auto sub_count = mut_sub.size(); - - auto tr = m_db->start_read(); - // List of tables covered by the latest subscription set. - auto tables = subs_store.get_tables_for_latest(*tr); - - // List of tables in the realm. - auto table_keys = tr->get_table_keys(); - for (auto key : table_keys) { - if (!tr->table_is_public(key)) { - continue; - } - auto table = tr->get_table(key); - if (table->get_table_type() != Table::Type::TopLevel) { - continue; - } - auto object_class_name = table->get_class_name(); - if (tables.find(object_class_name) == tables.end()) { - auto sub = make_subscription(object_class_name, rql_query_string); - mut_sub.insert_sub(sub); - } - } - - // No new subscription was added. - if (mut_sub.size() == sub_count) { - return; - } - - // Commit new subscription set. - mut_sub.commit(); -} - -void MigrationStore::create_sentinel_subscription_set(SubscriptionStore& subs_store) -{ - std::lock_guard lock{m_mutex}; - if (m_state != MigrationState::Migrated) { - return; - } - if (m_sentinel_subscription_set_version) { - return; - } - auto mut_sub = subs_store.get_latest().make_mutable_copy(); - auto subscription_set_version = mut_sub.commit().version(); - m_sentinel_subscription_set_version.emplace(subscription_set_version); - - auto tr = m_db->start_write(); - auto migration_table = tr->get_table(m_migration_table); - REALM_ASSERT(!migration_table->is_empty()); - auto migration_store_obj = migration_table->get_object(0); - migration_store_obj.set(m_sentinel_query_version, *m_sentinel_subscription_set_version); - tr->commit(); -} - -std::optional MigrationStore::get_sentinel_subscription_set_version() -{ - std::lock_guard lock{m_mutex}; - return m_sentinel_subscription_set_version; -} - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/migration_store.hpp b/src/realm/sync/noinst/migration_store.hpp deleted file mode 100644 index 79db9d279c4..00000000000 --- a/src/realm/sync/noinst/migration_store.hpp +++ /dev/null @@ -1,130 +0,0 @@ -/************************************************************************* - * - * Copyright 2023 Realm, Inc. - * - * 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 - -namespace realm::sync { - -class MigrationStore; -using MigrationStoreRef = std::shared_ptr; - -// A MigrationStore manages the PBS -> FLX migration metadata table. -class MigrationStore : public std::enable_shared_from_this { - struct Private {}; - -public: - explicit MigrationStore(Private, DBRef db); - MigrationStore(const MigrationStore&) = delete; - MigrationStore& operator=(const MigrationStore&) = delete; - - enum class MigrationState { - NotMigrated, - InProgress, - Migrated, - RollbackInProgress, - }; - - static MigrationStoreRef create(DBRef db); - - // Converts the configuration from PBS to FLX if a migration or rollback is in progress or completed, otherwise - // returns the passed in config object. - std::shared_ptr convert_sync_config(std::shared_ptr config); - // Convert a configuration from PBS to FLX. No-op if already an FLX configuration. - static std::shared_ptr convert_sync_config_to_flx(std::shared_ptr config); - - // Called when the server responds with migrate to FLX and stores the FLX subscription RQL query string. - void migrate_to_flx(std::string_view rql_query_string, std::string_view partition_value); - - // Called when the server responds with rollback to PBS. - void rollback_to_pbs(); - - // Clear the migrated state - void cancel_migration(); - - // Is a client migration to FLX in progress? - bool is_migration_in_progress(); - // Has the client migration to FLX completed? - bool is_migrated(); - // Is a client rollback to PBS in progress? - bool is_rollback_in_progress(); - - // Mark the migration or rollback complete and update the state. No-op if not in 'InProgress' or - // 'RollbackInProgress' state. - void complete_migration_or_rollback(); - - std::optional get_migrated_partition(); - std::optional get_query_string(); - - // Create subscriptions for each table that does not have a subscription. - // If new subscriptions are created, they are committed and a change of query is sent to the server. - void create_subscriptions(SubscriptionStore& subs_store); - void create_subscriptions(SubscriptionStore& subs_store, const std::string& rql_query_string); - - // Create a subscription set used as sentinel. No-op if not in 'Migrated' state. - // This method is idempotent (i.e, at most one subscription set can be created during the lifetime of a - // migration) - void create_sentinel_subscription_set(SubscriptionStore& subs_store); - std::optional get_sentinel_subscription_set_version(); - -protected: - // Read the data from the database - returns true if successful - // Will return false if read_only is set and the metadata schema - // versions info is not already set. - bool load_data(bool read_only = false); // requires !m_mutex - - // Clear the migration store info - void clear(std::unique_lock lock); - -private: - // Generate a new subscription that can be added to the subscription store using - // the query string returned from the server and a name that begins with "flx_migrated_" - // followed by the class name. - Subscription make_subscription(const std::string& object_class_name, const std::string& rql_query_string); - - DBRef m_db; - - TableKey m_migration_table; - ColKey m_migration_started_at; - ColKey m_migration_completed_at; - ColKey m_migration_state; - ColKey m_migration_query_str; - ColKey m_migration_partition; - ColKey m_sentinel_query_version; - - std::mutex m_mutex; - // Current migration state - MigrationState m_state; - // RQL query string received from the server - std::optional m_query_string; - // The original PBS partition string before the migration - std::optional m_migrated_partition; - // The version of the subscription set used as a sentinel so we know when to stop uploading unsynced changes - // before updating to native FLX. - std::optional m_sentinel_subscription_set_version; -}; - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/pending_bootstrap_store.cpp b/src/realm/sync/noinst/pending_bootstrap_store.cpp deleted file mode 100644 index 187de9b58fc..00000000000 --- a/src/realm/sync/noinst/pending_bootstrap_store.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/************************************************************************* - * - * Copyright 2022 Realm, Inc. - * - * 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 "realm/sync/noinst/pending_bootstrap_store.hpp" - -#include "realm/binary_data.hpp" -#include "realm/chunked_binary.hpp" -#include "realm/data_type.hpp" -#include "realm/db.hpp" -#include "realm/list.hpp" -#include "realm/query.hpp" -#include "realm/sync/changeset_parser.hpp" -#include "realm/sync/noinst/client_history_impl.hpp" -#include "realm/sync/noinst/protocol_codec.hpp" -#include "realm/sync/noinst/sync_metadata_schema.hpp" -#include "realm/sync/protocol.hpp" -#include "realm/sync/subscriptions.hpp" -#include "realm/sync/transform.hpp" -#include "realm/util/assert.hpp" -#include "realm/util/buffer.hpp" -#include "realm/util/compression.hpp" -#include "realm/util/logger.hpp" -#include - -namespace realm::sync { -namespace { -constexpr static int c_schema_version = 1; -constexpr static std::string_view c_progress_table("flx_pending_bootstrap_progress"); -constexpr static std::string_view c_pending_bootstrap_table("flx_pending_bootstrap"); -constexpr static std::string_view c_pending_changesets_table("flx_pending_bootstrap_changesets"); -constexpr static std::string_view c_pending_bootstrap_query_version("query_version"); -constexpr static std::string_view c_pending_bootstrap_changesets("changesets"); -constexpr static std::string_view c_pending_bootstrap_progress("progress"); -constexpr static std::string_view c_pending_changesets_remote_version("remote_version"); -constexpr static std::string_view - c_pending_changesets_last_integrated_client_version("last_integrated_client_version"); -constexpr static std::string_view c_pending_changesets_origin_file_ident("origin_file_ident"); -constexpr static std::string_view c_pending_changesets_origin_timestamp("origin_timestamp"); -constexpr static std::string_view c_pending_changesets_original_size("original_size"); -constexpr static std::string_view c_pending_changesets_data("data"); -constexpr static std::string_view c_progress_download_server_version("download_server_version"); -constexpr static std::string_view c_progress_download_client_version("download_client_version"); -constexpr static std::string_view c_progress_upload_server_version("upload_server_version"); -constexpr static std::string_view c_progress_upload_client_version("upload_client_version"); -constexpr static std::string_view c_progress_latest_server_version("latest_server_version"); -constexpr static std::string_view c_progress_latest_server_version_salt("latest_server_version_salt"); - -} // namespace - -PendingBootstrapStore::PendingBootstrapStore(DBRef db, util::Logger& logger, - std::shared_ptr subscription_store) - : m_db(std::move(db)) - , m_logger(logger) - , m_subscription_store(std::move(subscription_store)) -{ - std::vector internal_tables{ - {&m_table, - c_pending_bootstrap_table, - {&m_query_version, c_pending_bootstrap_query_version, type_Int}, - { - {&m_changesets, c_pending_bootstrap_changesets, c_pending_changesets_table, true}, - {&m_progress, c_pending_bootstrap_progress, c_progress_table, false}, - }}, - {&m_progress_table, - c_progress_table, - SyncMetadataTable::IsEmbeddedTag{}, - { - {&m_progress_upload_server_version, c_progress_upload_server_version, type_Int}, - {&m_progress_upload_client_version, c_progress_upload_client_version, type_Int}, - {&m_progress_download_server_version, c_progress_download_server_version, type_Int}, - {&m_progress_download_client_version, c_progress_download_client_version, type_Int}, - {&m_progress_latest_server_version, c_progress_latest_server_version, type_Int}, - {&m_progress_latest_server_version_salt, c_progress_latest_server_version_salt, type_Int}, - }}, - {&m_changeset_table, - c_pending_changesets_table, - SyncMetadataTable::IsEmbeddedTag{}, - { - {&m_changeset_remote_version, c_pending_changesets_remote_version, type_Int}, - {&m_changeset_last_integrated_client_version, c_pending_changesets_last_integrated_client_version, - type_Int}, - {&m_changeset_origin_file_ident, c_pending_changesets_origin_file_ident, type_Int}, - {&m_changeset_origin_timestamp, c_pending_changesets_origin_timestamp, type_Int}, - {&m_changeset_original_changeset_size, c_pending_changesets_original_size, type_Int}, - {&m_changeset_data, c_pending_changesets_data, type_Binary, true}, - }}}; - - auto tr = m_db->start_read(); - // Start with a reader so it doesn't try to write until we are ready - SyncMetadataSchemaVersionsReader schema_versions_reader(tr); - if (auto schema_version = - schema_versions_reader.get_version_for(tr, internal_schema_groups::c_pending_bootstraps)) { - if (*schema_version != c_schema_version) { - throw RuntimeError(ErrorCodes::SchemaVersionMismatch, - "Invalid schema version for FLX sync pending bootstrap table group"); - } - load_sync_metadata_schema(*tr, &internal_tables); - } - else { - tr->promote_to_write(); - // Ensure the schema versions table is initialized (may add its own commit) - SyncMetadataSchemaVersions schema_versions(tr); - // Create the metadata schema and set the version (in the same commit) - schema_versions.set_version_for(tr, internal_schema_groups::c_pending_bootstraps, c_schema_version); - create_sync_metadata_schema(*tr, &internal_tables); - tr->commit_and_continue_as_read(); - } - REALM_ASSERT(m_table); - - if (auto bootstrap_table = tr->get_table(m_table); !bootstrap_table->is_empty()) { - m_has_pending = true; - } - else { - m_has_pending = false; - } -} - -void PendingBootstrapStore::add_batch(int64_t query_version, util::Optional progress, - DownloadableProgress download_progress, - const _impl::ClientProtocol::ReceivedChangesets& changesets) -{ - std::vector> compressed_changesets; - compressed_changesets.reserve(changesets.size()); - - util::compression::CompressMemoryArena arena; - for (auto& changeset : changesets) { - compressed_changesets.emplace_back(); - util::compression::allocate_and_compress_nonportable(arena, {changeset.data.get_first_chunk()}, - compressed_changesets.back()); - } - - auto tr = m_db->start_write(); - auto bootstrap_table = tr->get_table(m_table); - auto incomplete_bootstraps = Query(bootstrap_table).not_equal(m_query_version, query_version).find_all(); - incomplete_bootstraps.for_each([&](Obj obj) { - m_logger.debug(util::LogCategory::changeset, "Clearing incomplete bootstrap for query version %1", - obj.get(m_query_version)); - return IteratorControl::AdvanceToNext; - }); - incomplete_bootstraps.clear(); - - bool did_create = false; - auto bootstrap_obj = bootstrap_table->create_object_with_primary_key(Mixed{query_version}, &did_create); - if (progress) { - auto progress_obj = bootstrap_obj.create_and_set_linked_object(m_progress); - progress_obj.set(m_progress_latest_server_version, int64_t(progress->latest_server_version.version)); - progress_obj.set(m_progress_latest_server_version_salt, int64_t(progress->latest_server_version.salt)); - progress_obj.set(m_progress_download_server_version, int64_t(progress->download.server_version)); - progress_obj.set(m_progress_download_client_version, - int64_t(progress->download.last_integrated_client_version)); - progress_obj.set(m_progress_upload_server_version, int64_t(progress->upload.last_integrated_server_version)); - progress_obj.set(m_progress_upload_client_version, int64_t(progress->upload.client_version)); - } - - auto changesets_list = bootstrap_obj.get_linklist(m_changesets); - for (size_t idx = 0; idx < changesets.size(); ++idx) { - auto cur_changeset = changesets_list.create_and_insert_linked_object(changesets_list.size()); - cur_changeset.set(m_changeset_remote_version, int64_t(changesets[idx].remote_version)); - cur_changeset.set(m_changeset_last_integrated_client_version, - int64_t(changesets[idx].last_integrated_local_version)); - cur_changeset.set(m_changeset_origin_file_ident, int64_t(changesets[idx].origin_file_ident)); - cur_changeset.set(m_changeset_origin_timestamp, int64_t(changesets[idx].origin_timestamp)); - cur_changeset.set(m_changeset_original_changeset_size, int64_t(changesets[idx].original_changeset_size)); - BinaryData compressed_data(compressed_changesets[idx].data(), compressed_changesets[idx].size()); - cur_changeset.set(m_changeset_data, compressed_data); - } - size_t total_changesets = changesets_list.size(); - - ClientHistory::set_download_progress(*tr, download_progress); - - if (did_create) { - m_subscription_store->begin_bootstrap(*tr, query_version); - } - - tr->commit(); - - if (did_create) { - m_logger.debug(util::LogCategory::changeset, - "Created new pending bootstrap object with %1 changesets for query version %2", - total_changesets, query_version); - } - else { - m_logger.debug(util::LogCategory::changeset, - "Added batch of %1 changesets (%2 total) to pending bootstrap object for query version %3", - changesets.size(), total_changesets, query_version); - } - if (progress) { - m_logger.debug(util::LogCategory::changeset, - "Finalized pending bootstrap object with %1 changesets for query version %2", total_changesets, - query_version); - } - - m_has_pending = true; -} - -bool PendingBootstrapStore::has_pending() const noexcept -{ - return m_has_pending; -} - -void PendingBootstrapStore::clear(Transaction& wt, int64_t query_version) -{ - auto bootstrap_table = wt.get_table(m_table); - bootstrap_table->clear(); - if (m_has_pending) { - m_subscription_store->cancel_bootstrap(wt, query_version); - } - m_has_pending = false; -} - -PendingBootstrapStore::PendingBatch PendingBootstrapStore::peek_pending(Transaction& tr, size_t limit_in_bytes) -{ - auto bootstrap_table = tr.get_table(m_table); - - // We should only have one pending bootstrap at a time. - REALM_ASSERT(bootstrap_table->size() == 1); - - auto bootstrap_obj = bootstrap_table->get_object(0); - PendingBatch ret; - ret.query_version = bootstrap_obj.get(m_query_version); - - if (!bootstrap_obj.is_null(m_progress)) { - auto progress_obj = bootstrap_obj.get_linked_object(m_progress); - SyncProgress progress; - progress.latest_server_version.version = progress_obj.get(m_progress_latest_server_version); - progress.latest_server_version.salt = progress_obj.get(m_progress_latest_server_version_salt); - progress.download.server_version = progress_obj.get(m_progress_download_server_version); - progress.download.last_integrated_client_version = - progress_obj.get(m_progress_download_client_version); - progress.upload.last_integrated_server_version = progress_obj.get(m_progress_upload_server_version); - progress.upload.client_version = progress_obj.get(m_progress_upload_client_version); - ret.progress = progress; - } - - auto changeset_list = bootstrap_obj.get_linklist(m_changesets); - size_t bytes_so_far = 0; - for (size_t idx = 0; idx < changeset_list.size() && bytes_so_far < limit_in_bytes; ++idx) { - auto cur_changeset = changeset_list.get_object(idx); - ret.changeset_data.push_back(util::AppendBuffer()); - auto& uncompressed_buffer = ret.changeset_data.back(); - - auto compressed_changeset_data = cur_changeset.get(m_changeset_data); - ChunkedBinaryInputStream changeset_is(compressed_changeset_data); - auto ec = util::compression::decompress_nonportable(changeset_is, uncompressed_buffer); - if (ec == util::compression::error::decompress_unsupported) { - REALM_TERMINATE( - "Synchronized Realm files with unprocessed pending bootstraps cannot be copied between platforms."); - } - REALM_ASSERT_3(ec, ==, std::error_code{}); - - RemoteChangeset parsed_changeset; - parsed_changeset.original_changeset_size = - static_cast(cur_changeset.get(m_changeset_original_changeset_size)); - parsed_changeset.origin_timestamp = cur_changeset.get(m_changeset_origin_timestamp); - parsed_changeset.origin_file_ident = cur_changeset.get(m_changeset_origin_file_ident); - parsed_changeset.remote_version = cur_changeset.get(m_changeset_remote_version); - parsed_changeset.last_integrated_local_version = - cur_changeset.get(m_changeset_last_integrated_client_version); - parsed_changeset.data = BinaryData(uncompressed_buffer.data(), uncompressed_buffer.size()); - bytes_so_far += parsed_changeset.data.size(); - ret.changesets.push_back(std::move(parsed_changeset)); - } - ret.remaining_changesets = changeset_list.size() - ret.changesets.size(); - - return ret; -} - -PendingBootstrapStore::PendingBatchStats PendingBootstrapStore::pending_stats() -{ - auto tr = m_db->start_read(); - auto bootstrap_table = tr->get_table(m_table); - if (bootstrap_table->is_empty()) { - return {}; - } - - REALM_ASSERT(bootstrap_table->size() == 1); - - auto bootstrap_obj = bootstrap_table->get_object(0); - auto changeset_list = bootstrap_obj.get_linklist(m_changesets); - - PendingBatchStats stats; - stats.query_version = bootstrap_obj.get(m_query_version); - stats.pending_changesets = changeset_list.size(); - changeset_list.for_each([&](Obj& cur_changeset) { - stats.pending_changeset_bytes += - static_cast(cur_changeset.get(m_changeset_original_changeset_size)); - return IteratorControl::AdvanceToNext; - }); - - return stats; -} - -void PendingBootstrapStore::pop_front_pending(const Transaction& tr, size_t count) -{ - REALM_ASSERT_3(tr.get_transact_stage(), ==, DB::transact_Writing); - auto bootstrap_table = tr.get_table(m_table); - // We should only have one pending bootstrap at a time. - REALM_ASSERT(bootstrap_table->size() == 1); - - auto bootstrap_obj = bootstrap_table->get_object(0); - auto changeset_list = bootstrap_obj.get_linklist(m_changesets); - REALM_ASSERT_3(changeset_list.size(), >=, count); - if (count == changeset_list.size()) { - changeset_list.clear(); - } - else { - for (size_t idx = 0; idx < count; ++idx) { - changeset_list.remove(0); - } - } - - int64_t query_version = bootstrap_obj.get(m_query_version); - if (changeset_list.is_empty()) { - m_logger.debug(util::LogCategory::changeset, "Removing pending bootstrap obj for query version %1", - query_version); - bootstrap_obj.remove(); - } - else { - m_logger.debug(util::LogCategory::changeset, - "Removing pending bootstrap batch for query version %1. %2 changeset remaining", query_version, - changeset_list.size()); - } - - m_has_pending = !bootstrap_table->is_empty(); - if (!m_has_pending) { - m_subscription_store->complete_bootstrap(tr, query_version); - } -} - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/pending_bootstrap_store.hpp b/src/realm/sync/noinst/pending_bootstrap_store.hpp deleted file mode 100644 index e9e4079694b..00000000000 --- a/src/realm/sync/noinst/pending_bootstrap_store.hpp +++ /dev/null @@ -1,117 +0,0 @@ -/************************************************************************* - * - * Copyright 2022 Realm, Inc. - * - * 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 "realm/db.hpp" -#include "realm/list.hpp" -#include "realm/obj.hpp" -#include "realm/object-store/c_api/util.hpp" -#include "realm/sync/transform.hpp" -#include "realm/util/buffer.hpp" -#include "realm/util/logger.hpp" -#include "realm/util/optional.hpp" -#include "realm/util/span.hpp" -#include - -namespace realm::sync { - -class SubscriptionStore; - -class PendingBootstrapException : public std::runtime_error { -public: - using std::runtime_error::runtime_error; -}; - -// The PendingBootstrapStore is used internally by the sync client to store changesets from FLX sync bootstraps -// that are sent across multiple download messages. -class PendingBootstrapStore { -public: - // Constructs from a DBRef. - // - // Underneath this creates a table which stores each download message's changesets. - PendingBootstrapStore(DBRef db, util::Logger& logger, std::shared_ptr subscription_store); - - PendingBootstrapStore(const PendingBootstrapStore&) = delete; - PendingBootstrapStore& operator=(const PendingBootstrapStore&) = delete; - - // True if there are pending changesets to process. - bool has_pending() const noexcept; - - struct PendingBatch { - int64_t query_version = 0; - std::vector changesets; - std::vector> changeset_data; - util::Optional progress; - size_t remaining_changesets = 0; - }; - - // Returns the next batch (download message) of changesets if it exists. The transaction must be in the reading - // state. - PendingBatch peek_pending(Transaction& tr, size_t limit_in_bytes); - - struct PendingBatchStats { - int64_t query_version = 0; - size_t pending_changesets = 0; - size_t pending_changeset_bytes = 0; - }; - PendingBatchStats pending_stats(); - - // Removes the first set of changesets from the current pending bootstrap batch. The transaction must be in the - // writing state. - void pop_front_pending(const Transaction& tr, size_t count); - - // Adds a set of changesets to the store. - void add_batch(int64_t query_version, util::Optional progress, - DownloadableProgress download_progress, const std::vector& changesets); - - void clear(Transaction& wt, int64_t query_version); - -private: - DBRef m_db; - // The pending_bootstrap_store is tied to the lifetime of a session, so a shared_ptr is not needed - util::Logger& m_logger; - std::shared_ptr m_subscription_store; - - TableKey m_cursor_table; - - TableKey m_table; - ColKey m_changesets; - ColKey m_query_version; - ColKey m_progress; - - TableKey m_progress_table; - ColKey m_progress_download_server_version; - ColKey m_progress_download_client_version; - ColKey m_progress_upload_server_version; - ColKey m_progress_upload_client_version; - ColKey m_progress_latest_server_version; - ColKey m_progress_latest_server_version_salt; - - TableKey m_changeset_table; - ColKey m_changeset_remote_version; - ColKey m_changeset_last_integrated_client_version; - ColKey m_changeset_origin_file_ident; - ColKey m_changeset_origin_timestamp; - ColKey m_changeset_original_changeset_size; - ColKey m_changeset_data; - - bool m_has_pending = false; -}; - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/pending_reset_store.cpp b/src/realm/sync/noinst/pending_reset_store.cpp deleted file mode 100644 index 7afa7083713..00000000000 --- a/src/realm/sync/noinst/pending_reset_store.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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 - -#include -#include - -#include -#include - -using namespace realm; -using namespace _impl; - -namespace realm::sync { - -std::ostream& operator<<(std::ostream& os, const sync::PendingReset& pr) -{ - if (pr.action == sync::ProtocolErrorInfo::Action::NoAction || pr.time.is_null()) { - os << "empty pending client reset"; - } - else if (pr.action != sync::ProtocolErrorInfo::Action::ClientReset) { - os << "pending '" << pr.action << "' client reset of type: '" << pr.mode << "' at: " << pr.time; - } - else { - os << "pending client reset of type: '" << pr.mode << "' at: " << pr.time; - } - os << " for error: " << pr.error; - return os; -} - -bool operator==(const sync::PendingReset& lhs, const sync::PendingReset& rhs) -{ - return (lhs.mode == rhs.mode && lhs.action == rhs.action && lhs.time == rhs.time); -} - -bool operator==(const sync::PendingReset& lhs, const PendingReset::Action& action) -{ - return lhs.action == action; -} - -// A table without a "class_" prefix will not generate sync instructions. -constexpr static std::string_view s_meta_reset_table_name("client_reset_metadata"); -constexpr static std::string_view s_version_col_name("core_version"); -constexpr static std::string_view s_timestamp_col_name("time"); -constexpr static std::string_view s_reset_recovery_mode_col_name("mode"); -constexpr static std::string_view s_reset_action_col_name("action"); -constexpr static std::string_view s_reset_error_code_col_name("error_code"); -constexpr static std::string_view s_reset_error_msg_col_name("error_msg"); - -void PendingResetStore::clear_pending_reset(Group& group) -{ - if (auto table = group.get_table(s_meta_reset_table_name); table && !table->is_empty()) { - table->clear(); - } -} - -std::optional PendingResetStore::has_pending_reset(const Group& group) -{ - auto reset_store = PendingResetStore::load_schema(group); - if (!reset_store) { - // Table hasn't been created yet (or has the wrong schema) - return std::nullopt; - } - REALM_ASSERT(reset_store->m_pending_reset_table); - auto table = group.get_table(reset_store->m_pending_reset_table); - - if (!table || table->size() != 1) { - return std::nullopt; - } - auto reset_entry = *table->begin(); - if (reset_entry.get(reset_store->m_version) != REALM_VERSION_STRING) { - // Previous pending reset was written by a different version, so ignore it - return std::nullopt; - } - - PendingReset pending; - pending.time = reset_entry.get(reset_store->m_timestamp); - pending.mode = to_resync_mode(reset_entry.get(reset_store->m_recovery_mode)); - pending.action = to_reset_action(reset_entry.get(reset_store->m_action)); - auto error_code = reset_entry.get(reset_store->m_error_code); - if (error_code != 0) { - pending.error = Status(static_cast(error_code), - reset_entry.get(reset_store->m_error_message)); - } - return pending; -} - -void PendingResetStore::track_reset(Group& group, ClientResyncMode mode, PendingReset::Action action, Status error) -{ - REALM_ASSERT(mode != ClientResyncMode::Manual); - auto reset_store = PendingResetStore::load_or_create_schema(group); - - REALM_ASSERT(reset_store.m_pending_reset_table); - auto table = group.get_table(reset_store.m_pending_reset_table); - REALM_ASSERT(table); - table->clear(); - table->create_object(null_key, { - {reset_store.m_version, Mixed(REALM_VERSION_STRING)}, - {reset_store.m_timestamp, Timestamp(std::chrono::system_clock::now())}, - {reset_store.m_recovery_mode, from_resync_mode(mode)}, - {reset_store.m_action, from_reset_action(action)}, - {reset_store.m_error_code, static_cast(error.code())}, - {reset_store.m_error_message, error.reason()}, - }); -} - -PendingResetStore::PendingResetStore(const Group& g) - : m_internal_tables{ - {&m_pending_reset_table, - s_meta_reset_table_name, - { - {&m_version, s_version_col_name, type_String}, - {&m_timestamp, s_timestamp_col_name, type_Timestamp}, - {&m_recovery_mode, s_reset_recovery_mode_col_name, type_Int}, - {&m_action, s_reset_action_col_name, type_Int}, - {&m_error_code, s_reset_error_code_col_name, type_Int}, - {&m_error_message, s_reset_error_msg_col_name, type_String}, - }}, - } -{ - if (!try_load_sync_metadata_schema(g, &m_internal_tables).is_ok()) { - m_pending_reset_table = {}; - } -} - -std::optional PendingResetStore::load_schema(const Group& group) -{ - if (PendingResetStore reset_store(group); reset_store.m_pending_reset_table) { - return reset_store; - } - return std::nullopt; -} - -PendingResetStore PendingResetStore::load_or_create_schema(Group& group) -{ - PendingResetStore reset_store(group); - if (!reset_store.m_pending_reset_table) { - // If the table exists but has the wrong schema just drop it - if (group.has_table(s_meta_reset_table_name)) { - group.remove_table(s_meta_reset_table_name); - } - - // Create the table with the correct schema - create_sync_metadata_schema(group, &reset_store.m_internal_tables); - } - return reset_store; -} - -int64_t PendingResetStore::from_reset_action(PendingReset::Action action) -{ - switch (action) { - case PendingReset::Action::ClientReset: - return 1; - case PendingReset::Action::ClientResetNoRecovery: - return 2; - case PendingReset::Action::MigrateToFLX: - return 3; - case PendingReset::Action::RevertToPBS: - return 4; - default: - throw ClientResetFailed(util::format("Unsupported client reset action: %1 for pending reset", action)); - } -} - -PendingReset::Action PendingResetStore::to_reset_action(int64_t action) -{ - switch (action) { - case 1: - return PendingReset::Action::ClientReset; - case 2: - return PendingReset::Action::ClientResetNoRecovery; - case 3: - return PendingReset::Action::MigrateToFLX; - case 4: - return PendingReset::Action::RevertToPBS; - default: - return PendingReset::Action::NoAction; - } -} - -ClientResyncMode PendingResetStore::to_resync_mode(int64_t mode) -{ - // Retains compatibility with v1 - // RecoverOrDiscard is treated as Recover and is not stored - switch (mode) { - case 0: // DiscardLocal - return ClientResyncMode::DiscardLocal; - case 1: // Recover - return ClientResyncMode::Recover; - default: - throw ClientResetFailed(util::format("Unsupported client reset resync mode: %1 for pending reset", mode)); - } -} - -int64_t PendingResetStore::from_resync_mode(ClientResyncMode mode) -{ - // Retains compatibility with v1 - switch (mode) { - case ClientResyncMode::DiscardLocal: - return 0; // DiscardLocal - case ClientResyncMode::RecoverOrDiscard: - [[fallthrough]]; // RecoverOrDiscard is treated as Recover - case ClientResyncMode::Recover: - return 1; // Recover - default: - throw ClientResetFailed(util::format("Unsupported client reset resync mode: %1 for pending reset", mode)); - } -} - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/pending_reset_store.hpp b/src/realm/sync/noinst/pending_reset_store.hpp deleted file mode 100644 index b8e06ab84b9..00000000000 --- a/src/realm/sync/noinst/pending_reset_store.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef REALM_NOINST_PENDING_RESET_STORE_HPP -#define REALM_NOINST_PENDING_RESET_STORE_HPP - -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace realm::sync { - -struct PendingReset { - using Action = sync::ProtocolErrorInfo::Action; - Timestamp time; - ClientResyncMode mode; - Action action = Action::NoAction; - Status error = Status::OK(); -}; - -std::ostream& operator<<(std::ostream& os, const sync::PendingReset& pr); -bool operator==(const sync::PendingReset& lhs, const sync::PendingReset& rhs); -bool operator==(const sync::PendingReset& lhs, const PendingReset::Action& action); - -class PendingResetStore { -public: - // Store the pending reset tracking information - it is an error if the tracking info already - // exists in the store - // Requires a writable transaction and changes must be committed manually - static void track_reset(Group& group, ClientResyncMode mode, PendingReset::Action action, Status error); - // Clear the pending reset tracking information, if it exists - // Requires a writable transaction and changes must be committed manually - static void clear_pending_reset(Group& group); - static std::optional has_pending_reset(const Group& group); - - static int64_t from_reset_action(PendingReset::Action action); - static PendingReset::Action to_reset_action(int64_t action); - static ClientResyncMode to_resync_mode(int64_t mode); - static int64_t from_resync_mode(ClientResyncMode mode); - -private: - // The instantiated class is only used internally - PendingResetStore(const Group& group); - - std::vector m_internal_tables; - TableKey m_pending_reset_table; - ColKey m_version; - ColKey m_timestamp; - ColKey m_recovery_mode; - ColKey m_action; - ColKey m_error_code; - ColKey m_error_message; - - // Returns true if the schema was loaded - static std::optional load_schema(const Group& group); - // Loads the schema or creates it if it doesn't exist - // Requires a writable transaction and changes must be committed manually - static PendingResetStore load_or_create_schema(Group& group); -}; - -} // namespace realm::sync - -#endif // REALM_NOINST_PENDING_RESET_STORE_HPP diff --git a/src/realm/sync/noinst/protocol_codec.cpp b/src/realm/sync/noinst/protocol_codec.cpp deleted file mode 100644 index a91bf1f7e78..00000000000 --- a/src/realm/sync/noinst/protocol_codec.cpp +++ /dev/null @@ -1,328 +0,0 @@ -#include -#include -#include -#include - -namespace realm::_impl { - -using OutputBuffer = util::ResettableExpandableBufferOutputStream; - -// Client protocol - -void ClientProtocol::make_pbs_bind_message(int protocol_version, OutputBuffer& out, session_ident_type session_ident, - const std::string& server_path, const std::string& signed_user_token, - bool need_client_file_ident, bool is_subserver) -{ - static_cast(protocol_version); - out << "bind " << session_ident << " " << server_path.size() << " " << signed_user_token.size() << " " - << int(need_client_file_ident) << " " << int(is_subserver) << "\n"; // Throws - REALM_ASSERT(!out.fail()); - - out.write(server_path.data(), server_path.size()); // Throws - out.write(signed_user_token.data(), signed_user_token.size()); // Throws -} - -void ClientProtocol::make_flx_bind_message(int protocol_version, OutputBuffer& out, session_ident_type session_ident, - const nlohmann::json& json_data, const std::string& signed_user_token, - bool need_client_file_ident, bool is_subserver) -{ - static_cast(protocol_version); - std::string json_data_stg; - // Protocol version v8 and above accepts stringified json_data for the first data argument - if (!json_data.empty()) { - json_data_stg = json_data.dump(); - } - - out << "bind " << session_ident << " " << json_data_stg.size() << " " << signed_user_token.size() << " " - << int(need_client_file_ident) << " " << int(is_subserver) << "\n"; // Throws - REALM_ASSERT(!out.fail()); - - out.write(json_data_stg.data(), json_data_stg.size()); // Throws - out.write(signed_user_token.data(), signed_user_token.size()); // Throws -} - -void ClientProtocol::make_pbs_ident_message(OutputBuffer& out, session_ident_type session_ident, - SaltedFileIdent client_file_ident, const SyncProgress& progress) -{ - out << "ident " << session_ident << " " << client_file_ident.ident << " " << client_file_ident.salt << " " - << progress.download.server_version << " " << progress.download.last_integrated_client_version << " " - << progress.latest_server_version.version << " " << progress.latest_server_version.salt << "\n"; // Throws - REALM_ASSERT(!out.fail()); -} - -void ClientProtocol::make_flx_ident_message(OutputBuffer& out, session_ident_type session_ident, - SaltedFileIdent client_file_ident, const SyncProgress& progress, - int64_t query_version, std::string_view query_body) -{ - out << "ident " << session_ident << " " << client_file_ident.ident << " " << client_file_ident.salt << " " - << progress.download.server_version << " " << progress.download.last_integrated_client_version << " " - << progress.latest_server_version.version << " " << progress.latest_server_version.salt << " " - << query_version << " " << query_body.size() << "\n" - << query_body; // Throws - REALM_ASSERT(!out.fail()); -} - -void ClientProtocol::make_query_change_message(OutputBuffer& out, session_ident_type session, int64_t version, - std::string_view query_body) -{ - out << "query " << session << " " << version << " " << query_body.size() << "\n" << query_body; // throws - REALM_ASSERT(!out.fail()); -} - -void ClientProtocol::make_json_error_message(OutputBuffer& out, session_ident_type session, int error_code, - std::string_view error_body) -{ - out << "json_error " << error_code << " " << error_body.size() << " " << session << "\n" << error_body; // throws - REALM_ASSERT(!out.fail()); -} - -void ClientProtocol::make_test_command_message(OutputBuffer& out, session_ident_type session, - request_ident_type request_ident, std::string_view body) -{ - out << "test_command " << session << " " << request_ident << " " << body.size() << "\n" << body; - REALM_ASSERT(!out.fail()); -} - -ClientProtocol::UploadMessageBuilder::UploadMessageBuilder( - OutputBuffer& body_buffer, std::vector& compression_buffer, - util::compression::CompressMemoryArena& compress_memory_arena) - : m_body_buffer{body_buffer} - , m_compression_buffer{compression_buffer} - , m_compress_memory_arena{compress_memory_arena} -{ - m_body_buffer.reset(); -} - -void ClientProtocol::UploadMessageBuilder::add_changeset(version_type client_version, version_type server_version, - timestamp_type origin_timestamp, - file_ident_type origin_file_ident, - ChunkedBinaryData changeset) -{ - m_body_buffer << client_version << " " << server_version << " " << origin_timestamp << " " << origin_file_ident - << " " << changeset.size() << " "; // Throws - changeset.write_to(m_body_buffer); // Throws - REALM_ASSERT(!m_body_buffer.fail()); - - ++m_num_changesets; -} - -void ClientProtocol::UploadMessageBuilder::make_upload_message(int protocol_version, OutputBuffer& out, - session_ident_type session_ident, - version_type progress_client_version, - version_type progress_server_version, - version_type locked_server_version) -{ - static_cast(protocol_version); - BinaryData body = {m_body_buffer.data(), std::size_t(m_body_buffer.size())}; - - constexpr std::size_t g_max_uncompressed = 1024; - - bool is_body_compressed = false; - if (body.size() > g_max_uncompressed) { - util::compression::allocate_and_compress(m_compress_memory_arena, body, - m_compression_buffer); // Throws - is_body_compressed = m_compression_buffer.size() < body.size(); - } - - // The compressed body is only sent if it is smaller than the uncompressed body. - std::size_t compressed_body_size = is_body_compressed ? m_compression_buffer.size() : 0; - - // The header of the upload message. - out << "upload " << session_ident << " " << int(is_body_compressed) << " " << body.size() << " " - << compressed_body_size; - out << " " << progress_client_version << " " << progress_server_version << " " << locked_server_version; // Throws - out << "\n"; // Throws - - if (is_body_compressed) - out.write(m_compression_buffer.data(), compressed_body_size); // Throws - else - out.write(body.data(), body.size()); // Throws - - REALM_ASSERT(!out.fail()); -} - -ClientProtocol::UploadMessageBuilder ClientProtocol::make_upload_message_builder() -{ - return UploadMessageBuilder{m_output_buffer, m_buffer, m_compress_memory_arena}; -} - -void ClientProtocol::make_unbind_message(OutputBuffer& out, session_ident_type session_ident) -{ - out << "unbind " << session_ident << "\n"; // Throws - REALM_ASSERT(!out.fail()); -} - -void ClientProtocol::make_mark_message(OutputBuffer& out, session_ident_type session_ident, - request_ident_type request_ident) -{ - out << "mark " << session_ident << " " << request_ident << "\n"; // Throws - REALM_ASSERT(!out.fail()); -} - - -void ClientProtocol::make_ping(OutputBuffer& out, milliseconds_type timestamp, milliseconds_type rtt) -{ - out << "ping " << timestamp << " " << rtt << "\n"; // Throws -} - - -std::string ClientProtocol::compressed_hex_dump(BinaryData blob) -{ - std::vector buf; - util::compression::allocate_and_compress(m_compress_memory_arena, blob, buf); // Throws - - std::string encode_buffer; - auto encoded_size = util::base64_encoded_size(buf.size()); - encode_buffer.resize(encoded_size); - util::base64_encode(buf, encode_buffer); - - return encode_buffer; -} - -// Server protocol - -void ServerProtocol::make_ident_message(int protocol_version, OutputBuffer& out, session_ident_type session_ident, - file_ident_type client_file_ident, salt_type client_file_ident_salt) -{ - static_cast(protocol_version); - out << "ident " << session_ident << " " << client_file_ident << " " << client_file_ident_salt << "\n"; // Throws -} - -void ServerProtocol::make_alloc_message(OutputBuffer& out, session_ident_type session_ident, - file_ident_type file_ident) -{ - out << "alloc " << session_ident << " " << file_ident << "\n"; // Throws -} - - -/// insert_single_changeset_download_message() inserts a single changeset and -/// the associated meta data into the output buffer. -/// -/// It is the functions responsibility to make sure that the buffer has -/// capacity to hold the inserted data. -/// -/// The message format for the single changeset is -/// -/// -void ServerProtocol::insert_single_changeset_download_message(OutputBuffer& out, const ChangesetInfo& changeset_info, - util::Logger& logger) -{ - const sync::HistoryEntry& entry = changeset_info.entry; - - out << changeset_info.server_version << " " << changeset_info.client_version << " " << entry.origin_timestamp - << " " << entry.origin_file_ident << " " << changeset_info.original_size << " " << entry.changeset.size() - << " "; - entry.changeset.write_to(out); - - if (logger.would_log(util::Logger::Level::trace)) { - util::AppendBuffer changeset_buffer; - entry.changeset.copy_to(changeset_buffer); - - logger.trace(util::LogCategory::changeset, - "DOWNLOAD: insert single changeset (server_version=%1, " - "client_version=%2, timestamp=%3, client_file_ident=%4, " - "original_changeset_size=%5, changeset_size=%6, changeset='%7').", - changeset_info.server_version, changeset_info.client_version, entry.origin_timestamp, - entry.origin_file_ident, changeset_info.original_size, entry.changeset.size(), - _impl::clamped_hex_dump(BinaryData(changeset_buffer.data(), changeset_buffer.size()))); // Throws - } -} - - -void ServerProtocol::make_download_message(int protocol_version, OutputBuffer& out, session_ident_type session_ident, - version_type download_server_version, version_type download_client_version, - version_type latest_server_version, salt_type latest_server_version_salt, - version_type upload_client_version, version_type upload_server_version, - std::uint_fast64_t downloadable_bytes, std::size_t num_changesets, - const char* body, std::size_t uncompressed_body_size, - std::size_t compressed_body_size, bool body_is_compressed, - util::Logger& logger) -{ - static_cast(protocol_version); - // The header of the download message. - out << "download " << session_ident << " " << download_server_version << " " << download_client_version << " " - << latest_server_version << " " << latest_server_version_salt << " " << upload_client_version << " " - << upload_server_version << " " << downloadable_bytes << " " << int(body_is_compressed) << " " - << uncompressed_body_size << " " << compressed_body_size << "\n"; // Throws - - std::size_t body_size = (body_is_compressed ? compressed_body_size : uncompressed_body_size); - out.write(body, body_size); - - logger.detail(util::LogCategory::changeset, - "Sending: DOWNLOAD(download_server_version=%1, download_client_version=%2, " - "latest_server_version=%3, latest_server_version_salt=%4, " - "upload_client_version=%5, upload_server_version=%6, " - "num_changesets=%7, is_body_compressed=%8, body_size=%9, " - "compressed_body_size=%10)", - download_server_version, download_client_version, latest_server_version, latest_server_version_salt, - upload_client_version, upload_server_version, num_changesets, body_is_compressed, - uncompressed_body_size, compressed_body_size); // Throws -} - - -void ServerProtocol::make_unbound_message(OutputBuffer& out, session_ident_type session_ident) -{ - out << "unbound " << session_ident << "\n"; // Throws -} - - -void ServerProtocol::make_mark_message(OutputBuffer& out, session_ident_type session_ident, - request_ident_type request_ident) -{ - out << "mark " << session_ident << " " << request_ident << "\n"; // Throws - REALM_ASSERT(!out.fail()); -} - - -void ServerProtocol::make_error_message(int protocol_version, OutputBuffer& out, sync::ProtocolError error_code, - const char* message, std::size_t message_size, bool try_again, - session_ident_type session_ident) -{ - static_cast(protocol_version); - sync::ProtocolError error_code_2 = error_code; - out << "error " << int(error_code_2) << " " << message_size << " " << int(try_again) << " " << session_ident - << "\n"; // Throws - out.write(message, message_size); // Throws -} - - -void ServerProtocol::make_pong(OutputBuffer& out, milliseconds_type timestamp) -{ - out << "pong " << timestamp << "\n"; // Throws -} - -void ServerProtocol::make_log_message(OutputBuffer& out, util::Logger::Level level, std::string message, - session_ident_type sess_id, std::optional co_id) -{ - nlohmann::json log_msg_json; - log_msg_json["level"] = util::Logger::level_to_string(level); - log_msg_json["msg"] = message; - if (co_id) { - log_msg_json["co_id"] = *co_id; - } - std::string json_data_stg = log_msg_json.dump(); - out << "log_message " << sess_id << " " << json_data_stg.length() << "\n" << json_data_stg; -} - -std::string make_authorization_header(const std::string& signed_user_token) -{ - return "Bearer " + signed_user_token; -} - - -util::Optional parse_authorization_header(const std::string& authorization_header) -{ - StringData prefix = "Bearer "; - // Token contains at least three characters. Stricter checks are possible, but do - // not belong here. - if (authorization_header.size() < prefix.size() + 4) - return util::none; - - if (authorization_header.compare(0, prefix.size(), prefix) != 0) - return util::none; - - std::size_t token_size = authorization_header.size() - prefix.size(); - return StringData{authorization_header.data() + prefix.size(), token_size}; -} - -} // namespace realm::_impl diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp deleted file mode 100644 index c6f71d78d9e..00000000000 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ /dev/null @@ -1,924 +0,0 @@ -#ifndef REALM_NOINST_PROTOCOL_CODEC_HPP -#define REALM_NOINST_PROTOCOL_CODEC_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace realm::_impl { -struct ProtocolCodecException : public std::runtime_error { - using std::runtime_error::runtime_error; -}; -class HeaderLineParser { -public: - explicit HeaderLineParser(std::string_view line) - : m_sv(line) - { - } - - template - T read_next(char expected_terminator = ' ') - { - const auto [tok, rest] = peek_token_impl(); - if (rest.empty()) { - throw ProtocolCodecException("header line ended prematurely without terminator"); - } - if (rest.front() != expected_terminator) { - throw ProtocolCodecException(util::format( - "expected to find delimeter '%1' in header line, but found '%2'", expected_terminator, rest.front())); - } - m_sv = rest.substr(1); - return tok; - } - - template - T read_sized_data(size_t size) - { - auto ret = m_sv; - advance(size); - return T(ret.data(), size); - } - - size_t bytes_remaining() const noexcept - { - return m_sv.size(); - } - - std::string_view remaining() const noexcept - { - return m_sv; - } - - bool at_end() const noexcept - { - return m_sv.empty(); - } - - void advance(size_t size) - { - if (size > m_sv.size()) { - throw ProtocolCodecException( - util::format("cannot advance header by %1 characters, only %2 characters left", size, m_sv.size())); - } - m_sv.remove_prefix(size); - } - -private: - template - std::pair peek_token_impl() const - { - // We currently only support numeric, string, and boolean values in header lines. - static_assert(std::is_integral_v || std::is_floating_point_v || - is_any_v); - if (at_end()) { - throw ProtocolCodecException("reached end of header line prematurely"); - } - if constexpr (is_any_v) { - // Currently all string fields in wire protocol header lines appear at the beginning of the line and - // should be delimited by a space. - auto delim_at = m_sv.find(' '); - if (delim_at == std::string_view::npos) { - throw ProtocolCodecException("reached end of header line prematurely"); - } - - return {m_sv.substr(0, delim_at), m_sv.substr(delim_at)}; - } - else if constexpr (std::is_integral_v && !std::is_same_v) { - T cur_arg = {}; - auto parse_res = util::from_chars(m_sv.data(), m_sv.data() + m_sv.size(), cur_arg, 10); - if (parse_res.ec != std::errc{}) { - throw ProtocolCodecException(util::format("error parsing integer in header line: %1", - std::make_error_code(parse_res.ec).message())); - } - - return {cur_arg, m_sv.substr(parse_res.ptr - m_sv.data())}; - } - else if constexpr (std::is_same_v) { - int cur_arg; - auto parse_res = util::from_chars(m_sv.data(), m_sv.data() + m_sv.size(), cur_arg, 10); - if (parse_res.ec != std::errc{}) { - throw ProtocolCodecException(util::format("error parsing boolean in header line: %1", - std::make_error_code(parse_res.ec).message())); - } - - return {(cur_arg != 0), m_sv.substr(parse_res.ptr - m_sv.data())}; - } - else if constexpr (std::is_floating_point_v) { - // Currently all double are in the middle of the string delimited by a space. - auto delim_at = m_sv.find(' '); - if (delim_at == std::string_view::npos) - throw ProtocolCodecException("reached end of header line prematurely for double value parsing"); - - // FIXME use std::from_chars one day when it's availiable in every std lib - T val = {}; - try { - std::string str(m_sv.substr(0, delim_at)); - if constexpr (std::is_same_v) - val = std::stof(str); - else if constexpr (std::is_same_v) - val = std::stod(str); - else if constexpr (std::is_same_v) - val = std::stold(str); - } - catch (const std::exception& err) { - throw ProtocolCodecException( - util::format("error parsing floating-point number in header line: %1", err.what())); - } - - return {val, m_sv.substr(delim_at)}; - } - } - - std::string_view m_sv; -}; - -class ClientProtocol { -public: - // clang-format off - using file_ident_type = sync::file_ident_type; - using version_type = sync::version_type; - using salt_type = sync::salt_type; - using timestamp_type = sync::timestamp_type; - using session_ident_type = sync::session_ident_type; - using request_ident_type = sync::request_ident_type; - using milliseconds_type = sync::milliseconds_type; - using SaltedFileIdent = sync::SaltedFileIdent; - using SaltedVersion = sync::SaltedVersion; - using DownloadCursor = sync::DownloadCursor; - using UploadCursor = sync::UploadCursor; - using SyncProgress = sync::SyncProgress; - // clang-format on - - using OutputBuffer = util::ResettableExpandableBufferOutputStream; - using RemoteChangeset = sync::RemoteChangeset; - using ReceivedChangesets = std::vector; - - /// Messages sent by the client. - - void make_pbs_bind_message(int protocol_version, OutputBuffer&, session_ident_type session_ident, - const std::string& server_path, const std::string& signed_user_token, - bool need_client_file_ident, bool is_subserver); - - void make_flx_bind_message(int protocol_version, OutputBuffer& out, session_ident_type session_ident, - const nlohmann::json& json_data, const std::string& signed_user_token, - bool need_client_file_ident, bool is_subserver); - - void make_pbs_ident_message(OutputBuffer&, session_ident_type session_ident, SaltedFileIdent client_file_ident, - const SyncProgress& progress); - - void make_flx_ident_message(OutputBuffer&, session_ident_type session_ident, SaltedFileIdent client_file_ident, - const SyncProgress& progress, int64_t query_version, std::string_view query_body); - - void make_query_change_message(OutputBuffer&, session_ident_type, int64_t version, std::string_view query_body); - - void make_json_error_message(OutputBuffer&, session_ident_type, int error_code, std::string_view error_body); - - void make_test_command_message(OutputBuffer&, session_ident_type session, request_ident_type request_ident, - std::string_view body); - - class UploadMessageBuilder { - public: - UploadMessageBuilder(OutputBuffer& body_buffer, std::vector& compression_buffer, - util::compression::CompressMemoryArena& compress_memory_arena); - - void add_changeset(version_type client_version, version_type server_version, timestamp_type origin_timestamp, - file_ident_type origin_file_ident, ChunkedBinaryData changeset); - - void make_upload_message(int protocol_version, OutputBuffer&, session_ident_type session_ident, - version_type progress_client_version, version_type progress_server_version, - version_type locked_server_version); - - private: - std::size_t m_num_changesets = 0; - OutputBuffer& m_body_buffer; - std::vector& m_compression_buffer; - util::compression::CompressMemoryArena& m_compress_memory_arena; - }; - - UploadMessageBuilder make_upload_message_builder(); - - void make_unbind_message(OutputBuffer&, session_ident_type session_ident); - - void make_mark_message(OutputBuffer&, session_ident_type session_ident, request_ident_type request_ident); - - void make_ping(OutputBuffer&, milliseconds_type timestamp, milliseconds_type rtt); - - std::string compressed_hex_dump(BinaryData blob); - - // Messages received by the client. - - // parse_message_received takes a (WebSocket) message and parses it. - // The result of the parsing is handled by an object of type Connection. - // Typically, Connection would be the Connection class from client.cpp - template - void parse_message_received(Connection& connection, std::string_view msg_data) - { - util::Logger& logger = connection.logger; - auto report_error = [&](const auto fmt, auto&&... args) { - auto msg = util::format(fmt, std::forward(args)...); - connection.handle_protocol_error(Status{ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); - }; - - HeaderLineParser msg(msg_data); - std::string_view message_type; - try { - message_type = msg.read_next(); - } - catch (const ProtocolCodecException& e) { - return report_error("Could not find message type in message: %1", e.what()); - } - - try { - if (message_type == "download") { - parse_download_message(connection, msg); - } - else if (message_type == "pong") { - auto timestamp = msg.read_next('\n'); - connection.receive_pong(timestamp); - } - else if (message_type == "unbound") { - auto session_ident = msg.read_next('\n'); - connection.receive_unbound_message(session_ident); // Throws - } - else if (message_type == "error") { - auto error_code = msg.read_next(); - auto message_size = msg.read_next(); - auto is_fatal = sync::IsFatal{!msg.read_next()}; - auto session_ident = msg.read_next('\n'); - auto message = msg.read_sized_data(message_size); - - connection.receive_error_message(sync::ProtocolErrorInfo{error_code, message, is_fatal}, - session_ident); // Throws - } - else if (message_type == "log_message") { // introduced in protocol version 10 - parse_log_message(connection, msg); - } - else if (message_type == "json_error") { // introduced in protocol 4 - sync::ProtocolErrorInfo info{}; - info.raw_error_code = msg.read_next(); - auto message_size = msg.read_next(); - auto session_ident = msg.read_next('\n'); - auto json_raw = msg.read_sized_data(message_size); - try { - auto json = nlohmann::json::parse(json_raw); - logger.trace(util::LogCategory::session, "Error message encoded as json: %1", json_raw); - info.client_reset_recovery_is_disabled = json["isRecoveryModeDisabled"]; - info.is_fatal = sync::IsFatal{!json["tryAgain"]}; - info.message = json["message"]; - info.log_url = std::make_optional(json["logURL"]); - info.should_client_reset = std::make_optional(json["shouldClientReset"]); - info.server_requests_action = string_to_action(json["action"]); // Throws - - if (auto backoff_interval = json.find("backoffIntervalSec"); backoff_interval != json.end()) { - info.resumption_delay_interval.emplace(); - info.resumption_delay_interval->resumption_delay_interval = - std::chrono::seconds{backoff_interval->get()}; - info.resumption_delay_interval->max_resumption_delay_interval = - std::chrono::seconds{json.at("backoffMaxDelaySec").get()}; - info.resumption_delay_interval->resumption_delay_backoff_multiplier = - json.at("backoffMultiplier").get(); - } - - if (info.raw_error_code == static_cast(sync::ProtocolError::migrate_to_flx)) { - auto query_string = json.find("partitionQuery"); - if (query_string == json.end() || !query_string->is_string() || - query_string->get().empty()) { - return report_error( - "Missing/invalid partition query string in migrate to flexible sync error response"); - } - - info.migration_query_string.emplace(query_string->get()); - } - - if (info.raw_error_code == static_cast(sync::ProtocolError::schema_version_changed)) { - auto schema_version = json.find("previousSchemaVersion"); - if (schema_version == json.end() || !schema_version->is_number_unsigned()) { - return report_error( - "Missing/invalid previous schema version in schema migration error response"); - } - - info.previous_schema_version.emplace(schema_version->get()); - } - - if (auto rejected_updates = json.find("rejectedUpdates"); rejected_updates != json.end()) { - if (!rejected_updates->is_array()) { - return report_error( - "Compensating writes error list is not stored in an array as expected"); - } - - for (const auto& rejected_update : *rejected_updates) { - if (!rejected_update.is_object()) { - return report_error( - "Compensating write error information is not stored in an object as expected"); - } - - sync::CompensatingWriteErrorInfo cwei; - cwei.reason = rejected_update["reason"]; - cwei.object_name = rejected_update["table"]; - std::string_view pk = rejected_update["pk"].get(); - cwei.primary_key = sync::parse_base64_encoded_primary_key(pk); - info.compensating_writes.push_back(std::move(cwei)); - } - - // Not provided when 'write_not_allowed' (230) error is received from the server. - if (auto server_version = json.find("compensatingWriteServerVersion"); - server_version != json.end()) { - info.compensating_write_server_version = - std::make_optional(server_version->get()); - } - info.compensating_write_rejected_client_version = - json.at("rejectedClientVersion").get(); - } - } - catch (const nlohmann::json::exception& e) { - // If any of the above json fields are not present, this is a fatal error - // however, additional optional fields may be added in the future. - return report_error("Failed to parse 'json_error' with error_code %1: '%2'", info.raw_error_code, - e.what()); - } - connection.receive_error_message(info, session_ident); // Throws - } - else if (message_type == "query_error") { - auto error_code = msg.read_next(); - auto message_size = msg.read_next(); - auto session_ident = msg.read_next(); - auto query_version = msg.read_next('\n'); - - auto message = msg.read_sized_data(message_size); - - connection.receive_query_error_message(error_code, message, query_version, session_ident); // throws - } - else if (message_type == "mark") { - auto session_ident = msg.read_next(); - auto request_ident = msg.read_next('\n'); - - connection.receive_mark_message(session_ident, request_ident); // Throws - } - else if (message_type == "ident") { - session_ident_type session_ident = msg.read_next(); - SaltedFileIdent client_file_ident; - client_file_ident.ident = msg.read_next(); - client_file_ident.salt = msg.read_next('\n'); - - connection.receive_ident_message(session_ident, client_file_ident); // Throws - } - else if (message_type == "test_command") { - session_ident_type session_ident = msg.read_next(); - request_ident_type request_ident = msg.read_next(); - auto body_size = msg.read_next('\n'); - auto body = msg.read_sized_data(body_size); - - connection.receive_test_command_response(session_ident, request_ident, body); - } - else { - return report_error("Unknown input message type '%1'", msg_data); - } - } - catch (const ProtocolCodecException& e) { - return report_error("Bad syntax in %1 message: %2", message_type, e.what()); - } - if (!msg.at_end()) { - return report_error("wire protocol message had leftover data after being parsed"); - } - } - - struct DownloadMessage { - SyncProgress progress; - std::optional query_version; // FLX sync only - sync::DownloadBatchState batch_state = sync::DownloadBatchState::SteadyState; - sync::DownloadableProgress downloadable; - ReceivedChangesets changesets; - }; - -private: - template - void parse_download_message(Connection& connection, HeaderLineParser& msg) - { - bool is_flx = connection.is_flx_sync_connection(); - - util::Logger& logger = connection.logger; - auto report_error = [&](ErrorCodes::Error code, const auto fmt, auto&&... args) { - auto msg = util::format(fmt, std::forward(args)...); - connection.handle_protocol_error(Status{code, std::move(msg)}); - }; - - auto msg_with_header = msg.remaining(); - auto session_ident = msg.read_next(); - - DownloadMessage message; - auto&& progress = message.progress; - progress.download.server_version = msg.read_next(); - progress.download.last_integrated_client_version = msg.read_next(); - progress.latest_server_version.version = msg.read_next(); - progress.latest_server_version.salt = msg.read_next(); - progress.upload.client_version = msg.read_next(); - progress.upload.last_integrated_server_version = msg.read_next(); - - if (is_flx) { - message.query_version = msg.read_next(); - if (message.query_version < 0) - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Bad query version: %1", - message.query_version); - int batch_state = msg.read_next(); - if (batch_state != static_cast(sync::DownloadBatchState::MoreToCome) && - batch_state != static_cast(sync::DownloadBatchState::LastInBatch) && - batch_state != static_cast(sync::DownloadBatchState::SteadyState)) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Bad batch state: %1", batch_state); - } - message.batch_state = static_cast(batch_state); - - double progress_estimate = msg.read_next(); - if (progress_estimate < 0 || progress_estimate > 1) - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Bad progress value: %1", - progress_estimate); - message.downloadable = progress_estimate; - } - else - message.downloadable = uint64_t(msg.read_next()); - - auto is_body_compressed = msg.read_next(); - auto uncompressed_body_size = msg.read_next(); - auto compressed_body_size = msg.read_next('\n'); - - if (uncompressed_body_size > s_max_body_size) { - auto header = msg_with_header.substr(0, msg_with_header.size() - msg.remaining().size()); - return report_error(ErrorCodes::LimitExceeded, "Limits exceeded in input message '%1'", header); - } - - std::unique_ptr uncompressed_body_buffer; - // if is_body_compressed == true, we must decompress the received body. - if (is_body_compressed) { - uncompressed_body_buffer = std::make_unique(uncompressed_body_size); - std::error_code ec = - util::compression::decompress({msg.remaining().data(), compressed_body_size}, - {uncompressed_body_buffer.get(), uncompressed_body_size}); - - if (ec) { - return report_error(ErrorCodes::RuntimeError, "compression::inflate: %1", ec.message()); - } - - msg = HeaderLineParser(std::string_view(uncompressed_body_buffer.get(), uncompressed_body_size)); - } - - logger.debug(util::LogCategory::changeset, - "Download message compression: session_ident=%1, is_body_compressed=%2, " - "compressed_body_size=%3, uncompressed_body_size=%4", - session_ident, is_body_compressed, compressed_body_size, uncompressed_body_size); - - // Loop through the body and find the changesets. - while (!msg.at_end()) { - RemoteChangeset cur_changeset; - cur_changeset.remote_version = msg.read_next(); - cur_changeset.last_integrated_local_version = msg.read_next(); - cur_changeset.origin_timestamp = msg.read_next(); - cur_changeset.origin_file_ident = msg.read_next(); - cur_changeset.original_changeset_size = msg.read_next(); - auto changeset_size = msg.read_next(); - - if (changeset_size > msg.bytes_remaining()) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Bad changeset size %1 > %2", - changeset_size, msg.bytes_remaining()); - } - if (cur_changeset.remote_version == 0) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, - "Server version in downloaded changeset cannot be zero"); - } - auto changeset_data = msg.read_sized_data(changeset_size); - logger.debug(util::LogCategory::changeset, - "Received: DOWNLOAD CHANGESET(session_ident=%1, server_version=%2, " - "client_version=%3, origin_timestamp=%4, origin_file_ident=%5, " - "original_changeset_size=%6, changeset_size=%7)", - session_ident, cur_changeset.remote_version, cur_changeset.last_integrated_local_version, - cur_changeset.origin_timestamp, cur_changeset.origin_file_ident, - cur_changeset.original_changeset_size, changeset_size); // Throws - if (logger.would_log(util::LogCategory::changeset, util::Logger::Level::trace)) { - if (changeset_data.size() < 1056) { - logger.trace(util::LogCategory::changeset, "Changeset: %1", - clamped_hex_dump(changeset_data)); // Throws - } - else { - logger.trace(util::LogCategory::changeset, "Changeset(comp): %1 %2", changeset_data.size(), - compressed_hex_dump(changeset_data)); // Throws - } -#if REALM_DEBUG - ChunkedBinaryInputStream in{changeset_data}; - sync::Changeset log; - sync::parse_changeset(in, log); - std::stringstream ss; - log.print(ss); - logger.trace(util::LogCategory::changeset, "Changeset (parsed):\n%1", ss.str()); -#endif - } - - cur_changeset.data = changeset_data; - message.changesets.push_back(std::move(cur_changeset)); // Throws - } - - connection.receive_download_message(session_ident, message); // Throws - } - - static sync::ProtocolErrorInfo::Action string_to_action(const std::string& action_string) - { - using action = sync::ProtocolErrorInfo::Action; - static const std::unordered_map mapping{ - {"ProtocolViolation", action::ProtocolViolation}, - {"ApplicationBug", action::ApplicationBug}, - {"Warning", action::Warning}, - {"Transient", action::Transient}, - {"DeleteRealm", action::DeleteRealm}, - {"ClientReset", action::ClientReset}, - {"ClientResetNoRecovery", action::ClientResetNoRecovery}, - {"MigrateToFLX", action::MigrateToFLX}, - {"RevertToPBS", action::RevertToPBS}, - {"RefreshUser", action::RefreshUser}, - {"RefreshLocation", action::RefreshLocation}, - {"LogOutUser", action::LogOutUser}, - {"MigrateSchema", action::MigrateSchema}, - }; - - if (auto action_it = mapping.find(action_string); action_it != mapping.end()) { - return action_it->second; - } - return action::ApplicationBug; - } - - template - void parse_log_message(Connection& connection, HeaderLineParser& msg) - { - auto report_error = [&](const auto fmt, auto&&... args) { - auto msg = util::format(fmt, std::forward(args)...); - connection.handle_protocol_error(Status{ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); - }; - - auto session_ident = msg.read_next(); - auto message_length = msg.read_next('\n'); - auto message_body_str = msg.read_sized_data(message_length); - nlohmann::json message_body; - try { - message_body = nlohmann::json::parse(message_body_str); - } - catch (const nlohmann::json::exception& e) { - return report_error("Malformed json in log_message message: \"%1\": %2", message_body_str, e.what()); - } - static const std::unordered_map name_to_level = { - {"fatal", util::Logger::Level::fatal}, {"error", util::Logger::Level::error}, - {"warn", util::Logger::Level::warn}, {"info", util::Logger::Level::info}, - {"detail", util::Logger::Level::detail}, {"debug", util::Logger::Level::debug}, - {"trace", util::Logger::Level::trace}, - }; - - // See if the log_message contains the appservices_request_id - if (auto it = message_body.find("co_id"); it != message_body.end() && it->is_string()) { - connection.receive_appservices_request_id(it->get()); - } - - std::string_view log_level; - bool has_level = false; - if (auto it = message_body.find("level"); it != message_body.end() && it->is_string()) { - log_level = it->get(); - has_level = !log_level.empty(); - } - - std::string_view msg_text; - if (auto it = message_body.find("msg"); it != message_body.end() && it->is_string()) { - msg_text = it->get(); - } - - // If there is no message text, then we're done - if (msg_text.empty()) { - return; - } - - // If a log level wasn't provided, default to debug - util::Logger::Level parsed_level = util::Logger::Level::debug; - if (has_level) { - if (auto it = name_to_level.find(log_level); it != name_to_level.end()) { - parsed_level = it->second; - } - else { - return report_error("Unknown log level found in log_message: \"%1\"", log_level); - } - } - connection.receive_server_log_message(session_ident, parsed_level, msg_text); - } - - static constexpr std::size_t s_max_body_size = std::numeric_limits::max(); - - // Permanent buffer to use for building messages. - OutputBuffer m_output_buffer; - - // Permanent buffers to use for internal purposes such as compression. - std::vector m_buffer; - - util::compression::CompressMemoryArena m_compress_memory_arena; -}; - - -class ServerProtocol { -public: - // clang-format off - using file_ident_type = sync::file_ident_type; - using version_type = sync::version_type; - using salt_type = sync::salt_type; - using timestamp_type = sync::timestamp_type; - using session_ident_type = sync::session_ident_type; - using request_ident_type = sync::request_ident_type; - using SaltedFileIdent = sync::SaltedFileIdent; - using SaltedVersion = sync::SaltedVersion; - using milliseconds_type = sync::milliseconds_type; - using UploadCursor = sync::UploadCursor; - // clang-format on - - using OutputBuffer = util::ResettableExpandableBufferOutputStream; - - // Messages sent by the server to the client - - void make_ident_message(int protocol_version, OutputBuffer&, session_ident_type session_ident, - file_ident_type client_file_ident, salt_type client_file_ident_salt); - - void make_alloc_message(OutputBuffer&, session_ident_type session_ident, file_ident_type file_ident); - - void make_unbound_message(OutputBuffer&, session_ident_type session_ident); - - - struct ChangesetInfo { - version_type server_version; - version_type client_version; - sync::HistoryEntry entry; - std::size_t original_size; - }; - - void make_download_message(int protocol_version, OutputBuffer&, session_ident_type session_ident, - version_type download_server_version, version_type download_client_version, - version_type latest_server_version, salt_type latest_server_version_salt, - version_type upload_client_version, version_type upload_server_version, - std::uint_fast64_t downloadable_bytes, std::size_t num_changesets, const char* body, - std::size_t uncompressed_body_size, std::size_t compressed_body_size, - bool body_is_compressed, util::Logger&); - - void make_mark_message(OutputBuffer&, session_ident_type session_ident, request_ident_type request_ident); - - void make_error_message(int protocol_version, OutputBuffer&, sync::ProtocolError error_code, const char* message, - std::size_t message_size, bool try_again, session_ident_type session_ident); - - void make_pong(OutputBuffer&, milliseconds_type timestamp); - - void make_log_message(OutputBuffer& out, util::Logger::Level level, std::string message, - session_ident_type sess_id = 0, std::optional co_id = std::nullopt); - - // Messages received by the server. - - // parse_ping_received takes a (WebSocket) ping and parses it. - // The result of the parsing is handled by an object of type Connection. - // Typically, Connection would be the Connection class from server.cpp - template - void parse_ping_received(Connection& connection, std::string_view msg_data) - { - try { - HeaderLineParser msg(msg_data); - auto timestamp = msg.read_next(); - auto rtt = msg.read_next('\n'); - - connection.receive_ping(timestamp, rtt); - } - catch (const ProtocolCodecException& e) { - connection.handle_protocol_error(Status{ErrorCodes::SyncProtocolInvariantFailed, - util::format("Bad syntax in PING message: %1", e.what())}); - } - } - - // UploadChangeset is used to store received changesets in - // the UPLOAD message. - struct UploadChangeset { - UploadCursor upload_cursor; - timestamp_type origin_timestamp; - file_ident_type origin_file_ident; // Zero when originating from connected client file - BinaryData changeset; - }; - - // parse_message_received takes a (WebSocket) message and parses it. - // The result of the parsing is handled by an object of type Connection. - // Typically, Connection would be the Connection class from server.cpp - template - void parse_message_received(Connection& connection, std::string_view msg_data) - { - auto& logger = connection.logger; - - auto report_error = [&](ErrorCodes::Error err, const auto fmt, auto&&... args) { - auto msg = util::format(fmt, std::forward(args)...); - connection.handle_protocol_error(Status{err, std::move(msg)}); - }; - - HeaderLineParser msg(msg_data); - std::string_view message_type; - try { - message_type = msg.read_next(); - } - catch (const ProtocolCodecException& e) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Could not find message type in message: %1", - e.what()); - } - - try { - if (message_type == "upload") { - auto msg_with_header = msg.remaining(); - auto session_ident = msg.read_next(); - auto is_body_compressed = msg.read_next(); - auto uncompressed_body_size = msg.read_next(); - auto compressed_body_size = msg.read_next(); - auto progress_client_version = msg.read_next(); - auto progress_server_version = msg.read_next(); - auto locked_server_version = msg.read_next('\n'); - - std::size_t body_size = (is_body_compressed ? compressed_body_size : uncompressed_body_size); - if (body_size > s_max_body_size) { - auto header = msg_with_header.substr(0, msg_with_header.size() - msg.bytes_remaining()); - - return report_error(ErrorCodes::LimitExceeded, - "Body size of upload message is too large. Raw header: %1", header); - } - - - std::unique_ptr uncompressed_body_buffer; - // if is_body_compressed == true, we must decompress the received body. - if (is_body_compressed) { - uncompressed_body_buffer = std::make_unique(uncompressed_body_size); - auto compressed_body = msg.read_sized_data(compressed_body_size); - - std::error_code ec = util::compression::decompress( - compressed_body, {uncompressed_body_buffer.get(), uncompressed_body_size}); - - if (ec) { - return report_error(ErrorCodes::RuntimeError, "compression::inflate: %1", ec.message()); - } - - msg = HeaderLineParser(std::string_view(uncompressed_body_buffer.get(), uncompressed_body_size)); - } - - logger.debug(util::LogCategory::changeset, - "Upload message compression: is_body_compressed = %1, " - "compressed_body_size=%2, uncompressed_body_size=%3, " - "progress_client_version=%4, progress_server_version=%5, " - "locked_server_version=%6", - is_body_compressed, compressed_body_size, uncompressed_body_size, - progress_client_version, progress_server_version, locked_server_version); // Throws - - - std::vector upload_changesets; - - // Loop through the body and find the changesets. - while (!msg.at_end()) { - UploadChangeset upload_changeset; - size_t changeset_size; - try { - upload_changeset.upload_cursor.client_version = msg.read_next(); - upload_changeset.upload_cursor.last_integrated_server_version = msg.read_next(); - upload_changeset.origin_timestamp = msg.read_next(); - upload_changeset.origin_file_ident = msg.read_next(); - changeset_size = msg.read_next(); - } - catch (const ProtocolCodecException& e) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, - "Bad changeset header syntax: %1", e.what()); - } - - if (changeset_size > msg.bytes_remaining()) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Bad changeset size"); - } - - upload_changeset.changeset = msg.read_sized_data(changeset_size); - - if (logger.would_log(util::Logger::Level::trace)) { - logger.trace(util::LogCategory::changeset, - "Received: UPLOAD CHANGESET(client_version=%1, server_version=%2, " - "origin_timestamp=%3, origin_file_ident=%4, changeset_size=%5)", - upload_changeset.upload_cursor.client_version, - upload_changeset.upload_cursor.last_integrated_server_version, - upload_changeset.origin_timestamp, upload_changeset.origin_file_ident, - changeset_size); // Throws - logger.trace(util::LogCategory::changeset, "Changeset: %1", - clamped_hex_dump(upload_changeset.changeset)); // Throws - } - upload_changesets.push_back(std::move(upload_changeset)); // Throws - } - - connection.receive_upload_message(session_ident, progress_client_version, progress_server_version, - locked_server_version, - upload_changesets); // Throws - } - else if (message_type == "mark") { - auto session_ident = msg.read_next(); - auto request_ident = msg.read_next('\n'); - - connection.receive_mark_message(session_ident, request_ident); // Throws - } - else if (message_type == "ping") { - auto timestamp = msg.read_next(); - auto rtt = msg.read_next('\n'); - - connection.receive_ping(timestamp, rtt); - } - else if (message_type == "bind") { - auto session_ident = msg.read_next(); - auto path_size = msg.read_next(); - auto signed_user_token_size = msg.read_next(); - auto need_client_file_ident = msg.read_next(); - auto is_subserver = msg.read_next('\n'); - - if (path_size == 0) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Path size in BIND message is zero"); - } - if (path_size > s_max_path_size) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, - "Path size in BIND message is too large"); - } - if (signed_user_token_size > s_max_signed_user_token_size) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, - "Signed user token size in BIND message is too large"); - } - - auto path = msg.read_sized_data(path_size); - auto signed_user_token = msg.read_sized_data(signed_user_token_size); - - connection.receive_bind_message(session_ident, std::move(path), std::move(signed_user_token), - need_client_file_ident, is_subserver); // Throws - } - else if (message_type == "ident") { - auto session_ident = msg.read_next(); - auto client_file_ident = msg.read_next(); - auto client_file_ident_salt = msg.read_next(); - auto scan_server_version = msg.read_next(); - auto scan_client_version = msg.read_next(); - auto latest_server_version = msg.read_next(); - auto latest_server_version_salt = msg.read_next('\n'); - - connection.receive_ident_message(session_ident, client_file_ident, client_file_ident_salt, - scan_server_version, scan_client_version, latest_server_version, - latest_server_version_salt); // Throws - } - else if (message_type == "unbind") { - auto session_ident = msg.read_next('\n'); - - connection.receive_unbind_message(session_ident); // Throws - } - else if (message_type == "json_error") { - auto error_code = msg.read_next(); - auto message_size = msg.read_next(); - auto session_ident = msg.read_next('\n'); - auto json_raw = msg.read_sized_data(message_size); - - connection.receive_error_message(session_ident, error_code, json_raw); - } - else { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "unknown message type %1", message_type); - } - } - catch (const ProtocolCodecException& e) { - return report_error(ErrorCodes::SyncProtocolInvariantFailed, "bad syntax in %1 message: %2", message_type, - e.what()); - } - } - - void insert_single_changeset_download_message(OutputBuffer&, const ChangesetInfo&, util::Logger&); - -private: - // clang-format off - static constexpr std::size_t s_max_head_size = 256; - static constexpr std::size_t s_max_signed_user_token_size = 2048; - static constexpr std::size_t s_max_client_info_size = 1024; - static constexpr std::size_t s_max_path_size = 1024; - static constexpr std::size_t s_max_changeset_size = std::numeric_limits::max(); // FIXME: What is a reasonable value here? - static constexpr std::size_t s_max_body_size = std::numeric_limits::max(); - // clang-format on -}; - -// make_authorization_header() makes the value of the Authorization header used in the -// sync Websocket handshake. -std::string make_authorization_header(const std::string& signed_user_token); - -// parse_authorization_header() parses the value of the Authorization header and returns -// the signed_user_token. None is returned in case of syntax error. -util::Optional parse_authorization_header(const std::string& authorization_header); - -} // namespace realm::_impl - -#endif // REALM_NOINST_PROTOCOL_CODEC_HPP diff --git a/src/realm/sync/noinst/root_certs.hpp b/src/realm/sync/noinst/root_certs.hpp deleted file mode 100644 index 315e3bb665c..00000000000 --- a/src/realm/sync/noinst/root_certs.hpp +++ /dev/null @@ -1,3961 +0,0 @@ -/******************************************************************************* - * - * This header file contains a list of SSL root certificates. The certificates - * are copied from the node.js git repository src/node_root_certs.h. node.js - * itself obtains its list of certificates from - * Mozilla's CA certificate store. - * - * This file can be updated at convenience. - * - *******************************************************************************/ - -#ifndef REALM_NOINST_ROOT_CERTS -#define REALM_NOINST_ROOT_CERTS -// clang-format off - -/* GlobalSign Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMC\n" -"QkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNV\n" -"BAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBa\n" -"MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdS\n" -"b290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA\n" -"A4IBDwAwggEKAoIBAQDaDuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtI\n" -"K+6NiY6arymAZavpxy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCO\n" -"XkNz8kHp1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" -"snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3\n" -"dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DP\n" -"AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRg\n" -"e2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEA1nPnfE920I2/7LqivjTF\n" -"KDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY7\n" -"76BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9\n" -"LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr\n" -"+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" -"HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" -"-----END CERTIFICATE-----\n", - -/* GlobalSign Root CA - R2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMX\n" -"R2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMT\n" -"Ckdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQL\n" -"ExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UE\n" -"AxMKR2xvYmFsU2lnbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8o\n" -"mUVCxKs+IVSbC9N/hHD6ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7\n" -"SqbKSaZeqKeMWhG8eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQ\n" -"BoZfXklqtTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd\n" -"C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feq\n" -"CapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8E\n" -"BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IHV2ccHsBqBt5ZtJot39wZhi4w\n" -"NgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9iYWxzaWduLm5ldC9yb290LXIyLmNy\n" -"bDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEA\n" -"mYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkI\n" -"k7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRD\n" -"LenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd\n" -"AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7TBj0/VLZ\n" -"jmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==\n" -"-----END CERTIFICATE-----\n", - -/* Verisign Class 3 Public Primary Certification Authority - G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQG\n" -"EwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0\n" -"IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv\n" -"cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1h\n" -"cnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3\n" -"MTYyMzU5NTlaMIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAd\n" -"BgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlT\n" -"aWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu\n" -"IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCC\n" -"ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2\n" -"R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6\n" -"yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFU\n" -"okWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyo\n" -"w0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBte\n" -"HRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my\n" -"/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe\n" -"DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC/Y4wjylG\n" -"sB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0xuKh\n" -"XFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa\n" -"t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==\n" -"-----END CERTIFICATE-----\n", - -/* Entrust.net Premium 2048 Secure Server CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVz\n" -"dC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJl\n" -"Zi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0\n" -"ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4\n" -"KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0\n" -"Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVm\n" -"LiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl\n" -"ZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp\n" -"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtK\n" -"TY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/EC\n" -"DNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ\n" -"/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzWnLLPKQP5L6RQstRIzgUyVYr9smRM\n" -"DuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVC\n" -"wQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\n" -"BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQAD\n" -"ggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo\n" -"U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6YfzX1XEC+b\n" -"BAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKTJ1wD\n" -"LW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e\n" -"nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=\n" -"-----END CERTIFICATE-----\n", - -/* Baltimore CyberTrust Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAG\n" -"A1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1v\n" -"cmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjEL\n" -"MAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEi\n" -"MCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQAD\n" -"ggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2ygu\n" -"zmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo\n" -"6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" -"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3z\n" -"yZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkC\n" -"AwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1BE3wMBIGA1UdEwEB/wQIMAYB\n" -"Af8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27\n" -"TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukM\n" -"JY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhS\n" -"NzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67\n" -"G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n" -"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" -"-----END CERTIFICATE-----\n", - -/* AddTrust Low-Value Services Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UE\n" -"ChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQD\n" -"ExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAz\n" -"ODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk\n" -"ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3Qw\n" -"ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH\n" -"+9ZOEQpnXvUGW2ulCDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7\n" -"Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl\n" -"dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0\n" -"K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG\n" -"9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+\n" -"wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MIGPBgNVHSMEgYcwgYSAFJWx\n" -"tPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1\n" -"c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVz\n" -"dCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0\n" -"MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz\n" -"43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MYeDdXL+gz\n" -"B2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xrmYbv\n" -"P33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj\n" -"ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=\n" -"-----END CERTIFICATE-----\n", - -/* AddTrust External Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UE\n" -"ChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3Jr\n" -"MSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoX\n" -"DTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYw\n" -"JAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1\n" -"c3QgRXh0ZXJuYWwgQ0EgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3\n" -"GjPm8gAELTngTlvtH7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCw\n" -"SXrbLpX9uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX\n" -"mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63u\n" -"bUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5\n" -"aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0WicCAwEAAaOB3DCB2TAdBgNV\n" -"HQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMB\n" -"Af8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYT\n" -"AlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwg\n" -"VFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJ\n" -"KoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH\n" -"YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw56wwCURQt\n" -"jr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355e6cJ\n" -"DUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u\n" -"G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49O\n" -"hgQ=\n" -"-----END CERTIFICATE-----\n", - -/* AddTrust Public Services Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UE\n" -"ChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQD\n" -"ExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQx\n" -"NTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRk\n" -"VHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIB\n" -"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4\n" -"jsIMEZBRpS9mVEBV6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrH\n" -"AZcHF/nXGCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP\n" -"dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2ro\n" -"yBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9\n" -"BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQWBBSBPjfYkrAfd59ctKtzquf2\n" -"NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfY\n" -"krAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0\n" -"IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3Qg\n" -"UHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmu\n" -"G7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/\n" -"iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/AoGEjwxrzQ\n" -"vzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9Yjll\n" -"pu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H\n" -"EufOX1362KqxMy3ZdvJOOjMMK7MtkAY=\n" -"-----END CERTIFICATE-----\n", - -/* AddTrust Qualified Certificates Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UE\n" -"ChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQD\n" -"ExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAx\n" -"MDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMU\n" -"QWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBS\n" -"b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTb\n" -"Yjx5eLfpMLXsDBwqxBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqz\n" -"ZwFZ8V1G87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i\n" -"2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mH\n" -"fExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvES\n" -"a0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6\n" -"WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGG\n" -"gBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0Fk\n" -"ZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRk\n" -"VHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2Vh\n" -"lRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm\n" -"hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6XdgWTP5XH\n" -"AeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9zeRXEw\n" -"Mn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB\n" -"iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE=\n" -"-----END CERTIFICATE-----\n", - -/* Entrust Root Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAU\n" -"BgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMg\n" -"aW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwg\n" -"SW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X\n" -"DTA2MTEyNzIwMjM0MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQK\n" -"Ew1FbnRydXN0LCBJbmMuMTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29y\n" -"cG9yYXRlZCBieSByZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4x\n" -"LTArBgNVBAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ\n" -"KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poB\n" -"j6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypo\n" -"wCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+\n" -"SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rV\n" -"vDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2\n" -"HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n" -"/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSME\n" -"GDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE\n" -"vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQCT\n" -"1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISMY/YP\n" -"yyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa\n" -"v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE\n" -"2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPc\n" -"j2A781q0tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8\n" -"-----END CERTIFICATE-----\n", - -/* GeoTrust Global CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYD\n" -"VQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIw\n" -"NTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2Vv\n" -"VHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0B\n" -"AQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEH\n" -"CIjaWC9mOSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu\n" -"T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386D\n" -"GXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+\n" -"bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvo\n" -"cWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9\n" -"qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1luMrMTjANBgkqhkiG9w0BAQUF\n" -"AAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VS\n" -"syShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfO\n" -"EVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQd\n" -"tqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeX\n" -"xx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==\n" -"-----END CERTIFICATE-----\n", - -/* GeoTrust Global CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UE\n" -"ChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQw\n" -"MzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2Vv\n" -"VHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3\n" -"DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6Csgncbz\n" -"YEbYwbLVjDHZ3CB5JIG/NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5S\n" -"JBri1WeR0IIQ13hLTytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHq\n" -"Z38MN5aL5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7\n" -"S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/\n" -"XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266\n" -"ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUapEBVYIAUJMA4GA1UdDwEB/wQE\n" -"AwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7srJerJsOflN4WT5CBP51o62sgU7X\n" -"AotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5\n" -"FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW4\n" -"1uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa\n" -"4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz\n" -"4iIprn2DQKi6bA==\n" -"-----END CERTIFICATE-----\n", - -/* GeoTrust Universal CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UE\n" -"ChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0\n" -"MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdl\n" -"b1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZI\n" -"hvcNAQEBBQADggIPADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckU\n" -"HUWCq8YdgNY96xCcOq9tJPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDg\n" -"FgDgEB8rMQ7XlFTTQjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEY\n" -"fyh3peFhF7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v\n" -"c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+\n" -"59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xdVHppCZbW2xHBjXWo\n" -"tM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCXteGYO8A3ZNY9lO4L4fUorgtW\n" -"v3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2\n" -"Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3\n" -"wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGj\n" -"YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8G\n" -"A1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG\n" -"9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRcaanQmjg8\n" -"+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2qaav\n" -"dy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL\n" -"oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG\n" -"8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzn\n" -"s0ccjkxFKyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3k\n" -"t0tm7wNFYGm2DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkD\n" -"MBmhLMi9ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt\n" -"DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6\n" -"ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=\n" -"-----END CERTIFICATE-----\n", - -/* GeoTrust Universal CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UE\n" -"ChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcN\n" -"MDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN\n" -"R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0G\n" -"CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6Gq\n" -"dHtXr0om/Nj1XqduGdt0DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSC\n" -"egx2oG9NzkEtoBUGFF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O\n" -"64ceJHdqXbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL\n" -"se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaq\n" -"W9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IEr\n" -"KIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73y/Zl92zxlfgCOzJWgjl6W70v\n" -"iRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuU\n" -"YbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xf\n" -"BHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQID\n" -"AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQ\n" -"KzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ\n" -"KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+zdXkzoS9t\n" -"cBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ4T7G\n" -"zKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+\n" -"mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEn\n" -"cKpqA1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8p\n" -"RPPphXpgY+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp\n" -"8RW04eWe3fiPpm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Bas\n" -"x7InQJJVOCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH\n" -"6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSL\n" -"akhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS\n" -"-----END CERTIFICATE-----\n", - -/* Visa eCommerce Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYD\n" -"VQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNl\n" -"cnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIw\n" -"NjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklT\n" -"QTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAa\n" -"BgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" -"AoIBAQCvV95WHm6h2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVI\n" -"sZHBAk4ElpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV\n" -"ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzz\n" -"lr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0\n" -"lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBA\n" -"MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMe\n" -"zUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytM\n" -"iUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1k\n" -"k5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGI\n" -"xHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw\n" -"++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt398znM/j\n" -"ra6O1I7mT1GvFpLgXPYHDw==\n" -"-----END CERTIFICATE-----\n", - -/* Certum Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYD\n" -"VQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTEx\n" -"MDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRv\n" -"IFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" -"ADCCAQoCggEBAM6xwS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYV\n" -"M42sLQnFdvkrOYCJ5JdLkKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82Kxu\n" -"jZlakE403Daaj4GIULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2\n" -"bu4lXapuOb7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg\n" -"AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEA\n" -"AaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESS\n" -"bLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIK\n" -"umB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvgGrZgFCdsMneMvLJymM/NzD+5yCRC\n" -"FNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQ\n" -"pNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6J\n" -"QEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==\n" -"-----END CERTIFICATE-----\n", - -/* Comodo AAA Services root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UE\n" -"CAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21v\n" -"ZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0\n" -"MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdy\n" -"ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENB\n" -"IExpbWl0ZWQxITAfBgNVBAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZI\n" -"hvcNAQEBBQADggEPADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686td\n" -"UIoWMQuaBtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe\n" -"3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8Ioa\n" -"E+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULi\n" -"mAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7S\n" -"w4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYD\n" -"VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDov\n" -"L2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0\n" -"dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG\n" -"9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q\n" -"GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLzRt0vxuBq\n" -"w8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z8VlI\n" -"MCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C\n" -"12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==\n" -"-----END CERTIFICATE-----\n", - -/* Comodo Secure Services root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UE\n" -"CAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21v\n" -"ZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4X\n" -"DTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgM\n" -"EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2Rv\n" -"IENBIExpbWl0ZWQxJDAiBgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIw\n" -"DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6\n" -"EfQlhfPMcm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S\n" -"HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJA\n" -"GysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtG\n" -"Cd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz6YiO/O1R65NxTq0B50SOqy3L\n" -"qP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJ\n" -"Y08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeG\n" -"NWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3Js\n" -"MDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNl\n" -"cy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0\n" -"5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmjZ55B+glS\n" -"zAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRLDXE9\n" -"7IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw\n" -"pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6s\n" -"Cx1HRR3B7Hzs/Sk=\n" -"-----END CERTIFICATE-----\n", - -/* Comodo Trusted Services root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UE\n" -"CAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21v\n" -"ZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAe\n" -"Fw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQI\n" -"DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9k\n" -"byBDQSBMaW1pdGVkMSUwIwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIB\n" -"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWa\n" -"HiWsnOWWfnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt\n" -"TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgym\n" -"BwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW1O24zG71++IsWL1/\n" -"T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7kUlcsutT6vifR4buv5XAwAaf\n" -"0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1UdDgQWBBTFe1i97doladL3WRaoszLA\n" -"eydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqg\n" -"OIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu\n" -"Y3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2Vy\n" -"dmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/\n" -"HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32pSxBvzwG\n" -"a+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDABHcT\n" -"uPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l\n" -"R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOj\n" -"GM9O9y5Xt5hwXsjEeLBi\n" -"-----END CERTIFICATE-----\n", - -/* QuoVadis Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcG\n" -"A1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1\n" -"dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n" -"eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYD\n" -"VQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0\n" -"aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5\n" -"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTR\n" -"vM16z/Ypli4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D\n" -"rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtf\n" -"fp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZ\n" -"yH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospUxbF6lR1xHkopigPcakXBpBle\n" -"bzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4wPQYIKwYBBQUHAQEEMTAvMC0GCCsG\n" -"AQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUw\n" -"AwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCB\n" -"xxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBw\n" -"YXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy\n" -"ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJhY3RpY2Vz\n" -"LCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEWFmh0\n" -"dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu\n" -"BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJN\n" -"MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRp\n" -"b24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0\n" -"aG9yaXR5ggQ6tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70\n" -"mpKnGdSkfnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8\n" -"7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe\n" -"/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsT\n" -"IlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJD\n" -"Wl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOKSnQ2+Q==\n" -"-----END CERTIFICATE-----\n", - -/* QuoVadis Root CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNV\n" -"BAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0w\n" -"NjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBR\n" -"dW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqG\n" -"SIb3DQEBAQUAA4ICDwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4Gt\n" -"Mh6QRr+jhiYaHv5+HBg6XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp\n" -"3MJGF/hd/aTa/55JWpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsR\n" -"E8Scd3bBrrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp\n" -"+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI\n" -"0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2\n" -"BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIizPtGo/KPaHbDRsSNU30R2be1B\n" -"2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOhD7osFRXql7PSorW+8oyWHhqPHWyk\n" -"YTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyP\n" -"ZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQAB\n" -"o4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwz\n" -"JQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL\n" -"MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1Zh\n" -"ZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUvZ+YT\n" -"RYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3\n" -"UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgt\n" -"JodmVjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q8\n" -"0m/DShcK+JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W\n" -"6ZM/57Es3zrWIozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQj\n" -"rLhVoQPRTUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD\n" -"mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6y\n" -"hhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO\n" -"1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAF\n" -"ZdWCEOrCMc0u\n" -"-----END CERTIFICATE-----\n", - -/* QuoVadis Root CA 3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNV\n" -"BAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0w\n" -"NjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBR\n" -"dW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqG\n" -"SIb3DQEBAQUAA4ICDwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTP\n" -"krgEQK0CSzGrvI2RaNggDhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZ\n" -"z3HmDyl2/7FWeUUrH556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2Objyj\n" -"Ptr7guXd8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv\n" -"vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mta\n" -"a7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJ\n" -"k8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1\n" -"ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEXMJPpGovgc2PZapKUSU60rUqFxKMi\n" -"MPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArl\n" -"zW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQAB\n" -"o4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMw\n" -"gcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0\n" -"aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0aWZpY2F0\n" -"ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYBBQUH\n" -"AgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD\n" -"VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1\n" -"XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEb\n" -"MBkGA1UEAxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62g\n" -"LEz6wPJv92ZVqyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon\n" -"24QRiSemd1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd\n" -"+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hR\n" -"OJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j5\n" -"6hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6l\n" -"i92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8S\n" -"h17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7\n" -"j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEo\n" -"kt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7\n" -"zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=\n" -"-----END CERTIFICATE-----\n", - -/* Security Communication Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UE\n" -"ChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJv\n" -"b3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEY\n" -"MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0\n" -"aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8\n" -"V6UMbXaKL0u/ZPtM7orw8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzG\n" -"jGdnSj74cbAZJ6kJDKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1ae\n" -"V+7AwFb9Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N\n" -"QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OV\n" -"YNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZ\n" -"aNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG\n" -"9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g0dNq/vu+m22/xwVtWSDEHPC32oRY\n" -"AmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7K\n" -"aEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKq\n" -"L8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfci\n" -"oU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==\n" -"-----END CERTIFICATE-----\n", - -/* Sonera Class 2 Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UE\n" -"ChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoX\n" -"DTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UE\n" -"AxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAX\n" -"SjWdyvANlsdE+hY3/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gX\n" -"GM2RX/uJ4+q/Tl18GybTdXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7\n" -"Jp12W5dCsv+u8E7s3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCW\n" -"ctRUz2EjvOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu\n" -"8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0T\n" -"AQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEB\n" -"BQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zilzqsWuasvfDXLrNAPtEwr/IDv\n" -"a4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEIcbCdjdY0RzKQxmUk96BKfARzjzlv\n" -"F4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHa\n" -"PJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj\n" -"4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M\n" -"-----END CERTIFICATE-----\n", - -/* UTN USERFirst Hardware Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkG\n" -"A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UE\n" -"ChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVz\n" -"dC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQy\n" -"WhcNMTkwNzA5MTgxOTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQH\n" -"Ew5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYD\n" -"VQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt\n" -"SGFyZHdhcmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn\n" -"0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7Ho\n" -"xuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEH\n" -"OG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1p\n" -"LgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjfPe58BEydCl5rkdbux+0ojatNh4lz\n" -"0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8G\n" -"A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9\n" -"MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3\n" -"YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF\n" -"BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM//bey1Wi\n" -"CuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogWXecB\n" -"5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2\n" -"lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchq\n" -"J/kniCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnAS\n" -"fxAynB67nfhmqA==\n" -"-----END CERTIFICATE-----\n", - -/* Camerfirma Chambers of Commerce Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UE\n" -"ChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3\n" -"LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAe\n" -"Fw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQK\n" -"Ex5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cu\n" -"Y2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIB\n" -"IDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1\n" -"c2VHfRtbunXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d\n" -"BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IU\n" -"tdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUM\n" -"I/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyXroDclDZK9D7ONhMeU+SsTjoF\n" -"7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0f\n" -"BDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNy\n" -"bDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCG\n" -"SAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3Jn\n" -"MCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN\n" -"BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJzaWduLm9y\n" -"Zy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAifJ/7\n" -"kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD\n" -"L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QU\n" -"u/wNUPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34Oi\n" -"rsrXdx/nADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuz\n" -"Pu5ifdmA6Ap1erfutGWaIZDgqtCYvDi1czyL+Nw=\n" -"-----END CERTIFICATE-----\n", - -/* Camerfirma Global Chambersign Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UE\n" -"ChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3\n" -"LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcN\n" -"MDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe\n" -"QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNo\n" -"YW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0G\n" -"CSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQK\n" -"kotgVvq0Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s\n" -"QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjq\n" -"GTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8Co\n" -"X6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oP\n" -"X9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2\n" -"MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3Js\n" -"MB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZI\n" -"AYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5v\n" -"cmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE\n" -"VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hhbWJlcnNp\n" -"Z24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEAPDtw\n" -"kfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y\n" -"gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76Svpyk\n" -"BMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHR\n" -"Jw0lyDL4IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxK\n" -"oHflCStFREest2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==\n" -"-----END CERTIFICATE-----\n", - -/* XRamp Global CA Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkG\n" -"A1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJh\n" -"bXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlm\n" -"aWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjEL\n" -"MAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMb\n" -"WFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2Vy\n" -"dGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\n" -"JB69FbS638eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP\n" -"KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5df\n" -"T2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3\n" -"hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSP\n" -"puIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJ\n" -"KwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\n" -"BBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwu\n" -"eHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcN\n" -"AQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR\n" -"vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxtqZ4Bfj8p\n" -"zgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8nnxCb\n" -"HIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz\n" -"8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=\n" -"-----END CERTIFICATE-----\n", - -/* Go Daddy Class 2 CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UE\n" -"ChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAy\n" -"IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYy\n" -"MFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjEx\n" -"MC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAw\n" -"DQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWiz\n" -"V3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HF\n" -"iH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi\n" -"EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN\n" -"f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44\n" -"dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLEsNKR1EwRcbNhyz2h/t2oatTj\n" -"MIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2oatTjoWekZTBjMQswCQYDVQQGEwJV\n" -"UzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRk\n" -"eSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJ\n" -"KoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYX\n" -"MP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P\n" -"TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQHmyW74cN\n" -"xA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VILs9R\n" -"aRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b\n" -"vZ8=\n" -"-----END CERTIFICATE-----\n", - -/* Starfield Class 2 CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UE\n" -"ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENs\n" -"YXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5\n" -"MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2ll\n" -"cywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRo\n" -"b3JpdHkwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N\n" -"78gDGIc/oav7PKaf8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMe\n" -"j2YcOadN+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\n" -"X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4Umkhyn\n" -"ArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W\n" -"93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRb\n" -"Vazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0fhvRbVazc1xDCDqmI56FspGowaDEL\n" -"MAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAw\n" -"BgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG\n" -"A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1ep\n" -"oXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\n" -"eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJlxy16paq8\n" -"U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJDKVtH\n" -"CN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3\n" -"QBFGmh95DmK/D5fs4C8fF5Q=\n" -"-----END CERTIFICATE-----\n", - -/* StartCom Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UE\n" -"ChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUg\n" -"U2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcN\n" -"MDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN\n" -"U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2ln\n" -"bmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0G\n" -"CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul3\n" -"8kMKogZkpMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf\n" -"OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYc\n" -"cjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d\n" -"5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9\n" -"bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z6+hsTXBbKWWc3apdzK8BMewM69KN\n" -"6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHu\n" -"EhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZP\n" -"V/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOz\n" -"EmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID\n" -"AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYEFE4L7xqk\n" -"QFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0Y29t\n" -"Lm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj\n" -"YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYB\n" -"BQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIB\n" -"FilodHRwOi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUH\n" -"AgIwgcMwJxYgU3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0\n" -"ZWQgTGlhYmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg\n" -"dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUg\n" -"YXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQD\n" -"AgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1\n" -"dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Ey\n" -"weg78T3dRAlbB0mKKctmArexmvclmAk8jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk\n" -"4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8\n" -"rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrz\n" -"ZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L\n" -"EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYuL6lwhceW\n" -"D3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+PwqyvqCUqDvr0tVk+vB\n" -"tfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl1njFmUNj403g\n" -"dy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/lwLFCRsI\n" -"3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=\n" -"-----END CERTIFICATE-----\n", - -/* Taiwan GRCA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYD\n" -"VQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9y\n" -"aXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAu\n" -"BgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJ\n" -"KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN8\n" -"6aXfTEc2pBsBHH8eV4qNw8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOU\n" -"T0b3EEk3+qhZSV1qgQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQk\n" -"clSGxtKyyhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts\n" -"F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBq\n" -"nTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUq\n" -"dULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FCVGqY8A2tl+lSXunVanLeavcb\n" -"YBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNt\n" -"sea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6O\n" -"M5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMB\n" -"AAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkG\n" -"BGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK\n" -"UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZTulStbng\n" -"CnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6TjZwj/\n" -"5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2\n" -"Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1\n" -"AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0\n" -"dDzpD6QzDxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5L\n" -"KlwCCDTb+HbkZ6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05e\n" -"r/ayl4WXudpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz\n" -"ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v\n" -"3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS\n" -"-----END CERTIFICATE-----\n", - -/* Swisscom Root CA 1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYD\n" -"VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNh\n" -"dGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2\n" -"MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTEl\n" -"MCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Nj\n" -"b20gUm9vdCBDQSAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h\n" -"+BvVM5OAFmUgdbI9m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrW\n" -"W/oLJdihFvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/\n" -"TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdnt\n" -"Mhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJ\n" -"vbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJn\n" -"B3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbNcA78yeNmuk6NO4HLFWR7uZToXTNS\n" -"hXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyD\n" -"CW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0W\n" -"R+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p\n" -"/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw\n" -"FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0jBBgwFoAU\n" -"AyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9MA0G\n" -"CSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn\n" -"jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzn\n" -"eAXQMbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL\n" -"0iT43R4HVtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZ\n" -"NuR55LU/vJtlvrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLH\n" -"UKKwf4ipmXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH\n" -"b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBa\n" -"ZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7G\n" -"h0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5g\n" -"mwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6MBr1mmz0DlP5OlvRHA==\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert Assured ID Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYD\n" -"VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" -"Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAw\n" -"MDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg\n" -"SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1\n" -"cmVkIElEIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOA\n" -"XLGH87dg+XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT\n" -"XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+\n" -"wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/l\n" -"bQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcX\n" -"xH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQE\n" -"AwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAf\n" -"BgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog68\n" -"3+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqo\n" -"R+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+\n" -"fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx\n" -"H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe+o0bJW1s\n" -"j6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert Global Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYD\n" -"VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" -"Y29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBa\n" -"Fw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx\n" -"GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBS\n" -"b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKP\n" -"C3eQyaKl7hLOllsBCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscF\n" -"s3YnFo97nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" -"43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6g\n" -"SzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSii\n" -"cNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYD\n" -"VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgw\n" -"FoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1E\n" -"nE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDi\n" -"qw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBA\n" -"I+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" -"YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQkCAUw7C29\n" -"C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert High Assurance EV Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYD\n" -"VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" -"Y29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2\n" -"MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp\n" -"Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNl\n" -"cnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" -"AQoCggEBAMbM5XPm+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlB\n" -"WTrT3JTWPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n" -"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeB\n" -"QVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5\n" -"OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsgEsxBu24LUTi4S8sCAwEAAaNj\n" -"MGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9H\n" -"AdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3\n" -"DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1\n" -"ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VH\n" -"MWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n" -"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCevEsXCS+0\n" -"yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K\n" -"-----END CERTIFICATE-----\n", - -/* Certplus Class 2 Primary CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkG\n" -"A1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkg\n" -"Q0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8G\n" -"A1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZI\n" -"hvcNAQEBBQADggEPADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxv\n" -"c0NXYKwzCkTsA18cgCSR5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLR\n" -"YE2+L0ER4/YXJQyLkcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v\n" -"0lPubNCdEgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas\n" -"H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC\n" -"40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNV\n" -"HQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQw\n" -"MC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29tL0NSTC9jbGFzczIuY3JsMA0GCSqG\n" -"SIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5t\n" -"n9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabg\n" -"lZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW\n" -"2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB\n" -"kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7l7+ijrRU\n" -"-----END CERTIFICATE-----\n", - -/* DST Root CA X3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYD\n" -"VQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENB\n" -"IFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRh\n" -"bCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJ\n" -"KoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdA\n" -"wRgUi+DoM3ZJKuM/IUmTrE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwG\n" -"MoOifooUMM0RoOEqOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4X\n" -"Lh7dIN9bxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" -"7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkq\n" -"tilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw\n" -"HQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqGSIb3DQEBBQUAA4IBAQCjGiyb\n" -"FwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikugdB/OEIKcdBodfpga3csTS7MgROSR\n" -"6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaL\n" -"bumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir\n" -"/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06Xyx\n" -"V3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" -"-----END CERTIFICATE-----\n", - -/* DST ACES CA X6 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYD\n" -"VQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERT\n" -"VCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzEx\n" -"MjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBU\n" -"cnVzdDERMA8GA1UECxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjAN\n" -"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5\n" -"DgO0PWGSvSMmtWPuktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+io\n" -"kYi5Q1K7gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH\n" -"fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd7\n" -"55jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEITajV8fTXpLmaRcpPV\n" -"MibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD\n" -"AgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3QuY29tMGIGA1UdIARbMFkwVwYKYIZI\n" -"AWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZp\n" -"Y2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7\n" -"eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99\n" -"Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/\n" -"h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQqnExaBqXp\n" -"IK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXsvFcj\n" -"4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3\n" -"oKfN5XozNmr6mis=\n" -"-----END CERTIFICATE-----\n", - -/* SwissSign Gold CA - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNI\n" -"MRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0g\n" -"RzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMG\n" -"A1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIIC\n" -"IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJC\n" -"Eyq8ZVeCQD5XJM1QiyUqt2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcf\n" -"DmJlD909Vopz2q5+bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpi\n" -"kJKVyh+c6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE\n" -"emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT\n" -"28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdV\n" -"xVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02yMszYF9rNt85mndT9Xv+9lz4p\n" -"ded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkOpeUDDniOJihC8AcLYiAQZzlG+qkD\n" -"zAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR7ySArqpWl2/5rX3aYT+Ydzyl\n" -"kbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+Zr\n" -"zsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\n" -"FgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn\n" -"8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDovL3JlcG9z\n" -"aXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm5djV\n" -"9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr\n" -"44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8\n" -"AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0V\n" -"qbe/vd6mGu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9Qkvfsywe\n" -"xcZdylU6oJxpmo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/Eb\n" -"MFYOkrCChdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3\n" -"92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG\n" -"2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/Y\n" -"YPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkk\n" -"W8mw0FfB+j564ZfJ\n" -"-----END CERTIFICATE-----\n", - -/* SwissSign Silver CA - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gx\n" -"FTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAt\n" -"IEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTAT\n" -"BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcy\n" -"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dO\n" -"cbpLj6VzHVxumK4DV644N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGi\n" -"TSf5YXu6t+WiE7brYT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi\n" -"0R86TieFnbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH\n" -"6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyC\n" -"bTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jM\n" -"qDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/\n" -"+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBsROopN4WSaGa8gzj+ezku01DwH/te\n" -"YLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIj\n" -"QAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calI\n" -"Lv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n" -"HQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c\n" -"wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0cDovL3Jl\n" -"cG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P4JUw\n" -"4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F\n" -"kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcS\n" -"H9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkD\n" -"lm4fS/Bx/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakM\n" -"DHiqYMZWjwFaDGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHk\n" -"Flt4dR2Xem1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR\n" -"dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29\n" -"MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI\n" -"4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s\n" -"5Aq7KkzrCWA5zspi2C5u\n" -"-----END CERTIFICATE-----\n", - -/* GeoTrust Primary Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYD\n" -"VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJp\n" -"bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYy\n" -"MzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQD\n" -"EyhHZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG\n" -"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92\n" -"/ZV+zmEwu3qDXwK9AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa\n" -"9OBesYjAZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0\n" -"7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0\n" -"EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s\n" -"0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV\n" -"HQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZIhvcNAQEFBQADggEBAFpwfyzdtzRP\n" -"9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z\n" -"+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD3\n" -"2sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJly\n" -"c1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU\n" -"AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=\n" -"-----END CERTIFICATE-----\n", - -/* thawte Primary Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkG\n" -"A1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlv\n" -"biBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0g\n" -"Rm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3Qg\n" -"Q0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTAT\n" -"BgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBE\n" -"aXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6\n" -"ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG\n" -"SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3\n" -"/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29\n" -"dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk6KHYcWUNo1F77rzSImANuVud\n" -"37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9\n" -"yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+\n" -"Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\n" -"A1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7OR\n" -"tvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz\n" -"YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAXxPcW6cTY\n" -"cvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89jxt5\n" -"dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH\n" -"z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA==\n" -"-----END CERTIFICATE-----\n", - -/* VeriSign Class 3 Public Primary Certification Authority - G5 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkG\n" -"A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU\n" -"cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh\n" -"dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQ\n" -"cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcN\n" -"MzYwNzE2MjM1OTU5WjCByjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMu\n" -"MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBW\n" -"ZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp\n" -"U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0g\n" -"RzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8\n" -"RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70PbZmIVYc9g\n" -"DaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ0\n" -"23tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9\n" -"r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MU\n" -"CH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" -"HQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH\n" -"BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u\n" -"Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqGSIb3\n" -"DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+\n" -"X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU\n" -"7qKEKQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMt\n" -"EMze/aiCKm0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7\n" -"MzVIcbidJ4vEZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq\n" -"-----END CERTIFICATE-----\n", - -/* SecureTrust CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYD\n" -"VQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNl\n" -"Y3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UE\n" -"BhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1\n" -"cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7C\n" -"T8rU4niVWJxB4Q2ZQCQXOZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29\n" -"vo6pQT64lO0pGtSO0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZ\n" -"bf2IzIaowW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj\n" -"7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xH\n" -"CzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIE\n" -"Bh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE\n" -"/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYjaHR0cDovL2NybC5zZWN1cmV0cnVz\n" -"dC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDt\n" -"T0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQ\n" -"f2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cp\n" -"rp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS\n" -"CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR3ItHuuG5\n" -"1WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=\n" -"-----END CERTIFICATE-----\n", - -/* Secure Global CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYD\n" -"VQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNl\n" -"Y3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYD\n" -"VQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNl\n" -"Y3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxV\n" -"aQZx5RNoJLNP2MwhR/jxYDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6Mpjh\n" -"HZevj8fcyTiW89sa/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ\n" -"/kG5VacJjnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI\n" -"HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPi\n" -"XB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGC\n" -"NxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9E\n" -"BMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJl\n" -"dHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IB\n" -"AQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQV\n" -"DpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895\n" -"P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY\n" -"iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xcf8LDmBxr\n" -"ThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW\n" -"-----END CERTIFICATE-----\n", - -/* COMODO Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkG\n" -"A1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9y\n" -"ZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZp\n" -"Y2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQsw\n" -"CQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxm\n" -"b3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRp\n" -"ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECL\n" -"i3LjkRv3UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI\n" -"2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7eu\n" -"NJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC\n" -"8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQF\n" -"ZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVIrLsm9wIDAQABo4GOMIGLMB0GA1Ud\n" -"DgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\n" -"AwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9D\n" -"ZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5\n" -"t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv\n" -"IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/RxdMosIG\n" -"lgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmcIGfE\n" -"7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN\n" -"+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==\n" -"-----END CERTIFICATE-----\n", - -/* Network Solutions Certificate Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYD\n" -"VQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO\n" -"ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAw\n" -"WhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1\n" -"dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBB\n" -"dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xG\n" -"zuAnlt7e+foS0zwzc7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQ\n" -"NJIg6nPPOCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl\n" -"mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1\n" -"QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMh\n" -"qxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA\n" -"106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MFIGA1Ud\n" -"HwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25z\n" -"Q2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ot\n" -"t3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVR\n" -"DuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH\n" -"/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3HtvwKeI8lN3\n" -"s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxDydi8\n" -"NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey\n" -"-----END CERTIFICATE-----\n", - -/* COMODO ECC Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UE\n" -"BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEa\n" -"MBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlm\n" -"aWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTEL\n" -"MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2Fs\n" -"Zm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0Mg\n" -"Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmC\n" -"FYX7deSRFtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J\n" -"cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZ\n" -"SBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq\n" -"hkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDmfQjGGoe9GKhzvSbKYAydzpmf\n" -"z1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeAU/7dIOA1mjbRxwG55tzd8/8dLDoW\n" -"V9mSOdY=\n" -"-----END CERTIFICATE-----\n", - -/* Security Communication EV RootCA1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UE\n" -"ChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29t\n" -"bXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlow\n" -"YDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4x\n" -"KjAoBgNVBAsTIVNlY3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZI\n" -"hvcNAQEBBQADggEPADCCAQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1\n" -"OXj/l3X3L+SqawSERMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1\n" -"V4qe70gOzXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5\n" -"bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5\n" -"kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+z\n" -"yRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eCOKyrcWUXdYydVZPmMA4GA1Ud\n" -"DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBn\n" -"XcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRG\n" -"ef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXk\n" -"gKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF7\n" -"5x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O\n" -"VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490\n" -"-----END CERTIFICATE-----\n", - -/* OISTE WISeKey Global Root GA CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkG\n" -"A1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAw\n" -"NTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUg\n" -"V0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5\n" -"NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJp\n" -"Z2h0IChjKSAyMDA1MSIwIAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYD\n" -"VQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEF\n" -"AAOCAQ8AMIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR\n" -"VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSL\n" -"tZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dy\n" -"oJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg4E8HsChWjBgbl0SOid3gF27n\n" -"Ku+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3RLoGbw9ho972WG6xwsRYUC9tguSYB\n" -"BQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+\n" -"rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEB\n" -"AEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VF\n" -"vbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8\n" -"vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXahNVQA7bi\n" -"hKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEYokxS\n" -"dsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=\n" -"-----END CERTIFICATE-----\n", - -/* Certigna */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZS\n" -"MRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMw\n" -"NVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczER\n" -"MA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ\n" -"1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lI\n" -"zw7sebYs5zRLcAglozyHGxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxr\n" -"yIRWijOp5yIVUxbwzBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJb\n" -"zg4ij02Q130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2\n" -"JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0T\n" -"AQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AU\n" -"Gu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlt\n" -"eW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEG\n" -"CWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl\n" -"1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxA\n" -"GYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9q\n" -"cEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w\n" -"t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/QwWyH8EZE0\n" -"vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==\n" -"-----END CERTIFICATE-----\n", - -/* Deutsche Telekom Root CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UE\n" -"ChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRl\n" -"cjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAw\n" -"WhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVs\n" -"ZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1\n" -"dHNjaGUgVGVsZWtvbSBSb290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\n" -"AQCrC6M14IspFLEUha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1c\n" -"Os7TuKhCQN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr\n" -"rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1\n" -"Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFh\n" -"mHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0G\n" -"A1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB\n" -"/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f7\n" -"6Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSY\n" -"SKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juw\n" -"zTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+\n" -"xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mUCm26OWMo\n" -"hpLzGITY+9HPBVZkVw==\n" -"-----END CERTIFICATE-----\n", - -/* Cybertrust Global Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMP\n" -"Q3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2\n" -"MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5j\n" -"MR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC\n" -"AQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO2\n" -"1O1fWLE3TdVJDm71aofW0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2O\n" -"lTEQXO2iLb3VOm2yHLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeW\n" -"P032a7iPt3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz\n" -"FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQID\n" -"AQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2\n" -"CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJs\n" -"aWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8GA1UdIwQYMBaAFLYIew16zKwgTIZW\n" -"Ml7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ej\n" -"hVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24C\n" -"JJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+z\n" -"v+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc\n" -"A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jWWL1WMRJO\n" -"Ecgh4LMRkWXbtKaIOM5V\n" -"-----END CERTIFICATE-----\n", - -/* ePKI Root Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYD\n" -"VQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsM\n" -"IWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0z\n" -"NDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29t\n" -"IENvLiwgTHRkLjEqMCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5\n" -"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U\n" -"82N0ywEhajfqhFAHSyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrB\n" -"p0xtInAhijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X\n" -"DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZr\n" -"xQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ad\n" -"o4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffAsgRFelQArr5T9rXn4fg8ozHS\n" -"qf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ETOxQvdibBjWzwloPn9s9h6PYq2l\n" -"Y9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUa\n" -"dCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+Xk\n" -"wY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3\n" -"pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF\n" -"MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLHClZ87lt4\n" -"DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B01GqZ\n" -"NF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq\n" -"KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnV\n" -"vwdVxrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltab\n" -"rNMdjmEPNXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc\n" -"7b3jajWvY9+rGNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8\n" -"GrBQAuUBo2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS\n" -"/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C\n" -"6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yI\n" -"VMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4\n" -"EZw=\n" -"-----END CERTIFICATE-----\n", - -/* T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYD\n" -"VQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRl\n" -"a25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVz\n" -"YWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0g\n" -"VUVLQUUxIzAhBgNVBAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFU\n" -"w5xCxLBUQUsgVUVLQUUgS8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAt\n" -"IFPDvHLDvG0gMzAeFw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UE\n" -"BhMCVFIxGDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls\n" -"aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBG\n" -"BgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5z\n" -"dGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkx\n" -"SjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xh\n" -"ecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" -"im1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6V\n" -"QIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+81\n" -"8qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw\n" -"lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oLhmUZEdPp\n" -"CSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAdBgNV\n" -"HQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF\n" -"MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTf\n" -"vCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpN\n" -"eBLWrcLTy9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceE\n" -"xh/VS4ESshYhLBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0\n" -"a+IDRM5noN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs\n" -"yZyQ2uypQjyttgI=\n" -"-----END CERTIFICATE-----\n", - -/* certSIGN ROOT CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREw\n" -"DwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQx\n" -"NzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lH\n" -"TjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" -"AQoCggEBALczuX7IJUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oq\n" -"rl0Hj0rDKH/v+yv6efHHrfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsA\n" -"fsT8AzNXDe3i+s5dRdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUo\n" -"Se1b16kQOA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv\n" -"JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNC\n" -"MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPx\n" -"fIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJLjX8+HXd5n9liPRyTMks1zJO\n" -"890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6\n" -"IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KT\n" -"afcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI\n" -"0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5V\n" -"aZVDADlN9u6wWk5JRFRYX0KD\n" -"-----END CERTIFICATE-----\n", - -/* CNNIC ROOT */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwG\n" -"A1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcw\n" -"NDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNO\n" -"TklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LR\n" -"b+1VvG7q6KR5smzDo+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx\n" -"3zkBwRP9SFIhxFXf2tizVHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJ\n" -"MfAw28Mbdim7aXZOV/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPih\n" -"NIaj3XrCGHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN\n" -"v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIA\n" -"BzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsG\n" -"A1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO76bVOxEwDQYJKoZIhvcNAQEF\n" -"BQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMX\n" -"YFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23\n" -"xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftO\n" -"hpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8\n" -"yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=\n" -"-----END CERTIFICATE-----\n", - -/* GeoTrust Primary Certification Authority - G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkG\n" -"A1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdl\n" -"b1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1\n" -"c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAw\n" -"MFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJ\n" -"bmMuMTkwNwYDVQQLEzAoYykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQg\n" -"dXNlIG9ubHkxNjA0BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRo\n" -"b3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz\n" -"+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD6\n" -"14SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeD\n" -"XTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/WJmxsYAQlTlV+fe+/lEjetx3d\n" -"cI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ\n" -"6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB\n" -"/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqG\n" -"SIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTT\n" -"Od8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN\n" -"kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGDAWh9jUGh\n" -"lBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33st/3L\n" -"jWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt\n" -"-----END CERTIFICATE-----\n", - -/* thawte Primary Root CA - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UE\n" -"BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3Rl\n" -"LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmlt\n" -"YXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQsw\n" -"CQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0\n" -"aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3Rl\n" -"IFByaW1hcnkgUm9vdCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFS\n" -"eIf+iha/BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6\n" -"papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/\n" -"MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZI\n" -"zj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3KMqh9HneteY4sPBlcIx/AlTC\n" -"v//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3Krr0TKUQNJ1uo52icEvdYPy5yAlej\n" -"j6EULg==\n" -"-----END CERTIFICATE-----\n", - -/* thawte Primary Root CA - G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkG\n" -"A1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlv\n" -"biBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0g\n" -"Rm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg\n" -"Q0EgLSBHMzAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJV\n" -"UzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZp\n" -"Y2VzIERpdmlzaW9uMTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0\n" -"aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz\n" -"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu\n" -"86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/E\n" -"th9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lfb1+6a4KinVvnSr0eAXLbS3To\n" -"O39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY7CFJXJv2eul/VTV+lmuNk5Mny5K7\n" -"6qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiY\n" -"nODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB\n" -"/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQAD\n" -"ggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW\n" -"oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1but8jLZ8HJ\n" -"nBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC8rZc\n" -"JwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm\n" -"er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A=\n" -"-----END CERTIFICATE-----\n", - -/* GeoTrust Primary Certification Authority - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UE\n" -"BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1Ry\n" -"dXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3Qg\n" -"UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoX\n" -"DTM4MDExODIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMu\n" -"MTkwNwYDVQQLEzAoYykgMjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl\n" -"IG9ubHkxNjA0BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp\n" -"dHkgLSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL\n" -"So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf\n" -"691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" -"AQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSW\n" -"WaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7mndwxHLKgpxgceeHHNgIwOlavmnRs\n" -"9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2npaqBA+K\n" -"-----END CERTIFICATE-----\n", - -/* VeriSign Universal Root Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkG\n" -"A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU\n" -"cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBh\n" -"dXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBD\n" -"ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTla\n" -"MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl\n" -"cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMu\n" -"IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh\n" -"bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" -"MIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbna\n" -"zU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWHH26MfF8WIFFE0XBPV+rjHOPM\n" -"ee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL729fdC4uW/h2KJXwBL38Xd5HVEMkE6\n" -"HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ\n" -"79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQAB\n" -"o4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEw\n" -"X6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs\n" -"exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1UdDgQWBBS2\n" -"d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3Y8xu\n" -"TPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx\n" -"Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahf\n" -"YtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tew\n" -"XDpPaj+PwGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WI\n" -"g0vvBZIGcfK4mJO37M2CYfE45k+XmCpajQ==\n" -"-----END CERTIFICATE-----\n", - -/* VeriSign Class 3 Public Primary Certification Authority - G4 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UE\n" -"BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz\n" -"dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo\n" -"b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt\n" -"YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgw\n" -"MTE4MjM1OTU5WjCByjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8w\n" -"HQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJp\n" -"U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln\n" -"biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQw\n" -"djAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmD\n" -"iWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3vefLK+ymVhAIau2o970ImtTR1Z\n" -"mkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYI\n" -"KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoas\n" -"jY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYw\n" -"HQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgm\n" -"YFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga\n" -"FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==\n" -"-----END CERTIFICATE-----\n", - -/* NetLock Arany (Class Gold) Főtanúsítvány */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTER\n" -"MA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFu\n" -"w7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwws\n" -"TmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjEx\n" -"MTUwODIxWhcNMjgxMjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFw\n" -"ZXN0MRUwEwYDVQQKDAxOZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lh\n" -"ZMOzayAoQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkg\n" -"KENsYXNzIEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" -"MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFtt\n" -"vzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn\n" -"7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5VA1lddkVQZQBr17s9o3x/61k\n" -"/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7\n" -"GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiL\n" -"o0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpn\n" -"k/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ\n" -"5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C\n" -"+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzCbLBQWV2Q\n" -"WzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5KfnaNwUA\n" -"SZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu\n" -"dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=\n" -"-----END CERTIFICATE-----\n", - -/* Staat der Nederlanden Root CA - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwG\n" -"A1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJs\n" -"YW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjEL\n" -"MAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwi\n" -"U3RhYXQgZGVyIE5lZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQAD\n" -"ggIPADCCAgoCggIBAMVZ5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZ\n" -"qhQlEq0i6ABtQ8SpuOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU2\n" -"54DBtvPUZ5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE\n" -"pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV\n" -"3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9\n" -"whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2U\n" -"uIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V6548r6f1CGPqI0GAwJaCgRHOThuVw+\n" -"R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/\n" -"Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymY\n" -"NqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYD\n" -"VR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov\n" -"L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNVHQ8BAf8E\n" -"BAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUAA4IC\n" -"AQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz\n" -"+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUX\n" -"vQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sU\n" -"OlWDuYaNkqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fM\n" -"dWVSSt7wsKfkCpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2t\n" -"UKRXCnxLvJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm\n" -"bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8s\n" -"V4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXq\n" -"ZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOL\n" -"nR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==\n" -"-----END CERTIFICATE-----\n", - -/* Hongkong Post Root CA 1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNV\n" -"BAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4X\n" -"DTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT\n" -"DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjAN\n" -"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSS\n" -"HSL22oVyaf7XPwnU3ZG1ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8g\n" -"PW2iNr4joLFutbEnPzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7j\n" -"EAaPIpjhZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9\n" -"nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208\n" -"o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQE\n" -"AwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsCmEEIjEy82tvuJxuC52pF7BaL\n" -"T4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37piol7Yutmcn1KZJ/RyTZXaeQi/cImya\n" -"T/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgC\n" -"IDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES\n" -"7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4Jx\n" -"HYB0yvbiAmvZWg==\n" -"-----END CERTIFICATE-----\n", - -/* SecureSign RootCA11 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UE\n" -"ChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJl\n" -"U2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNV\n" -"BAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRww\n" -"GgYDVQQDExNTZWN1cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" -"CgKCAQEA/XeqpRyQBTvLTJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1y\n" -"fIw/XwFndBWW4wI8h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyK\n" -"yiyhFTOVMdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9\n" -"UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V\n" -"1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsCh8U+iQIDAQABo0Iw\n" -"QDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud\n" -"EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKChOBZmLqdWHyGcBvod7bkixTgm2E5P\n" -"7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI\n" -"6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAY\n" -"ga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR\n" -"7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN\n" -"QSdJQO7e5iNEOdyhIta6A/I=\n" -"-----END CERTIFICATE-----\n", - -/* ACEDICOM Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNF\n" -"RElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVT\n" -"MB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00g\n" -"Um9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjAN\n" -"BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7\n" -"w2rbYgIB8hiGtXxaOLHkWLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auOD\n" -"AKgrLlUTY4HKRxx7XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW1\n" -"0W2ZEi5PGrjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK\n" -"t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ\n" -"1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQA\n" -"twBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQUfecyuB+81fFOvW8XAjnXDpVC\n" -"OscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTu\n" -"tYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27\n" -"k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MC\n" -"AwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB\n" -"53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw\n" -"RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNvbS5lZGlj\n" -"b21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqgaHtP\n" -"kl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP\n" -"eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH\n" -"1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf\n" -"8seACQl1ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7\n" -"tq3PgbUhh8oIKiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtP\n" -"F2Y9fwsZo5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6\n" -"zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQY\n" -"XlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyl\n" -"eW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+\n" -"KzgHVZhepA==\n" -"-----END CERTIFICATE-----\n", - -/* Microsec e-Szigno Root CA 2009 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJI\n" -"VTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMM\n" -"Hk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0Bl\n" -"LXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQG\n" -"EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNV\n" -"BAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5m\n" -"b0BlLXN6aWduby5odTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG\n" -"2KfgQvvPkd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc\n" -"cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDH\n" -"QWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqp\n" -"GrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV\n" -"87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQF\n" -"MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAf\n" -"BgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3pp\n" -"Z25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5Dw\n" -"pL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk\n" -"ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775tyERzAMB\n" -"VnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02yULy\n" -"Mtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi\n" -"LXpUq3DDfSJlgnCW\n" -"-----END CERTIFICATE-----\n", - -/* GlobalSign Root CA - R3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMX\n" -"R2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMT\n" -"Ckdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQL\n" -"ExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UE\n" -"AxMKR2xvYmFsU2lnbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5Bngi\n" -"FvXAg7aEyiie/QV2EcWtiHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0M\n" -"K66X17YUhhB5uzsTgHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL\n" -"0gRgykmmKPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd\n" -"QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613\n" -"t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQD\n" -"AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0G\n" -"CSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7lgAJQayzE4aGKAczymvmdLm6AC2u\n" -"pArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdW\n" -"PoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0\n" -"095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJr\n" -"lAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH\n" -"WD9f\n" -"-----END CERTIFICATE-----\n", - -/* Autoridad de Certificacion Firmaprofesional CIF A62634068 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMx\n" -"QjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwg\n" -"Q0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNV\n" -"BAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zl\n" -"c2lvbmFsIENJRiBBNjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDK\n" -"lmuO6vj78aI14H9M2uDDUtd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOS\n" -"L/UR5GLXMnE42QQMcas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9\n" -"qFD0sefGL9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i\n" -"NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2\n" -"f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44\n" -"I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCyZ/QYFpM6/EfY0XiWMR+6Kwxf\n" -"XZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy\n" -"9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF\n" -"8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mV\n" -"BngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8C\n" -"AQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD\n" -"VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZpcm1hcHJv\n" -"ZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAAbABh\n" -"ACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx\n" -"ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+\n" -"xDLx51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5x\n" -"hOW1//qkR71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5\n" -"eTSSPi5E6PaPT481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5Fl\n" -"ClrD2VQS3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k\n" -"SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2\n" -"gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYD\n" -"NEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhr\n" -"JKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIyS\n" -"xZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V\n" -"-----END CERTIFICATE-----\n", - -/* Izenpe.com */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYD\n" -"VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcN\n" -"MDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwL\n" -"SVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC\n" -"DwAwggIKAoICAQDJ03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5Tz\n" -"cqQsRNiekpsUOqHnJJAKClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpz\n" -"bm3benhB6QiIEn6HLmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJ\n" -"GjMxCrFXuaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD\n" -"yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8\n" -"hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG7\n" -"0t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyNBjNaooXlkDWgYlwWTvDjovoD\n" -"GrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+0rnq49qlw0dpEuDb8PYZi+17cNcC\n" -"1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQD\n" -"fo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNV\n" -"HREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4g\n" -"LSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB\n" -"BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAxMCBWaXRv\n" -"cmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\n" -"FB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l\n" -"Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9\n" -"fbgakEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJO\n" -"ubv5vr8qhT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m\n" -"5hzkQiCeR7Csg1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Py\n" -"e6kfLqCTVyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk\n" -"LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqt\n" -"ujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZ\n" -"pR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6i\n" -"SNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE4\n" -"1V4tC5h9Pmzb/CaIxw==\n" -"-----END CERTIFICATE-----\n", - -/* Chambers of Commerce Root - 2008 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJF\n" -"VTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZp\n" -"cm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1l\n" -"cmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4\n" -"MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYD\n" -"VQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t\n" -"L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEg\n" -"Uy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G\n" -"CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+\n" -"JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCG\n" -"hSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072QDuKZoRuGDtqaCrsLYVAGUvGe\n" -"f3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL\n" -"+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9\n" -"ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esH\n" -"nFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2w\n" -"sWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5\n" -"Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhjya6BXBg1\n" -"4JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2EQID\n" -"AQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI\n" -"G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4x\n" -"CzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQg\n" -"d3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNV\n" -"BAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2Ug\n" -"Um9vdCAtIDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV\n" -"HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI\n" -"hvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I\n" -"6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0\n" -"/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk1\n" -"8pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rc\n" -"f+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+K\n" -"MjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb\n" -"0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq\n" -"jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1DefhiYtUU7\n" -"9nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRgOGcEMeyP84LG3rlV\n" -"8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ\n" -"-----END CERTIFICATE-----\n", - -/* Global Chambersign Root - 2008 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJF\n" -"VTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZp\n" -"cm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1l\n" -"cmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAe\n" -"Fw0wODA4MDExMjMxNDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UE\n" -"BxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9h\n" -"ZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu\n" -"QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI\n" -"hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwG\n" -"Mi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7G706tcuto8xEpw2u\n" -"IRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBA\n" -"spjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/\n" -"LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkB\n" -"fSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9\n" -"kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al\n" -"/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r\n" -"6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9JhwZG7SMA0\n" -"j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMBAAGj\n" -"ggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT\n" -"BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkG\n" -"A1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cu\n" -"Y2FtZXJmaXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMS\n" -"QUMgQ2FtZXJmaXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAt\n" -"IDIwMDiCCQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow\n" -"KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEF\n" -"BQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv\n" -"4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWC\n" -"koQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIa\n" -"dJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJ\n" -"jUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uY\n" -"lDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3L\n" -"m6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso\n" -"M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4gev8CSlDQb\n" -"4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z09gwzxMNTxXJhLyn\n" -"SC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B\n" -"-----END CERTIFICATE-----\n", - -/* Go Daddy Root Certificate Authority - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNV\n" -"BAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29t\n" -"LCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAt\n" -"IEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAw\n" -"DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5\n" -"LmNvbSwgSW5jLjExMC8GA1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3Jp\n" -"dHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3\n" -"gElY6SKDE6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH\n" -"/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLI\n" -"jWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6\n" -"gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGRtDtwKj9useiciAF9n9T521Nt\n" -"YJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO\n" -"BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3\n" -"DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC\n" -"2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95\n" -"kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo\n" -"2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPOLPAvTK33\n" -"sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1\n" -"-----END CERTIFICATE-----\n", - -/* Starfield Root Certificate Authority - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNV\n" -"BAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBU\n" -"ZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRl\n" -"IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJ\n" -"BgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYD\n" -"VQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQg\n" -"Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" -"ADCCAQoCggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg\n" -"nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSu\n" -"S/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhh\n" -"dM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dNdloedl40wOiWVpmKs/B/pM29\n" -"3DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbUJtQIBFnQmA4O5t78w+wfkPECAwEA\n" -"AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n\n" -"2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWU\n" -"XuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox\n" -"9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg\n" -"8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/KpL/QlwVK\n" -"vOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZc2T5\n" -"NnReJaH1ZgUufzkVqSr7UIuOhWn0\n" -"-----END CERTIFICATE-----\n", - -/* Starfield Services Root Certificate Authority - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNV\n" -"BAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBU\n" -"ZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENl\n" -"cnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1\n" -"OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNk\n" -"YWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJT\n" -"dGFyZmllbGQgU2VydmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw\n" -"DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p\n" -"OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2\n" -"dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS\n" -"7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufehRhJfGZOozptqbXuNC66DQO4\n" -"M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFBrMnUVN+HL8cisibMn1lUaJ/8viov\n" -"xFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" -"AQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBL\n" -"NqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynV\n" -"v/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z\n" -"qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkdiEDPfUYd\n" -"/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jzaYyWf\n" -"/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6\n" -"-----END CERTIFICATE-----\n", - -/* AffirmTrust Commercial */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMx\n" -"FDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFs\n" -"MB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNV\n" -"BAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjAN\n" -"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTW\n" -"zsO3qyxPxkEylFf6EqdbDuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U\n" -"6Mje+SJIZMblq8Yrba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNA\n" -"FxHUdPALMeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1\n" -"yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1J\n" -"dX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8w\n" -"DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAFis\n" -"9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M\n" -"06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1Ua\n" -"ADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjip\n" -"M1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclN\n" -"msxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=\n" -"-----END CERTIFICATE-----\n", - -/* AffirmTrust Networking */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMx\n" -"FDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5n\n" -"MB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNV\n" -"BAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjAN\n" -"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWRE\n" -"ZY9nZOIG41w3SfYvm4SEHi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ\n" -"/Ls6rnla1fTWcbuakCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXL\n" -"viRmVSRLQESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp\n" -"6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKB\n" -"Nv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0w\n" -"DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAIlX\n" -"shZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t\n" -"3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA\n" -"3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzek\n" -"ujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfx\n" -"ojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=\n" -"-----END CERTIFICATE-----\n", - -/* AffirmTrust Premium */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMx\n" -"FDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4X\n" -"DTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoM\n" -"C0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG\n" -"9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64t\n" -"b+eT2TZwamjPjlGjhVtnBKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/\n" -"0qRY7iZNyaqoe5rZ+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/\n" -"K+k8rNrSs8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5\n" -"HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua\n" -"2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/\n" -"9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+SqHZGnEJlPqQewQcDWkYtuJfz\n" -"t9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m\n" -"6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKP\n" -"KrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNC\n" -"MEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYD\n" -"VR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2\n" -"KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMgNt58D2kT\n" -"iKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC6C1Y\n" -"91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S\n" -"L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQ\n" -"wUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFo\n" -"oC8k4gmVBtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5Yw\n" -"H2AG7hsj/oFgIxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/\n" -"qzWaVYa8GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO\n" -"RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAlo\n" -"GRwYQw==\n" -"-----END CERTIFICATE-----\n", - -/* AffirmTrust Premium ECC */ -"-----BEGIN CERTIFICATE-----\n" -"MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDAS\n" -"BgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAe\n" -"Fw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQK\n" -"DAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcq\n" -"hkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQU\n" -"X+iOGasvLkjmrBhDeKzQN8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR\n" -"4ptlKymjQjBAMB0GA1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTAD\n" -"AQH/MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs\n" -"aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9C\n" -"a/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==\n" -"-----END CERTIFICATE-----\n", - -/* Certum Trusted Network CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYD\n" -"VQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlm\n" -"aWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0Ew\n" -"HhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UE\n" -"ChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmlj\n" -"YXRpb24gQXV0aG9yaXR5MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIB\n" -"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/\n" -"91sts1rHUV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM\n" -"TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmt\n" -"VSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM\n" -"+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8xAcPs3hEtF10fuFDRXhmnad4H\n" -"MyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQI\n" -"ds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEB\n" -"AKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsi\n" -"srCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv\n" -"94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY\n" -"VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI03YnnZot\n" -"BqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=\n" -"-----END CERTIFICATE-----\n", - -/* Certinomis - Autorité Racine */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UE\n" -"ChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRp\n" -"bm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1\n" -"OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIg\n" -"NDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIw\n" -"DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2\n" -"DpdUzZlMGvE5x4jYF1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOr\n" -"J3NqDi5N8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe\n" -"rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0\n" -"K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb\n" -"4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6\n" -"DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTClrLooyPCXQP8w9PlfMl1I9f09bze\n" -"5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGo\n" -"OBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75\n" -"mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29Ynf\n" -"AgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN\n" -"jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJKoZIhvcN\n" -"AQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/xWqnd\n" -"IlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva\n" -"R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCX\n" -"wH40nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQR\n" -"E7rWhh1BCxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPb\n" -"VFsDbVRfsbjvJL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJ\n" -"Oqxp9YDG5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq\n" -"pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XS\n" -"APCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ\n" -"8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5\n" -"-----END CERTIFICATE-----\n", - -/* TWCA Root Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UE\n" -"CgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2Vy\n" -"dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBf\n" -"MQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSow\n" -"KAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3\n" -"DQEBAQUAA4IBDwAwggEKAoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bi\n" -"hSX0NXIP+FPQQeFEAcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQ\n" -"sIBct+HHK3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX\n" -"RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJb\n" -"KdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxY\n" -"A7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\n" -"DgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG9w0BAQUFAAOCAQEAPNV3PdrfibqH\n" -"DAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqG\n" -"fczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4g\n" -"umlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKu\n" -"D8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ\n" -"YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==\n" -"-----END CERTIFICATE-----\n", - -/* Security Communication RootCA2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UE\n" -"ChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29t\n" -"bXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTEL\n" -"MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAl\n" -"BgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEB\n" -"BQADggEPADCCAQoCggEBANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz3\n" -"35c9S672XewhtUGrzbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonC\n" -"v/Q4EpVMVAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ\n" -"hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhw\n" -"Hyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCca\n" -"dfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQE\n" -"AwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBMOqNErLlFsceTfsgL\n" -"CkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8\n" -"AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g6\n" -"9ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR\n" -"50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/\n" -"SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03\n" -"-----END CERTIFICATE-----\n", - -/* EC-ACC */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkG\n" -"A1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChO\n" -"SUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNh\n" -"Y2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAo\n" -"YykwMzE1MDMGA1UECxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRh\n" -"bGFuZXMxDzANBgNVBAMTBkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTla\n" -"MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZp\n" -"Y2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD\n" -"ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3Zl\n" -"cmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNh\n" -"Y2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" -"MIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfC\n" -"Q2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6\n" -"PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST\n" -"2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n\n" -"5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB\n" -"o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8EBTADAQH/\n" -"MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYDVR0g\n" -"BHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0\n" -"Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0\n" -"Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/\n" -"sXE7zDkJlF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPp\n" -"qojlNcAZQmNaAl6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7Awa\n" -"boMMPOhyRp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS\n" -"Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6\n" -"Un/10asIbvPuW/mIPX64b24D5EI=\n" -"-----END CERTIFICATE-----\n", - -/* Hellenic Academic and Research Institutions RootCA 2011 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNV\n" -"BAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4g\n" -"QXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5z\n" -"dGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1Mlow\n" -"gZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFy\n" -"Y2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNh\n" -"ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZI\n" -"hvcNAQEBBQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz\n" -"dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0a\n" -"e50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsm\n" -"LIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD75O6aRXxYp2fmTmCobd0LovU\n" -"xQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH3N6sQWRstBmbAmNtJGSPRLIl6s5d\n" -"dAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNV\n" -"HQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUw\n" -"BoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3\n" -"DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p\n" -"6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8TqBTnbI6\n" -"nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD/md9\n" -"zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N\n" -"7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4\n" -"-----END CERTIFICATE-----\n", - -/* Actalis Authentication Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQx\n" -"DjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEn\n" -"MCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIw\n" -"MloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYD\n" -"VQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRo\n" -"ZW50aWNhdGlvbiBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bE\n" -"pSmkLO/lGMWwUKNvUTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW\n" -"1V8IbInX4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9\n" -"KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63\n" -"igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8\n" -"oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RH\n" -"ILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8lEfKXGkJh90qX6IuxEAf6ZYGyojnP\n" -"9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4\n" -"RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U\n" -"5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/j\n" -"Vo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz\n" -"ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbtifN7OHCU\n" -"yQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyIWOYd\n" -"iPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0\n" -"JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjR\n" -"lwKxK3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2ryk\n" -"OLpn7VU+Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2T\n" -"lf05fbsq4/aC4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst\n" -"842/6+OkfcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R\n" -"K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VL\n" -"kn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDz\n" -"zFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7y\n" -"FIrM6bV8+2ydDKXhlg==\n" -"-----END CERTIFICATE-----\n", - -/* Trustis FPS Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYD\n" -"VQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQ\n" -"UyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMC\n" -"R0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9v\n" -"dCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2\n" -"mfRC6qc+gIMPpqdZh8mQRUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkc\n" -"hU59j9WvezX2fihHiTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE\n" -"2gfmHhjjvSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA\n" -"0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L6\n" -"8MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV\n" -"HSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuy\n" -"ZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2cGE+esCu8jowU/yyg2kdbw++BLa8F\n" -"6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5B\n" -"uO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWh\n" -"PBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/\n" -"rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN\n" -"ZetX2fNXlrtIzYE=\n" -"-----END CERTIFICATE-----\n", - -/* StartCom Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UE\n" -"ChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUg\n" -"U2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcN\n" -"MDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN\n" -"U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2ln\n" -"bmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0G\n" -"CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul3\n" -"8kMKogZkpMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf\n" -"OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYc\n" -"cjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d\n" -"5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9\n" -"bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z6+hsTXBbKWWc3apdzK8BMewM69KN\n" -"6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHu\n" -"EhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZP\n" -"V/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOz\n" -"EmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID\n" -"AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\n" -"FE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQQa7y\n" -"MIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0\n" -"dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93\n" -"d3cuc3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0\n" -"YXJ0IENvbW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0\n" -"eSwgcmVhZCB0aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENv\n" -"bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93\n" -"d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG\n" -"+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkq\n" -"hkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqn\n" -"UCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/Jx\n" -"XrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myzieb\n" -"iMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MN\n" -"q6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww\n" -"2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK\n" -"1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLmKhQxw4Ut\n" -"jJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuEJnHEhV5xJMqlG2zY\n" -"YdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdibD4x3TrVoivJs\n" -"9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8=\n" -"-----END CERTIFICATE-----\n", - -/* StartCom Certification Authority G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UE\n" -"ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRo\n" -"b3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJ\n" -"TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNh\n" -"dGlvbiBBdXRob3JpdHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZb\n" -"B7cgNr2Cu+EWIAOVeq8Oo1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe\n" -"3ikj1AENoBB5uNsDvfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSC\n" -"b0AgJnooD/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/\n" -"Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr\n" -"7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq\n" -"42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxNnw3h3Kq74W4a7I/htkxNeXJd\n" -"FzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ\n" -"85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0s\n" -"AA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPI\n" -"N93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud\n" -"DwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL\n" -"BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K2s06Ctg6\n" -"Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbkJ4kd\n" -"+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+\n" -"JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w\n" -"6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9\n" -"mk47EDTcnIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1\n" -"dZxAF7L+/XldblhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6M\n" -"anY5Ka5lIxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo\n" -"hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjR\n" -"kcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI\n" -"-----END CERTIFICATE-----\n", - -/* Buypass Class 2 Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UE\n" -"CgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290\n" -"IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAb\n" -"BgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIg\n" -"Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1\n" -"aeTuMgHbo4Yf5FkNuud1g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXl\n" -"zwx87vFKu3MwZfPVL4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FV\n" -"M5I+GC911K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx\n" -"MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfg\n" -"olXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkBarcNuAeBfos4Gzjm\n" -"CleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T\n" -"3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1L\n" -"PC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIH\n" -"ZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVe\n" -"e7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+Bi\n" -"koL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h\n" -"9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462sA20ucS6v\n" -"xOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EIosHs\n" -"Hdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S\n" -"aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlq\n" -"YLYdDnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6\n" -"OBE1/yWDLfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6w\n" -"pJ9qzo6ysmD0oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYK\n" -"beaP4NK75t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h\n" -"3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv\n" -"4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=\n" -"-----END CERTIFICATE-----\n", - -/* Buypass Class 3 Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UE\n" -"CgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290\n" -"IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAb\n" -"BgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMg\n" -"Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEG\n" -"Mnqb8RB2uACatVI2zSRHsJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fk\n" -"oF0LXOBXByow9c3EN3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOo\n" -"TyrvYLs9tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX\n" -"0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux\n" -"9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6a\n" -"ny2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5\n" -"GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon\n" -"74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3i\n" -"iZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFM\n" -"OVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/l\n" -"b+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj\n" -"QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdVcSQy9sgL\n" -"8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+GuIAe\n" -"qcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG\n" -"Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshA\n" -"pqr8ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjEN\n" -"SoYc6+I2KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr1\n" -"8okmAWiDSKIz6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2X\n" -"cEQNtg413OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD\n" -"u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN\n" -"12TyUb7mqqta6THuBrxzvxNiCp/HuZc=\n" -"-----END CERTIFICATE-----\n", - -/* T-TeleSec GlobalRoot Class 3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNV\n" -"BAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lz\n" -"dGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNz\n" -"IDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzAp\n" -"BgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQt\n" -"U3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENs\n" -"YXNzIDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3Z\n" -"JNW4t/zN8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/\n" -"RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys5\n" -"2qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HM\n" -"VDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6\n" -"tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD\n" -"VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0B\n" -"AQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ\n" -"85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/\n" -"vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT\n" -"91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuImle9eiPZaG\n" -"zPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==\n" -"-----END CERTIFICATE-----\n", - -/* EE Certification Centre Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYD\n" -"VQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwf\n" -"RUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNr\n" -"LmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVF\n" -"MSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0\n" -"aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEi\n" -"MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLq\n" -"I9iroWUyeuuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO\n" -"bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajm\n" -"ofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAd\n" -"TX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE1CV2yreN1x5KZmTNXMWcg+HC\n" -"CIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E\n" -"BAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUF\n" -"BwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkw\n" -"DQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQi\n" -"ZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG\n" -"E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5uuSlNDUmJ\n" -"EYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU3j2L\n" -"rTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM\n" -"dcGWxZ0=\n" -"-----END CERTIFICATE-----\n", - -/* TURKTRUST Certificate Services Provider Root 2007 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVT\n" -"VCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQG\n" -"EwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0\n" -"acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJh\n" -"bMSxayAyMDA3MB4XDTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMM\n" -"NlTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPE\n" -"sTELMAkGA1UEBhMCVFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBC\n" -"aWxnaSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F\n" -"ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3\n" -"PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40\n" -"IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA\n" -"1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtyaKhUG9qPw9ODHFNRRf8+0222vR5YX\n" -"m3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsif\n" -"LfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0O\n" -"BBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD\n" -"AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW\n" -"AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+IaE1KBiY3\n" -"iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OOXl0X\n" -"rRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb\n" -"BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+M\n" -"mNAKpoRq0Tl9\n" -"-----END CERTIFICATE-----\n", - -/* D-TRUST Root Class 3 CA 2 2009 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYD\n" -"VQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIg\n" -"MjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUw\n" -"EwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENB\n" -"IDIgMjAwOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/W\n" -"UEWJNTrGa9v+2wBoqOADER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23D\n" -"EE0NkVJD2IfgXU42tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/\n" -"RcPHAY9RySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM\n" -"lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8\n" -"gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMB\n" -"Af8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYD\n" -"VR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRS\n" -"VVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21i\n" -"SCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10\n" -"cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZI\n" -"hvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni\n" -"acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0o3/U37CY\n" -"Aqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEVdT1B\n" -"/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph\n" -"X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=\n" -"-----END CERTIFICATE-----\n", - -/* D-TRUST Root Class 3 CA 2 EV 2009 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYD\n" -"VQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIg\n" -"RVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRF\n" -"MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAz\n" -"IENBIDIgRVYgMjAwOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3\n" -"MKCOvXwEz75ivJn9gpfSegpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut\n" -"8Mxk2og+KbgPCdM03TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsT\n" -"l28So/6ZqQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR\n" -"p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDY\n" -"D8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB\n" -"/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9NteaHNxnMA4GA1UdDwEB/wQEAwIB\n" -"BjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0\n" -"L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89\n" -"RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBo\n" -"dHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2\n" -"XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp\n" -"3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05nsKtjHEh\n" -"8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lXANtu\n" -"2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA\n" -"NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjm\n" -"JuVvw9y4AyHqnxbxLFS1\n" -"-----END CERTIFICATE-----\n", - -/* PSCProcert */ -"-----BEGIN CERTIFICATE-----\n" -"MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRh\n" -"ZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQG\n" -"EwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQG\n" -"A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMw\n" -"QQYDVQQLEzpTdXBlcmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9u\n" -"IEVsZWN0cm9uaWNhMSUwIwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4X\n" -"DTEwMTIyODE2NTEwMFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRh\n" -"Y3RvQHByb2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx\n" -"KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMt\n" -"U2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQG\n" -"EwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\n" -"ggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5P\n" -"p6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6V\n" -"zbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0v\n" -"luYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg\n" -"353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2\n" -"0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH0quhJZb2\n" -"5uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqUtd+t\n" -"FjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw\n" -"Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggm\n" -"osvpr2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8E\n" -"CDAGAQH/AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYt\n" -"Ry0yMDAwNDAzNi0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSC\n" -"AUcwggFDgBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0\n" -"b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJ\n" -"BgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFs\n" -"MTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25p\n" -"Y2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmlj\n" -"YWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2Iu\n" -"dmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCG\n" -"XgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8E\n" -"bzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t\n" -"UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2NlcnRlLmdv\n" -"Yi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vY3NwLnN1c2NlcnRl\n" -"LmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5odHRwOi8vd3d3\n" -"LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4SqT96QxtGG\n" -"cSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+m\n" -"vTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q\n" -"uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWu\n" -"q2w1n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm\n" -"pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FT\n" -"vZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRK\n" -"hqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0\n" -"R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9o\n" -"hri4zspV4KuxPX+Y1zMOWj3YeMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1\n" -"P93+hvS84Bpxs2Km\n" -"-----END CERTIFICATE-----\n", - -/* China Internet Network Information Center EV Certificates Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAw\n" -"BgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYD\n" -"VQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0\n" -"aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYD\n" -"VQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBD\n" -"ZW50ZXIxRzBFBgNVBAMMPkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2Vu\n" -"dGVyIEVWIENlcnRpZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" -"AQEAm35z7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//\n" -"DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW\n" -"9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdp\n" -"xWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmP\n" -"MowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC7c+WXmPbqOY4twIDAQABo2MwYTAf\n" -"BgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud\n" -"DwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEF\n" -"BQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/G\n" -"VhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB\n" -"echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4WsZkJHwlgk\n" -"meHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U05K2J\n" -"RVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8=\n" -"-----END CERTIFICATE-----\n", - -/* Swisscom Root CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYD\n" -"VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNh\n" -"dGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4\n" -"MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTEl\n" -"MCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Nj\n" -"b20gUm9vdCBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ\n" -"6HJaI2nbeHCOFvErjw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J\n" -"2HzFZ++r0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f\n" -"2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvL\n" -"P+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK7\n" -"5MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTAtukxGggo5WDDH8SQjhBiYEQN\n" -"7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLa\n" -"dkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+H\n" -"io5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV\n" -"4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yP\n" -"hOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw\n" -"FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0OBBYEFE0m\n" -"ICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8TwuMA0G\n" -"CSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO\n" -"v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2l\n" -"ejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX\n" -"+Ave5XLzo9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4Dooq\n" -"zgB53MBfGWcsa0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/P\n" -"s/8XciATwoCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n\n" -"Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07\n" -"qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlc\n" -"CxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col\n" -"49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5wSsSnqaeG8XmDtkx2Q==\n" -"-----END CERTIFICATE-----\n", - -/* Swisscom Root EV CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkG\n" -"A1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmlj\n" -"YXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0\n" -"MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Nj\n" -"b20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3\n" -"aXNzY29tIFJvb3QgRVYgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3\n" -"HS9X6lds93BdY7BxUglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaV\n" -"ylvNwSqD1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH\n" -"oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeF\n" -"GHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin\n" -"5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQvidm5y8kDnftslFGXEBuGCxob\n" -"P/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAGalNgHa/2+2m8atwBz735j9m9W8E6\n" -"X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqD\n" -"J2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb\n" -"7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSna\n" -"Fp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G\n" -"A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgEDMB0GA1Ud\n" -"DgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbBbkUe\n" -"88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL\n" -"j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTw\n" -"kLbUwp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/\n" -"UOGED1V7XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK\n" -"4/HsGOV1timH59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFh\n" -"JpiTt3tm7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S\n" -"nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZ\n" -"N5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwy\n" -"VcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FH\n" -"ZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc=\n" -"-----END CERTIFICATE-----\n", - -/* CA Disig Root R1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNL\n" -"MRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBD\n" -"QSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkG\n" -"A1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAX\n" -"BgNVBAMTEENBIERpc2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" -"AQCqw3j33Jijp1pedxiy3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXK\n" -"E5Pn7cZ3Xza1lK/oOI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMy\n" -"EtztDK3AfQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe\n" -"IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7\n" -"JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3\n" -"Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKjrckWtc7dXpl4fho5frLABaTA\n" -"gqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0e\n" -"RWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmv\n" -"TiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDy\n" -"pVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\n" -"MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI\n" -"hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNRxVgYZk2C\n" -"2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9BLxyE\n" -"04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM\n" -"CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK\n" -"5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGv\n" -"xdpHyN85YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHC\n" -"gWzN4funodKSds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQ\n" -"jX2v3wvkF7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF\n" -"a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn\n" -"6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL\n" -"-----END CERTIFICATE-----\n", - -/* CA Disig Root R2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNL\n" -"MRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBD\n" -"QSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkG\n" -"A1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAX\n" -"BgNVBAMTEENBIERpc2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" -"AQCio8QACdaFXS1tFPbCw3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9\n" -"vgMsRfYvZNSrXaNHPWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwA\n" -"FjxfGs3Ix2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe\n" -"QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4k\n" -"LlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SO\n" -"fW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912H9AZdugsBbPWnDTYltxhh5EF\n" -"5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYEkoopKW1rOhzndX0CcQ7zwOe9yxnd\n" -"nWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhb\n" -"hZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6IN\n" -"fPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\n" -"MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI\n" -"hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFMtCQSin1t\n" -"ERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVVsRHF\n" -"qQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je\n" -"dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QO\n" -"y7W81k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kH\n" -"bA7v/zjxmHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOG\n" -"dGSVyCh13x01utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+\n" -"boE+18DrG5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os\n" -"zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3v\n" -"QCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL\n" -"-----END CERTIFICATE-----\n", - -/* ACCVRAIZ1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUND\n" -"VlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAe\n" -"Fw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQ\n" -"MA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqG\n" -"SIb3DQEBAQUAA4ICDwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPf\n" -"mt4ftVTdFXxpNRFvu8gMjmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM38\n" -"02/J+Nq2DoLSRYWoG2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkX\n" -"hBilyNpAlHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr\n" -"IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJ\n" -"cRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eHk6fGioozl2A3ED6X\n" -"Pm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/474KElB0iryYl0/wiPgL/AlmXz\n" -"7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE\n" -"3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbh\n" -"dQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQAB\n" -"o4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3Yu\n" -"ZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG\n" -"AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2VuqB5TbM\n" -"jB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyMHj+9\n" -"MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA\n" -"QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAA\n" -"UgBhAO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUA\n" -"IABUAGUAYwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4A\n" -"IABFAGwAZQBjAHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUA\n" -"KQAuACAAQwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA\n" -"czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUG\n" -"A1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3Mv\n" -"Y2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREE\n" -"EDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7Uwo\n" -"ZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTK\n" -"FpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/\n" -"+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA\n" -"9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms\n" -"tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH7ehVRE2I\n" -"9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5hI6zppSSMEYCUWqKi\n" -"uUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1XgNce4hL60Xc16\n" -"gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew\n" -"+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbR\n" -"D0tVNEYqi4Y7\n" -"-----END CERTIFICATE-----\n", - -/* TWCA Global Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNV\n" -"BAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwg\n" -"Um9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRX\n" -"MRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0Eg\n" -"R2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zE\n" -"booh745NnHEKH1Jw7W2CnJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvV\n" -"avKOZsTuKwEHktSz0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XT\n" -"P3VfKfChMBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH\n" -"zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWof\n" -"wpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/\n" -"T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6m\n" -"OL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/pyJV/v1WRBXrPPRXAb94JlAGD1zQb\n" -"zECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJr\n" -"nu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53\n" -"L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\n" -"BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL\n" -"1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsnLhpNgb+E\n" -"1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M8VeG\n" -"TslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg\n" -"/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRd\n" -"gFlglPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92\n" -"a6O2JryPA9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/Zuepi\n" -"iI7E8UuDEq3mi4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZz\n" -"JBPqpK5jwa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz\n" -"aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=\n" -"-----END CERTIFICATE-----\n", - -/* TeliaSonera Root CA v1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIG\n" -"A1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcN\n" -"MDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEf\n" -"MB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIP\n" -"ADCCAgoCggIBAMK+6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3\n" -"t+XmfHnqjLWCi65ItqwA3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq\n" -"/t75rH2D+1665I+XZ75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1\n" -"jF3oI7x+/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs\n" -"81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAg\n" -"HNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzT\n" -"jU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMusDor8zagrC/kb2HCUQk5PotT\n" -"ubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7Rc\n" -"We/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUB\n" -"iJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB\n" -"/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjAN\n" -"BgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl\n" -"dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx0GtnLLCo\n" -"4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfWpb/I\n" -"mWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV\n" -"G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KF\n" -"dSpcc41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrE\n" -"gUy7onOTJsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQ\n" -"mz1wHiRszYd2qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfuj\n" -"uLpwQMcnHL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx\n" -"SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=\n" -"-----END CERTIFICATE-----\n", - -/* E-Tugra Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRS\n" -"MQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtu\n" -"b2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlm\n" -"aWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9y\n" -"aXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8w\n" -"DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xv\n" -"amlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWth\n" -"c3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5\n" -"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq98\n" -"99SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0b\n" -"QNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSFQ9OArqGIW66z6l7LFpp3RMih\n" -"9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+gElIwcxmOj+GMB6LDu0rw6h8VqO4l\n" -"zKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3\n" -"fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2o\n" -"MoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QO\n" -"XVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8\n" -"zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+LznrFpct1pH\n" -"XFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5dUyQ\n" -"5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB\n" -"/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQD\n" -"AgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd\n" -"0dCrfOAKkEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/\n" -"u6Au/U5Mh/jOXKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1\n" -"Q9Jauz1c77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3\n" -"+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5\n" -"TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4\n" -"R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDY\n" -"wKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186\n" -"zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9\n" -"I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==\n" -"-----END CERTIFICATE-----\n", - -/* T-TeleSec GlobalRoot Class 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNV\n" -"BAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lz\n" -"dGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNz\n" -"IDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzAp\n" -"BgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQt\n" -"U3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENs\n" -"YXNzIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl\n" -"82hVYAUdAqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC\n" -"FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcx\n" -"lkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1q\n" -"lVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZwI18gfNycJ5v/hqO2V81xrJv\n" -"NHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD\n" -"VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0B\n" -"AQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSp\n" -"p+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joi\n" -"fsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6\n" -"g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN9noHV8ci\n" -"gwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==\n" -"-----END CERTIFICATE-----\n", - -/* Atos TrustedRoot 2011 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRv\n" -"cyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3\n" -"MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3Qg\n" -"MjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IB\n" -"DwAwggEKAoIBAQCVhTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI41\n" -"9KkM/IL9bcFyYie96mvr54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+\n" -"yj5vdHLqqjAqc2K+SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFs\n" -"Q/H3NYkQ4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L\n" -"cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMB\n" -"AAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/\n" -"MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgGA1UdIAQRMA8wDQYLKwYBBAGw\n" -"LQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4G\n" -"kGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0\n" -"BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQP\n" -"OLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYV\n" -"qL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv\n" -"KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed\n" -"-----END CERTIFICATE-----\n", - -/* QuoVadis Root CA 1 G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDEL\n" -"MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1Zh\n" -"ZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJ\n" -"BgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRp\n" -"cyBSb290IENBIDEgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjuny\n" -"bEC0BJyFuTHK3C3kEakEPBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/\n" -"gpqG7D0DmVIB0jWerNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOG\n" -"MAqNF34168Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh\n" -"4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5\n" -"cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnA\n" -"AZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2Y\n" -"fF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVd\n" -"YdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7\n" -"a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfn\n" -"kduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB\n" -"/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD\n" -"ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOCMTaIzen7\n" -"xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3GPoa\n" -"jOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct\n" -"Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvr\n" -"iBbP+V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCk\n" -"eF9OrYMh3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ\n" -"+p6Q9pxyz0fawx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTD\n" -"vdbJWqNjZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp\n" -"q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwS\n" -"tIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD\n" -"-----END CERTIFICATE-----\n", - -/* QuoVadis Root CA 2 G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDEL\n" -"MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1Zh\n" -"ZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJ\n" -"BgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRp\n" -"cyBSb290IENBIDIgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjc\n" -"V4g/Ruv5r+LrI3HimtFhZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WV\n" -"HhLL5hSEBMHfNrMWn4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs\n" -"+L5u+9ymc5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+\n" -"O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8M\n" -"SPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPj\n" -"EiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKqIcGY5jDjZ1XHm26sGahVpkUG\n" -"0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIh\n" -"O4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoos\n" -"FCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4\n" -"r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB\n" -"/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD\n" -"ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66AarHakE7\n" -"kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7KpVMN\n" -"qT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9\n" -"x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9\n" -"sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDb\n" -"Il9qxV6XU/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+\n" -"D77vfoRrQ+NwmNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpn\n" -"IdsPNWNgKCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM\n" -"HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0Z\n" -"iC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M\n" -"-----END CERTIFICATE-----\n", - -/* QuoVadis Root CA 3 G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDEL\n" -"MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1Zh\n" -"ZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJ\n" -"BgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRp\n" -"cyBSb290IENBIDMgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47q\n" -"FJenMioKVjZ/aEzHs286IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O\n" -"2YIyC0TeytuMrKNuFoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMB\n" -"OSBDGzXRU7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c\n" -"ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f\n" -"+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCT\n" -"ZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzweyuxwHApw0BiLTtIadwjPEjr\n" -"ewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqiMd5mBPfAdOhx3v89WcyWJhKLhZVX\n" -"GqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUC\n" -"f+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/J\n" -"xHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB\n" -"/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD\n" -"ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3pxKGmPc+FS\n" -"kNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzSt/Ac\n" -"5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ\n" -"TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2Nn\n" -"L9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE\n" -"8/nxoGibIh6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8\n" -"XgBCH/MyJnmDhPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H\n" -"6QrG2vd+DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN\n" -"PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDY\n" -"WGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert Assured ID Root G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYD\n" -"VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" -"Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIw\n" -"MDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg\n" -"SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1\n" -"cmVkIElEIFJvb3QgRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82\n" -"ckmIkzTz+GoeMVSAn61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxN\n" -"EP4HteccbiJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp\n" -"EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO7\n" -"04gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8\n" -"p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQF\n" -"MAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTOw0q5mVXyuNtgv6l+vVa1lzan1jAN\n" -"BgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTU\n" -"iaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LR\n" -"dWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70\n" -"CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv\n" -"ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwoIhNzbM8m\n" -"9Yop5w==\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert Assured ID Root G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQG\n" -"EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t\n" -"MSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAw\n" -"WhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j\n" -"MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk\n" -"IElEIFJvb3QgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0F\n" -"FfLvC/8QdJ+1YlJfZn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+\n" -"CW7if17QRSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n" -"BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBk\n" -"AjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2\n" -"U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv6pZjamVFkpUBtA==\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert Global Root G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYD\n" -"VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" -"Y29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBa\n" -"Fw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx\n" -"GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBS\n" -"b290IEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/\n" -"RrohCgiN9RlUyfuI2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxps\n" -"MNzaHxmx1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n" -"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F\n" -"8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9\n" -"RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G\n" -"A1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcN\n" -"AQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVn\n" -"NeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2Z\n" -"L7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUT\n" -"Fy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n" -"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTflMrY=\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert Global Root G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQG\n" -"EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t\n" -"MSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0z\n" -"ODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAX\n" -"BgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290\n" -"IEczMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu8\n" -"0JX28MzQC7phW1FGfp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6I\n" -"p6FrtUPOZ9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd\n" -"BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/\n" -"EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q\n" -"3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8sycX\n" -"-----END CERTIFICATE-----\n", - -/* DigiCert Trusted Root G4 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYD\n" -"VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" -"Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAw\n" -"WhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j\n" -"MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVk\n" -"IFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAi\n" -"MGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/W\n" -"BTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV\n" -"ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw\n" -"2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+\n" -"EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1\n" -"EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADk\n" -"RSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+\n" -"9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m8\n" -"00ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn1\n" -"5GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\n" -"hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQADggIBALth\n" -"2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYYZhsj\n" -"DT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr\n" -"yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXIN\n" -"wBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfR\n" -"nGTZ6iahixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhV\n" -"Mt5xSdkoF1BN5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbS\n" -"pKhil9Ie4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI\n" -"r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIVi\n" -"HTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq\n" -"6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+\n" -"-----END CERTIFICATE-----\n", - -/* WoSign */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBVMQswCQYD\n" -"VQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNVBAMTIUNlcnRpZmlj\n" -"YXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAw\n" -"MDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UE\n" -"AxMhQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEF\n" -"AAOCAg8AMIICCgKCAgEAvcqNrLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4\n" -"uEBf2zmoAHqLoE1UfcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxII\n" -"FgsDcSccf+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2\n" -"ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4Mx1wdC34F\n" -"Mr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpRaG53/Ma/UkpmRqGy\n" -"Zxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjchzDBwyYaYD8xYTYO7feUapTeN\n" -"tqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDaruHqklWJqbrDKaiFaafPz+x1wOZXzp26m\n" -"gYmhiMU7ccqjUu6Du/2gd/Tkb+dC221KmYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHX\n" -"qc1S+4RFaQkAKtxVi8QGRkvASh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8\n" -"gdLKH3Ep3XZPkiWvHYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYD\n" -"VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H\n" -"EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1LOJwXcgu\n" -"2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJMuYhOZO9sxXqT2r0\n" -"9Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2eJXLOC62qx1ViC777Y7NhRCOj\n" -"y+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VNg64N1uajeeAz0JmWAjCnPv/So0M/BVoG\n" -"6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWpdIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wu\n" -"cPrXnbes5cVAWubXbHssw1abR80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAd\n" -"BtaP4/tIEp9/EaEQPkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/\n" -"kWjFgGcexGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+\n" -"J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMlOtzCWfHj\n" -"XEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWTee5Ehr7XHuQe+w==\n" -"-----END CERTIFICATE-----\n", - -/* WoSign China */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBGMQswCQYD\n" -"VQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMMEkNBIOayg+mA\n" -"muagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMEYxCzAJBgNVBAYT\n" -"AkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC5\n" -"6K+B5LmmMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYG\n" -"TufQdDTc7WU1/FDWiD+k8H/rD195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqo\n" -"GXqW5pWOdO2XCld19AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+\n" -"jJZSEExfv5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk\n" -"UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+LNVgbExz0\n" -"3jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb+gIgthhid5E7o9Vl\n" -"rdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52VqyBPt7D3h1ymoOQ3OMdc4zUP\n" -"LK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6KyX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklT\n" -"fuQZa/HpelmjbX7FF+Ynxu8b22/8DU0GAbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53\n" -"oWprfi1tWFxK1I5nuPHa1UaKJ/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9\n" -"YDxOiT7nnO4fuwECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w\n" -"HQYDVR0OBBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4\n" -"WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6yAa+Tkvv\n" -"/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj/feTZU7n85iYr83d\n" -"2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6jBAyvd0zaziGfjk9DgNyp115\n" -"j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6e\n" -"FN0AkLppRQjbbpCBhqcqBT/mhDn4t/lXX0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KN\n" -"PQx96N97qA4bLJyuQHCH2u2nFoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOG\n" -"G0jfKq+nwf/Yjj4Du9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7D\n" -"lfEvr10lO1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le\n" -"ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR12KvxAmLB\n" -"sX5VYc8T1yaw15zLKYs4SgsOkI26oQ==\n" -"-----END CERTIFICATE-----\n", - -/* COMODO RSA Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkG\n" -"A1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9y\n" -"ZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2Vy\n" -"dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCB\n" -"hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMH\n" -"U2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBS\n" -"U0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n" -"AoICAQCR6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\n" -"pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7E\n" -"pi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrk\n" -"m2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFI\n" -"Q2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+8\n" -"6V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4\n" -"jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZED\n" -"LXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RM\n" -"hnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\n" -"crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4EFgQUu69+\n" -"Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ\n" -"KoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt\n" -"rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA\n" -"1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4\n" -"sjn8OoSgtZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrr\n" -"kguhxuhoqEwWsRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpj\n" -"IXUDgIiKboHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\n" -"jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0\n" -"QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN\n" -"/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog\n" -"88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHBNVOFBkpdn627G190\n" -"-----END CERTIFICATE-----\n", - -/* USERTrust RSA Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkG\n" -"A1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4w\n" -"HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0Eg\n" -"Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5\n" -"WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n" -"eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJU\n" -"cnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4IC\n" -"DwAwggIKAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B\n" -"3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTb\n" -"f6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshy\n" -"Z9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3K\n" -"fyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq/nROacdrjGCT3sTHDN/hMq7MkztR\n" -"eJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+td\n" -"Omw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugT\n" -"ncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE\n" -"9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE\n" -"Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAdBgNVHQ4E\n" -"FgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB\n" -"Af8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW\n" -"FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1V\n" -"eCkZ7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jR\n" -"Ra8YFWSQEg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9s\n" -"pnFixdjQg3IM8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwX\n" -"tuhxkYzeSf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ\n" -"XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL3\n" -"9ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnu\n" -"Wih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1\n" -"cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfGjjxDah2nGN59PRbxYvnKkKj9\n" -"-----END CERTIFICATE-----\n", - -/* USERTrust ECC Certification Authority */ -"-----BEGIN CERTIFICATE-----\n" -"MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UE\n" -"BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYD\n" -"VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2Vy\n" -"dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCB\n" -"iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBD\n" -"aXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVz\n" -"dCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQa\n" -"rFRaqfloI+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n" -"o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQW\n" -"BBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n" -"/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpk\n" -"ue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbR\n" -"qZtNyWHa0V1Xahg=\n" -"-----END CERTIFICATE-----\n", - -/* GlobalSign ECC Root CA - R4 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UE\n" -"CxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMw\n" -"EQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEk\n" -"MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxT\n" -"aWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5\n" -"049sJQ6fLjkZHAOkrprlOQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5Rikqtlx\n" -"P6jUuc6MHaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE\n" -"FFSwe61FuOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX\n" -"kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q=\n" -"-----END CERTIFICATE-----\n", - -/* GlobalSign ECC Root CA - R5 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UE\n" -"CxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMw\n" -"EQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEk\n" -"MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxT\n" -"aWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9\n" -"Xb/pOdEh+J8LttV7HpI6SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwz\n" -"ocWdTaRvQZU4f8kehOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMC\n" -"AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI\n" -"KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguD\n" -"nFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL\n" -"+SvzZpA3\n" -"-----END CERTIFICATE-----\n", - -/* Staat der Nederlanden Root CA - G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwG\n" -"A1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJs\n" -"YW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjEL\n" -"MAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwi\n" -"U3RhYXQgZGVyIE5lZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQAD\n" -"ggIPADCCAgoCggIBAL4yolQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKe\n" -"zIJnByeHaHE6n3WWIkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+\n" -"tjzk7FqXxz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy\n" -"KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHga\n" -"mPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXL\n" -"CCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N86U78dULI7ViVDAZCopz35HCz\n" -"33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwF\n" -"dozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXN\n" -"o42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke2\n" -"75dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1Ud\n" -"EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd\n" -"INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BDU5cqPco8\n" -"R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPqKqrM\n" -"CQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1\n" -"v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkv\n" -"RtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox\n" -"+wrZ13+b8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzh\n" -"VNXkanjvSr0rmj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgS\n" -"KL59NVzq4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR\n" -"1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U6\n" -"7cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk=\n" -"-----END CERTIFICATE-----\n", - -/* Staat der Nederlanden EV Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwG\n" -"A1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJs\n" -"YW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJ\n" -"BgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0\n" -"YWF0IGRlciBOZWRlcmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A\n" -"MIICCgKCAgEA48d+ifkkSzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79\n" -"VWZxXSzFYGgEt9nCUiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs\n" -"3NZmdO3dZ//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p\n" -"rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd\n" -"4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5\n" -"m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxCKFhmpUZtcALXEPlLVPxdhkqH\n" -"z3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV0b5GnUngC6agIk440ME8MLxwjyx1\n" -"zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8J\n" -"OV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZB\n" -"iFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/\n" -"BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7\n" -"MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsIeK9p0gtJ\n" -"3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmuc0iH\n" -"55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq\n" -"5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLld\n" -"RqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW\n" -"2HNnh/tNf1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy\n" -"+TSrK0m1zSBi5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCj\n" -"uTaPPoIaGl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL\n" -"eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB\n" -"4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg==\n" -"-----END CERTIFICATE-----\n", - -/* IdenTrust Commercial Root CA 1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYD\n" -"VQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVy\n" -"Y2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYD\n" -"VQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVy\n" -"Y2lhbCBSb290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k9\n" -"1DNG8W9RYYKyqU+PZ4ldhNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1e\n" -"hm7zCYofWjK9ouuU+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQf\n" -"Yo3fw7gpS0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1\n" -"bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sO\n" -"dBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz\n" -"4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjKVsk9+w8YfYs7wRPCTY/JTw43\n" -"6R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzVWYfCP04MXFL0PfdSgvHqo6z9STQa\n" -"KPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h\n" -"9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHw\n" -"cz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\n" -"BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD\n" -"ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH6oi6mYtQ\n" -"lNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pgghst\n" -"O8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt\n" -"ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gj\n" -"mmmVYjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l\n" -"2xPE4iUXfeu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lz\n" -"zY9GvlU47/rokTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2\n" -"gXjtw+hG4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ\n" -"mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgG\n" -"pRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H\n" -"-----END CERTIFICATE-----\n", - -/* IdenTrust Public Sector Root CA 1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYD\n" -"VQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGlj\n" -"IFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQsw\n" -"CQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVi\n" -"bGljIFNlY3RvciBSb290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2\n" -"IpT8pEiv6EdrCvsnduTyP4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2L\n" -"qEfpYnYeEe4IFNGyRBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1\n" -"B5+ctMlSbdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF\n" -"/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbh\n" -"uaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH0\n" -"1bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy9yLxkA2T26pEUWbMfXYD62qo\n" -"KjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyhLrXHFub4qjySjmm2AcG1hp2JDws4\n" -"lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYP\n" -"Qxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsby\n" -"VtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" -"VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN\n" -"AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qjt2odIFfl\n" -"AWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7AmgjV\n" -"QdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt\n" -"GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S\n" -"3OFtm6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHW\n" -"chezxQMxNRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF\n" -"5PgLZxYWxoK4Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57I\n" -"cXR5f1GJtshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA\n" -"GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXws\n" -"BOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c\n" -"-----END CERTIFICATE-----\n", - -/* Entrust Root Certification Authority - G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAU\n" -"BgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn\n" -"YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9y\n" -"aXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0\n" -"aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UE\n" -"BhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVz\n" -"dC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBm\n" -"b3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj\n" -"YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6\n" -"hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3\n" -"gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWNcCG0szLni6LVhjkCsbjSR87k\n" -"yUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKUs/Ja5CeanyTXxuzQmyWC48zCxEXF\n" -"jJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+\n" -"tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1Ud\n" -"DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2f\n" -"kBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/\n" -"jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZRkfz6/dj\n" -"wUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDginWyT\n" -"msQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+\n" -"vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ\n" -"19xOe4pIb4tF9g==\n" -"-----END CERTIFICATE-----\n", - -/* Entrust Root Certification Authority - EC1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMC\n" -"VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5u\n" -"ZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3Ig\n" -"YXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRp\n" -"b24gQXV0aG9yaXR5IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8x\n" -"CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3\n" -"LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJ\n" -"bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD\n" -"ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQT\n" -"ydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9\n" -"ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/\n" -"BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLdj5xrdjekIplWDpOBqUEFlEUJJ\n" -"MAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHv\n" -"AvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZB\n" -"WyVgrtBIGu4G\n" -"-----END CERTIFICATE-----\n", - -/* CFCA EV ROOT */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4G\n" -"A1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD\n" -"DAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYD\n" -"VQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y\n" -"aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n" -"AoICAQDXXWvNED8fBVnVBU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCj\n" -"Z9YMrM8irq93VCpLTIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3Iv\n" -"HWOX6Jn5/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp\n" -"7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wm\n" -"nvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXS\n" -"G7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvPa931DfSCt/SyZi4QKPaXWnuW\n" -"Fo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBF\n" -"DWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUp\n" -"dPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900Pvh\n" -"tgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj\n" -"/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd\n" -"BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIBACXGumvr\n" -"h8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9qecsA\n" -"IyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua\n" -"4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9B\n" -"C2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rU\n" -"QElsgIfXBDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZeP\n" -"glr4UeWJoBjnaH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4R\n" -"UHlzEhLN5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe\n" -"/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV\n" -"2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UC\n" -"Kc2Jo5YPSjXnTkLAdc0Hz+Ys63su\n" -"-----END CERTIFICATE-----\n", - -/* TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UEBhMCVFIx\n" -"DzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2lt\n" -"IHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4xQjBABgNVBAMMOVTD\n" -"nFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBI\n" -"NTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0G\n" -"A1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg\n" -"QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktU\n" -"UlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB\n" -"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom/4NZzkQq\n" -"L/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kRGml1bsMdi9GYjZOH\n" -"p3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh34khon6La8eHBEJ/rPCmBp+Ey\n" -"CNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1\n" -"nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/\n" -"dV8c7TKcfGkAaZHjIxhT6QIDAQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4g\n" -"wPswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB\n" -"AJ5FdnsXSDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l\n" -"VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwqURawXs3q\n" -"ZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nfpeYVhDfwwvJllpKQ\n" -"d/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CFYv4HAqGEVka+lgqaE9chTLd8\n" -"B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW+qtB4Uu2NQvAmxU=\n" -"-----END CERTIFICATE-----\n", - -/* Certinomis - Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjETMBEGA1UE\n" -"ChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAbBgNVBAMTFENlcnRp\n" -"bm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMzMTAyMTA5MTcxOFowWjELMAkG\n" -"A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAz\n" -"MR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP\n" -"ADCCAgoCggIBANTMCQosP5L2fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4\n" -"kkjW4znuzuRZWJflLieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9\n" -"n+ws+hQVWZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF\n" -"TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb5nPJWqHZ\n" -"ZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLScCbAK42C++PhmiM1b\n" -"8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6RiwsXm9E/G+Z8ajYJJGYrKWUM6\n" -"6A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJwx3tFvYk9CcbXFcx3FXuqB5vbKziRcxX\n" -"V4p1VxngtViZSTYxPDMBbRZKzbgqg4SGm/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC6\n" -"3M9933zZxKyGIjK8e2uR73r4F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb\n" -"1TQdvtj/dBxThZngWVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB\n" -"/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0\n" -"2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsFAAOCAgEA\n" -"fj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/0KGRHCwPT5iVWVO9\n" -"0CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWwF6YSjNRieOpWauwK0kDDPAUw\n" -"Pk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZSg081lLMSVX3l4kLr5JyTCcBMWwerx20R\n" -"oFAXlCOotQqSD7J6wWAsOMwaplv/8gzjqh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5n\n" -"U7aJ+BIJy29SWwNyhlCVCNSNh4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrj\n" -"cOa4pvi2WsS9Iff/ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu\n" -"6AdBBR8Vbtaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj\n" -"Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ8ow7bkrH\n" -"xuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvWgQncItzujrnEj1Ph\n" -"Z7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE=\n" -"-----END CERTIFICATE-----\n", - -/* OISTE WISeKey Global Root GB CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYD\n" -"VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBF\n" -"bmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0x\n" -"NDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdX\n" -"SVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9P\n" -"SVNURSBXSVNlS2V5IEdsb2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" -"MIIBCgKCAQEA2Be3HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvY\n" -"D06fWvGxWuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX\n" -"1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn2\n" -"1HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiND\n" -"ecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9rM2RYk61pv48b74JIxwIDAQAB\n" -"o1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs\n" -"+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4ey\n" -"mYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHE\n" -"thYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3Z\n" -"wLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf\n" -"aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02IcNc1MaRVU\n" -"GpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=\n" -"-----END CERTIFICATE-----\n", - -/* Certification Authority of WoSign G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDfDCCAmSgAwIBAgIQayXaioidfLwPBbOxemFFRDANBgkqhkiG9w0BAQsFADBYMQswCQYD\n" -"VQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxLTArBgNVBAMTJENlcnRpZmlj\n" -"YXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbiBHMjAeFw0xNDExMDgwMDU4NThaFw00NDExMDgw\n" -"MDU4NThaMFgxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEtMCsG\n" -"A1UEAxMkQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgb2YgV29TaWduIEcyMIIBIjANBgkqhkiG\n" -"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvsXEoCKASU+/2YcRxlPhuw+9YH+v9oIOH9ywjj2X4FA8\n" -"jzrvZjtFB5sg+OPXJYY1kBaiXW8wGQiHC38Gsp1ij96vkqVg1CuAmlI/9ZqD6TRay9nVYlzm\n" -"DuDfBpgOgHzKtB0TiGsOqCR3A9DuW/PKaZE1OVbFbeP3PU9ekzgkyhjpJMuSA93MHD0JcOQg\n" -"5PGurLtzaaNjOg9FD6FKmsLRY6zLEPg95k4ot+vElbGs/V6r+kHLXZ1L3PR8du9nfwB6jdKg\n" -"GlxNIuG12t12s9R23164i5jIFFTMaxeSt+BKv0mUYQs4kI9dJGwlezt52eJ+na2fmKEG/HgU\n" -"YFf47oB3sQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n" -"HQ4EFgQU+mCp62XF3RYUCE4MD42b4Pdkr2cwDQYJKoZIhvcNAQELBQADggEBAFfDejaCnI2Y\n" -"4qtAqkePx6db7XznPWZaOzG73/MWM5H8fHulwqZm46qwtyeYP0nXYGdnPzZPSsvxFPpahygc\n" -"7Y9BMsaV+X3avXtbwrAh449G3CE4Q3RM+zD4F3LBMvzIkRfEzFg3TgvMWvchNSiDbGAtROtS\n" -"jFA9tWwS1/oJu2yySrHFieT801LYYRf+epSEj3m2M1m6D8QL4nCgS3gu+sif/a+RZQp4OBXl\n" -"lxcU3fngLDT4ONCEIgDAFFEYKwLcMFrw6AF8NTojrwjkr6qOKEJJLvD1mTS+7Q9LGOHSJDy7\n" -"XUe3IfKN0QqZjuNuPq1w4I+5ysxugTH2e5x6eeRncRg=\n" -"-----END CERTIFICATE-----\n", - -/* CA WoSign ECC Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIICCTCCAY+gAwIBAgIQaEpYcIBr8I8C+vbe6LCQkDAKBggqhkjOPQQDAzBGMQswCQYDVQQG\n" -"EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMTEkNBIFdvU2lnbiBF\n" -"Q0MgUm9vdDAeFw0xNDExMDgwMDU4NThaFw00NDExMDgwMDU4NThaMEYxCzAJBgNVBAYTAkNO\n" -"MRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAxMSQ0EgV29TaWduIEVDQyBS\n" -"b290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4f2OuEMkq5Z7hcK6C62N4DrjJLnSsb6IOsq/\n" -"Srj57ywvr1FQPEd1bPiUt5v8KB7FVMxjnRZLU8HnIKvNrCXSf4/CwVqCXjCLelTOA7WRf6qU\n" -"0NGKSMyCBSah1VES1ns2o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd\n" -"BgNVHQ4EFgQUqv3VWqP2h4syhf3RMluARZPzA7gwCgYIKoZIzj0EAwMDaAAwZQIxAOSkhLCB\n" -"1T2wdKyUpOgOPQB0TKGXa/kNUTyh2Tv0Daupn75OcsqF1NnstTJFGG+rrQIwfcf3aWMvoeGY\n" -"7xMQ0Xk/0f7qO3/eVvSQsRUR2LIiFdAvwyYua/GRspBl9JrmkO5K\n" -"-----END CERTIFICATE-----\n", - -/* SZAFIR ROOT CA2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTEL\n" -"MAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4x\n" -"GDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQz\n" -"MzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93\n" -"YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IB\n" -"DwAwggEKAoIBAQC3vD5QqEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj\n" -"5/QqGJ3a0a4m7utT3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd\n" -"3BucPbOw3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6\n" -"3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0Bw\n" -"PLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMB\n" -"AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlK\n" -"GLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOCAQEAtXP4A9xZWx126aMqe5Aosk3A\n" -"M0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2\n" -"onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcP\n" -"v5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4M\n" -"NIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg\n" -"LvWpCz/UXeHPhJ/iGcJfitYgHuNztw==\n" -"-----END CERTIFICATE-----\n", - -/* Certum Trusted Network CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkG\n" -"A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsT\n" -"HkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0\n" -"ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGA\n" -"MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUG\n" -"A1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0g\n" -"VHJ1c3RlZCBOZXR3b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9\n" -"+Xj45tWADGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn\n" -"0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/\n" -"j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxS\n" -"iyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq\n" -"28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130GO6IyY0XRSmZMnUCMe4pJshrAua1\n" -"YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz\n" -"5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXX\n" -"cPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqC\n" -"lnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n\n" -"3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G\n" -"A1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcN\n" -"AQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW\n" -"Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ\n" -"2vuAL55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BN\n" -"XuMp8SMoclm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3\n" -"lEu6LaTaM4tMpkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVI\n" -"eVheO/jbAoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq\n" -"P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bIND\n" -"d82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXa\n" -"QHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlF\n" -"w5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbiDrW5viSP\n" -"-----END CERTIFICATE-----\n", - -/* Hellenic Academic and Research Institutions RootCA 2015 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNV\n" -"BAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIElu\n" -"c3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWlj\n" -"IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIx\n" -"WhcNNDAwNjMwMTAxMTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIG\n" -"A1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0\n" -"LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJ\n" -"bnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" -"AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFB\n" -"zh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioN\n" -"mToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe104S+nfK8nNLspfZu2zwnI5dMK\n" -"/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXdFPQf/7J31Ycvqm59JCfnxssm5uX+\n" -"Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1\n" -"NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS\n" -"5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZ\n" -"YeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko\n" -"LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZaycBw/qa9wf\n" -"LgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\n" -"MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI\n" -"hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg\n" -"2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6Hwb\n" -"ISHTGzrMd/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkN\n" -"aeJK9E10A/+yd+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRG\n" -"ar9KC/eaj8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh\n" -"X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZd\n" -"wJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+j\n" -"qk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoX\n" -"xdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODf\n" -"qiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9qp/UsQu0yrbYhnr68\n" -"-----END CERTIFICATE-----\n", - -/* Hellenic Academic and Research Institutions ECC RootCA 2015 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcT\n" -"BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3Rp\n" -"dHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFu\n" -"ZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcx\n" -"MloXDTQwMDYzMDEwMzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBC\n" -"BgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2Vy\n" -"dC4gQXV0aG9yaXR5MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2gg\n" -"SW5zdGl0dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg\n" -"QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvkt\n" -"TpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/\n" -"BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kq\n" -"MAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTAGiecMjvAwNW6qef4BENThe5SId6d\n" -"9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRt\n" -"m8rifOoCWCKR\n" -"-----END CERTIFICATE-----\n", - -/* Certplus Root CA G1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUAMD4xCzAJ\n" -"BgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBD\n" -"QSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4xCzAJBgNVBAYTAkZSMREw\n" -"DwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJ\n" -"KoZIhvcNAQEBBQADggIPADCCAgoCggIBANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTY\n" -"j+eJZJ+622SLZOZ5KmHNr49aiZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8\n" -"KvzsiNWI7nC9hRYt6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y\n" -"1/oA/caP0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f\n" -"6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDEEW4wduOU\n" -"8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN1iNQMLAVdBM+5S//\n" -"Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrch2c0798wct3zyT8j/zXhviEp\n" -"IDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCTmehd4F6H50boJZwKKSTUzViGUkAksnsP\n" -"mBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V\n" -"/YDI+HLlJWvEYLF7bY5KinPOWftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMB\n" -"AAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCb\n" -"kahDFXxdBie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq\n" -"hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh66Ryj5QX\n" -"vBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7/SMNkPX0XtPGYX2e\n" -"EeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BSS7CTKtQ+FjPlnsZlFT5kOwQ/\n" -"2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0l\n" -"pu+F6ALEUz65noe8zDUa3qHpimOHZR4RKttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7\n" -"BlXGEilXCNQ314cnrUlZp5GrRHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+\n" -"aLHsIqKqkHWetUNy6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ\n" -"8uQKTuEVV/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5\n" -"g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl++O/Qmue\n" -"D6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo=\n" -"-----END CERTIFICATE-----\n", - -/* Certplus Root CA G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4xCzAJBgNV\n" -"BAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBDQSBH\n" -"MjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4xCzAJBgNVBAYTAkZSMREwDwYD\n" -"VQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBsdXMgUm9vdCBDQSBHMjB2MBAGByqGSM49\n" -"AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/\n" -"aWfYeOKmycTbLXku54uNAm8xIk0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSps\n" -"BqNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5\n" -"jtpMxjwjFNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG\n" -"SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNchp+e/IQ8r\n" -"zfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAalU5ORGpOucGpnutee\n" -"5WEaXw==\n" -"-----END CERTIFICATE-----\n", - -/* OpenTrust Root CA G1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUAMEAxCzAJ\n" -"BgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290\n" -"IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAwMFowQDELMAkGA1UEBhMCRlIx\n" -"EjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3BlblRydXN0IFJvb3QgQ0EgRzEwggIi\n" -"MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICk\n" -"ES3d5oeuXT2R0odsN7faYp6bwiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1c\n" -"JwzdMOWo010hOHQX/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHc\n" -"YTSSjFR077F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP\n" -"uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLxp2NX5Ntq\n" -"p66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czxKselu7Cizv5Ta01B\n" -"G2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2TgOzfALU5nsaqocTvz6hdLub\n" -"DuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+WG+Oin6+Sx+31QrclTDsTBM8clq8cIqPQ\n" -"qwWyTBIjUtz9GVsnnB47ev1CI9sjgBPwvFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG6\n" -"31UAC9hWLbFJSXKAqWLXwPYYEQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2\n" -"LQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\n" -"l0YhVyE12jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw\n" -"DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000EPLuHIT83\n" -"9HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kfgLMtMrpkZ2CvuVnN\n" -"35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbSFXJfLkur1J1juONI5f6ELlgK\n" -"n0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+\n" -"WVLhX4SPgPL0DTatdrOjteFkdjpY3H1PXlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4\n" -"Fdvb8e80nR14SohWZ25g/4/Ii+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvy\n" -"DEsMpZTGMKcmGS3tTAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4\n" -"poEL0L9109S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky\n" -"Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJAwSQiumP\n" -"v+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj1oxx\n" -"-----END CERTIFICATE-----\n", - -/* OpenTrust Root CA G2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUAMEAxCzAJ\n" -"BgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290\n" -"IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFowQDELMAkGA1UEBhMCRlIx\n" -"EjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3BlblRydXN0IFJvb3QgQ0EgRzIwggIi\n" -"MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqW\n" -"ON2ic2rxb95eolq5cSG+Ntmh/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf8\n" -"5KxP6O6JHnSrT78eCbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWP\n" -"yKwlCm/61UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE\n" -"FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TSgSuyzpJL\n" -"HB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3XG7OHngevZXHloM8a\n" -"pwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZyYhK05pyDRPZRpOLAeiRXyg6l\n" -"Pzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaHvGOz9bGTXOBut9Dq+WIyiET7vycotjCV\n" -"XRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9Rjz\n" -"IR9u701oBnstXW5DiabA+aC/gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmE\n" -"JwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\n" -"ajn6QiL35okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w\n" -"DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamzGj5oXScm\n" -"p7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0nXGEL8pZ0keImUEi\n" -"yTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qTRmTFAHneIWv2V6CG1wZy7HBG\n" -"S4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpTwm+bREx50B1ws9efAvSyB7DH5fitIw6m\n" -"VskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8\n" -"oValX9YZ6e18CL13zSdkzJTaTkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1R\n" -"GUFcPk8G97krgCf2o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK\n" -"2gKgW0VU3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA\n" -"iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14fWKGVyasv\n" -"c0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEMS1IK\n" -"-----END CERTIFICATE-----\n", - -/* OpenTrust Root CA G3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAxCzAJBgNV\n" -"BAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENB\n" -"IEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFowQDELMAkGA1UEBhMCRlIxEjAQ\n" -"BgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3BlblRydXN0IFJvb3QgQ0EgRzMwdjAQBgcq\n" -"hkjOPQIBBgUrgQQAIgNiAARK7liuTcpm3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa\n" -"3RTqnVkrQ7cG7DK2uu5Bta1doYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMU\n" -"muXZl5mjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRH\n" -"d8MUi2I5DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK\n" -"BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+qj9uEwov1\n" -"NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx4nxp5V2a+EEfOzmT\n" -"k51V6s2N8fvB\n" -"-----END CERTIFICATE-----\n", - -/* ISRG Root X1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkG\n" -"A1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUw\n" -"EwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBP\n" -"MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3Jv\n" -"dXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\n" -"ggIBAK3oJHP0FDfzm54rVygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj\n" -"/RQSa78f0uoxmyF+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7i\n" -"S4+3mX6UA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" -"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3Hs\n" -"LuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02\n" -"dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUvKBds0pjBqAlkd25HN7rOrFle\n" -"aJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFuhjuefXKnEgV4We0+UXgVCwOPjdAv\n" -"BbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymC\n" -"zLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC\n" -"1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB\n" -"BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" -"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZLubhzEFnT\n" -"IZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV0nxv\n" -"wuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt\n" -"hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztX\n" -"OoJwTdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIu\n" -"vtd7u+Nxe5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1N\n" -"bdWhscdCb+ZAJzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4k\n" -"qKOJ2qxq4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" -"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcY\n" -"xn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" -"-----END CERTIFICATE-----\n", - -/* AC RAIZ FNMT-RCM */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNV\n" -"BAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAe\n" -"Fw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQK\n" -"DAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEB\n" -"BQADggIPADCCAgoCggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuO\n" -"i5KOpyVdWRHbNi63URcfqQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qs\n" -"NI1NOHZnjrDIbzAzWHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhf\n" -"TzC8PhxFtBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z\n" -"374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1\n" -"TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo\n" -"/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7wk5HlqX2doWjKI/pgG6BU6Vt\n" -"X7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ47xVqCfWS+2QrYv6YyVZLag13cqX\n" -"M7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5\n" -"QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAw\n" -"DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn\n" -"9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3\n" -"LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDDnFFlm5wi\n" -"oooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+sD8+N\n" -"b/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ\n" -"j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6\n" -"NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71\n" -"uSANA+iW+YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8\n" -"TxxuBEOb+dY7Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj\n" -"2zs3gyLp1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B\n" -"9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9ba\n" -"RRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViA\n" -"qhOLUTpPSPaLtrM=\n" -"-----END CERTIFICATE-----\n", - -/* Amazon Root CA 1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQsw\n" -"CQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAx\n" -"MB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNV\n" -"BAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEB\n" -"BQADggEPADCCAQoCggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOg\n" -"Q3pOsqTQNroBvo3bSMgHFzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9\n" -"tBb6dNqcmzU5L/qwIFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAw\n" -"hmahRWa6VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" -"93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrI\n" -"qg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYE\n" -"FIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUAA4IBAQCY8jdaQZChGsV2USgg\n" -"NiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PMCCjjmCXPI6T53iHTfIUJrU6adTrC\n" -"C2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V\n" -"8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJc\n" -"JmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeX\n" -"eGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5\n" -"-----END CERTIFICATE-----\n", - -/* Amazon Root CA 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQsw\n" -"CQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAy\n" -"MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNV\n" -"BAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEB\n" -"BQADggIPADCCAgoCggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBv\n" -"IITplLGbhQPDW9tK6Mj4kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZM\n" -"UnbqQ523BNFQ9lXg1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6\n" -"PBJTYv9K8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r\n" -"2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18\n" -"JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh\n" -"32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6ZjmUyl+17vIWR6IF9sZIUVyzfp\n" -"YgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vn\n" -"SUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3y\n" -"VAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8G\n" -"A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPl\n" -"Uq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2\n" -"LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY+gn0oJMs\n" -"XdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LEYFiw\n" -"zAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW\n" -"xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq\n" -"2WWQgj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JO\n" -"cQ3AWEbWaQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn\n" -"9Kr5v2c69BoVYh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG\n" -"6lzWE7OE76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H\n" -"9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=\n" -"-----END CERTIFICATE-----\n", - -/* Amazon Root CA 3 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYD\n" -"VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4X\n" -"DTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoT\n" -"BkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49\n" -"AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6A\n" -"F2hiRVd9RFgdszflZwjrZt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGG\n" -"MB0GA1UdDgQWBBSrttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWS\n" -"oxe3jfkrBqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM\n" -"YyRIHN8wfdVoOw==\n" -"-----END CERTIFICATE-----\n", - -/* Amazon Root CA 4 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYD\n" -"VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4X\n" -"DTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoT\n" -"BkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAi\n" -"A2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhF\n" -"XRbb/egQbeOc4OO9X4Ri83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYD\n" -"VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc\n" -"84ZtV+WBMAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw\n" -"CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJ\n" -"ElMzrdfkviT8tQp21KW8EA==\n" -"-----END CERTIFICATE-----\n", - -/* LuxTrust Global Root 2 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQELBQAwRjEL\n" -"MAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNVBAMMFkx1eFRydXN0\n" -"IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUwMzA1MTMyMTU3WjBGMQswCQYD\n" -"VQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5BLjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xv\n" -"YmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNM\n" -"PIf5U2o3C/IPPIfOb9wmKb3FibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJI\n" -"cRHIbjuend+JZTemhfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy9\n" -"5iJMHZP1EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn\n" -"Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4zDRbIvCG\n" -"p4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ96Tlk0u8d2cx3Rz9\n" -"MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5mj5wWEWCPnolvZ77gR1o7DJpn\n" -"i89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4gDEa/a4ebsypmQjVGbKq6rfmYe+lQVRQx\n" -"v7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbW\n" -"TLkpS5XTdvN3JW1CHDiDTf2jX5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8\n" -"xTiOZCXhTTmQzsmHhFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5\n" -"MDcGByuBKwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0\n" -"Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT+Et8szAd\n" -"BgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQELBQADggIBAGoZFO1u\n" -"ecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9BzZAcg4atmpZ1gDlaCDdLnIN\n" -"H2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTOjFu1EVzPig4N1qx3gf4ynCSecs5U89Bv\n" -"olbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaX\n" -"cozrhAIuZY+kt9J/Z93I055cqqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8w\n" -"kbIEa91WvpWAVWe+2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx\n" -"9xIX3eP/JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre\n" -"zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQfLSoSOcbD\n" -"WjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+x9CWttrhSmQGbmBN\n" -"vUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6oJO9r4Ak4Ap+58rVyuiFVdw2\n" -"KuGUaJPHZnJED4AhMmwlxyOAgwrr\n" -"-----END CERTIFICATE-----\n", - -/* TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNV\n" -"BAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtu\n" -"b2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRp\n" -"ZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBT\n" -"U0wgS29rIFNlcnRpZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUw\n" -"ODI1NTVaMIHSMQswCQYDVQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYD\n" -"VQQKEzlUdXJraXllIEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAt\n" -"IFRVQklUQUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT\n" -"TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1\n" -"bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA\n" -"027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjF\n" -"xlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wcwv61A+xXzry0tcXtAA9TNypN9E8M\n" -"g/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K\n" -"18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESo\n" -"lbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1\n" -"+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL\n" -"BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifhAHe+SMg1\n" -"qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yclNhO\n" -"T8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R\n" -"e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X\n" -"8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=\n" -"-----END CERTIFICATE-----\n", - -// clang-format on -#endif // REALM_NOINST_ROOT_CERTS diff --git a/src/realm/sync/noinst/server/CMakeLists.txt b/src/realm/sync/noinst/server/CMakeLists.txt deleted file mode 100644 index a650c484018..00000000000 --- a/src/realm/sync/noinst/server/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -set(SERVER_SOURCES - access_control.cpp - access_token.cpp - server.cpp - server_dir.cpp - server_file_access_cache.cpp - server_history.cpp -) - -set(SERVER_HEADERS - access_control.hpp - access_token.hpp - clock.hpp - crypto_server.hpp - permissions.hpp - server.hpp - server_dir.hpp - server_file_access_cache.hpp - server_history.hpp - server_impl_base.hpp -) - - -add_library(SyncServer STATIC EXCLUDE_FROM_ALL ${SERVER_SOURCES} ${SERVER_HEADERS}) -add_library(Realm::SyncServer ALIAS SyncServer) - -set_target_properties(SyncServer PROPERTIES - OUTPUT_NAME "realm-server" -) -target_link_libraries(SyncServer PUBLIC Sync QueryParser) - -if(APPLE AND NOT REALM_FORCE_OPENSSL) - target_sources(SyncServer PRIVATE crypto_server_apple.mm) -elseif(REALM_HAVE_OPENSSL) - target_sources(SyncServer PRIVATE crypto_server_openssl.cpp) -else() - target_sources(SyncServer PRIVATE crypto_server_stub.cpp) -endif() diff --git a/src/realm/sync/noinst/server/access_control.cpp b/src/realm/sync/noinst/server/access_control.cpp deleted file mode 100644 index ef4f87d65d5..00000000000 --- a/src/realm/sync/noinst/server/access_control.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include - -using namespace realm; -using namespace realm::sync; - -struct AccessControl::Impl final : public AccessToken::Verifier { - util::Optional m_public_key; - - Impl(util::Optional public_key) - : m_public_key(std::move(public_key)) - { - } - - // Overriding members of AccessToken::Verifier - bool verify(BinaryData access_token, BinaryData signature) const override final - { - REALM_ASSERT(m_public_key); - return m_public_key->verify(access_token, signature); // Throws - } -}; - -AccessControl::AccessControl(util::Optional public_key) - : m_impl(new Impl(std::move(public_key))) -{ -} - -AccessControl::~AccessControl() {} - -util::Optional AccessControl::verify_access_token(StringData signed_token, - AccessToken::ParseError* out_error) const -{ - AccessToken::ParseError error; - AccessToken token; - // For the purpose of testing, public key is allowed to be absent. When it - // is absent, we set `out_error` to - // `AccessToken::ParseError::invalid_signature` but still pass the parsed - // token back to the caller. - AccessToken::Verifier* verifier = nullptr; - if (REALM_LIKELY(m_impl->m_public_key)) - verifier = &*m_impl; - if (REALM_LIKELY(AccessToken::parse(signed_token, token, error, verifier))) { - if (REALM_LIKELY(out_error)) { - if (REALM_LIKELY(m_impl->m_public_key)) { - *out_error = AccessToken::ParseError::none; - } - else { - *out_error = AccessToken::ParseError::invalid_signature; - } - } - return token; - } - if (out_error) - *out_error = error; - return util::none; -} - -bool AccessControl::can(const AccessToken& token, Privilege permission, - const RealmFileIdent& realm_file) const noexcept -{ - if (token.path && *token.path != realm_file) { - return false; - } - unsigned int p = static_cast(permission); - return (token.access & p) == p; -} - -bool AccessControl::can(const AccessToken& token, unsigned int mask, const RealmFileIdent& realm_file) const noexcept -{ - if (token.path && *token.path != realm_file) { - return false; - } - return (token.access & mask) == mask; -} - -AccessToken::Verifier& AccessControl::verifier() const noexcept -{ - return *m_impl; -} - -// This is_admin() function is more complicated than it should be due to -// the current format of the tokens and behavior of ROS. -// This function can be simplified with new a token format. -bool AccessControl::is_admin(const AccessToken& token) const noexcept -{ - if (token.admin_field) - return token.admin; - - if (!token.path) - return true; - - // This will catch admins due to the way ROS makes access tokens. - // It is not safe since it might be too liberal. This function will be - // replaced as described above. - if (token.access & (Privilege::ModifySchema | Privilege::SetPermissions)) - return true; - - return false; -} diff --git a/src/realm/sync/noinst/server/access_control.hpp b/src/realm/sync/noinst/server/access_control.hpp deleted file mode 100644 index 2bd5ebbeab4..00000000000 --- a/src/realm/sync/noinst/server/access_control.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef REALM_SYNC_ACCESS_CONTROL_HPP -#define REALM_SYNC_ACCESS_CONTROL_HPP - -#include -#include -#include -#include - -namespace realm { -namespace sync { - -struct AccessControl { - /// Opens the Realm database at path \a db_path and initializes this - /// AccessControl object to verify access tokens using \a public_key. - /// - /// If \a public_key is not present, access tokens without a signature - /// will pass verification. - AccessControl(util::Optional public_key); - ~AccessControl(); - - /// Verify a string representing an access token. - /// - /// If \a error is non-null, it will be set to indicate the type of failure. - /// - /// NOTE: This method is thread-safe. - util::Optional verify_access_token(StringData access_token, - AccessToken::ParseError* error = nullptr) const; - - //@{ - /// Check whether user has the requested permission for the given - /// Realm file using the particular access token. - /// - /// In the version accepting \a mask, it is a bitfield representing - /// AND'ed values of the `Permission` enum, and it will return true if all - /// the permissions are granted. - /// - /// NOTE: This method is thread-safe. - bool can(const AccessToken&, Privilege, const RealmFileIdent&) const noexcept; - bool can(const AccessToken&, unsigned int mask, const RealmFileIdent&) const noexcept; - //@} - - bool is_admin(const AccessToken&) const noexcept; - - AccessToken::Verifier& verifier() const noexcept; - -private: - struct Impl; - std::unique_ptr m_impl; -}; - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_ACCESS_CONTROL_HPP diff --git a/src/realm/sync/noinst/server/access_token.cpp b/src/realm/sync/noinst/server/access_token.cpp deleted file mode 100644 index 8d178349957..00000000000 --- a/src/realm/sync/noinst/server/access_token.cpp +++ /dev/null @@ -1,324 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace realm; -using namespace realm::util; -using namespace realm::sync; - -namespace { - -struct AccessTokenParser { - using JSONEvent = JSONParser::EventType; - using JSONError = JSONParser::Error; - - AccessToken m_token; - - std::error_condition operator()(const JSONParser::Event& event) noexcept - { - if (state_stack.empty()) { - if (event.type != JSONEvent::object_begin) - return JSONError::unexpected_token; - state_stack.push(parser_state::toplevel); - return std::error_condition{}; // ok - } - switch (state_stack.top()) { - case toplevel: { - if (event.type == JSONEvent::object_end) { - state_stack.pop(); - break; // End of object - } - if (event.type != JSONEvent::string) - return JSONError::unexpected_token; - StringData escaped_key = event.escaped_string_value(); - if (escaped_key == "access") { - state_stack.push(await_access); - } - else if (escaped_key == "identity" || escaped_key == "sub") { - state_stack.push(await_identity); - } - else if (escaped_key == "admin" || escaped_key == "isAdmin") { - state_stack.push(await_admin); - } - else if (escaped_key == "timestamp" || escaped_key == "iat") { - state_stack.push(await_timestamp); - } - else if (escaped_key == "expires" || escaped_key == "exp") { - state_stack.push(await_expires); - } - else if (escaped_key == "path") { - state_stack.push(await_path); - } - else if (escaped_key == "sync_label" || escaped_key == "syncLabel") { - state_stack.push(await_sync_label); - } - else if (escaped_key == "app_id" || escaped_key == "appId") { - state_stack.push(await_app_id); - } - else { - state_stack.push(skip_value); - } - break; - } - case skip_value: { - if (event.type == JSONEvent::object_begin || event.type == JSONEvent::array_begin) { - ++m_skip_depth; - } - else if (event.type == JSONEvent::object_end || event.type == JSONEvent::array_end) { - --m_skip_depth; - } - if (m_skip_depth == 0) { - state_stack.pop(); - } - break; - } - case await_identity: { - if (event.type != JSONEvent::string) { - return JSONError::unexpected_token; - } - m_token.identity = UserIdent(event.escaped_string_value()); // FIXME: Unescape - state_stack.pop(); - break; - } - case await_admin: { - if (event.type != JSONEvent::boolean) { - return JSONError::unexpected_token; - } - m_token.admin_field = true; - m_token.admin = event.boolean; - state_stack.pop(); - break; - } - case await_timestamp: { - if (event.type == JSONEvent::null) { - m_token.timestamp = 0; - } - else if (event.type == JSONEvent::number) { - m_token.timestamp = std::int_fast64_t(event.number); - } - else { - return JSONError::unexpected_token; - } - state_stack.pop(); - break; - } - case await_expires: { - if (event.type == JSONEvent::null) { - m_token.expires = 0; - } - else if (event.type == JSONEvent::number) { - m_token.expires = std::int_fast64_t(event.number); - } - else { - return JSONError::unexpected_token; - } - state_stack.pop(); - break; - } - case await_sync_label: { - if (event.type != JSONEvent::string) - return JSONError::unexpected_token; - m_token.sync_label = SyncLabel(event.escaped_string_value()); // FIXME: Unescape - state_stack.pop(); - break; - } - case await_app_id: { - if (event.type != JSONEvent::string) - return JSONError::unexpected_token; - m_token.app_id = event.escaped_string_value(); // FIXME: Unescape - state_stack.pop(); - break; - } - case await_path: { - if (event.type != JSONEvent::string) - return JSONError::unexpected_token; - m_token.path = RealmFileIdent{event.escaped_string_value()}; // FIXME: Unescape - state_stack.pop(); - break; - } - case await_access: { - if (event.type != JSONEvent::array_begin) - return JSONError::unexpected_token; - state_stack.pop(); - state_stack.push(await_access_strings); - break; - } - case await_access_strings: { - if (event.type == JSONEvent::string) { - StringData access_string = event.escaped_string_value(); - if (access_string == "download") - m_token.access |= 0 | Privilege::Download; - else if (access_string == "upload") - m_token.access |= 0 | Privilege::Upload; - else if (access_string == "manage") - m_token.access |= Privilege::ModifySchema | Privilege::SetPermissions; - } - else if (event.type == JSONEvent::array_end) { - state_stack.pop(); - } - break; - } - } - return std::error_condition{}; // ok - } - -private: - enum parser_state { - toplevel, - await_identity, - await_admin, - await_timestamp, - await_expires, - await_app_id, - await_path, - await_sync_label, - await_access, - await_access_strings, - skip_value, - }; - - std::stack state_stack; - std::size_t m_skip_depth = 0; -}; - -} // unnamed namespace - -bool AccessToken::parseJWT(StringData signed_token, AccessToken& token, ParseError& error, Verifier* verifier) -{ - const char* signed_token_begin = signed_token.data(); - const char* signed_token_end = signed_token_begin + signed_token.size(); - - auto sep1 = std::find(signed_token_begin, signed_token_end, '.'); - if (sep1 == signed_token_end) { - error = ParseError::invalid_jwt; - return false; - } - std::size_t sep_pos = sep1 - signed_token_begin; - auto sep2 = std::find(sep1 + 1, signed_token_end, '.'); - if (sep2 == signed_token_end) { - error = ParseError::invalid_jwt; - return false; - } - std::size_t sep2_pos = sep2 - signed_token_begin; - - // Decode signature - if (verifier) { - StringData signature_base64 = StringData{sep2 + 1, signed_token.size() - sep2_pos - 1}; - std::vector signature_buffer; - signature_buffer.resize(base64_decoded_size(signature_base64.size())); - Optional num_bytes_signature = base64_decode(signature_base64, signature_buffer); - if (!num_bytes_signature) { - error = ParseError::invalid_base64; - return false; - } - - // Verify signature - BinaryData signature{signature_buffer.data(), *num_bytes_signature}; - bool verified = verifier->verify(BinaryData{signed_token.data(), sep2_pos}, - signature); // Throws - if (!verified) { - error = ParseError::invalid_signature; - return false; - } - } - - Optional> payload_vec = base64_decode_to_vector(StringData{sep1 + 1, sep2_pos - sep_pos - 1}); - if (!payload_vec) { - error = ParseError::invalid_base64; - return false; - } - - StringData payload = StringData{payload_vec->data(), payload_vec->size()}; - - // Parse decoded user token - JSONParser parser{payload}; - AccessTokenParser token_parser; - auto json_error = parser.parse(token_parser); - - if (json_error) { - error = ParseError::invalid_json; - return false; - } - - token = std::move(token_parser.m_token); - return true; -} - -bool AccessToken::parse(StringData signed_token, AccessToken& token, ParseError& error, Verifier* verifier) -{ - const char* signed_token_begin = signed_token.data(); - const char* signed_token_end = signed_token_begin + signed_token.size(); - - auto sep = std::find(signed_token_begin, signed_token_end, ':'); - StringData token_base64; - StringData signature_base64; - if (sep != signed_token_end) { - std::size_t sep_pos = sep - signed_token_begin; - std::size_t sig_len = signed_token.size() - sep_pos - 1; - token_base64 = StringData{signed_token.data(), sep_pos}; - signature_base64 = StringData{signed_token.data() + sep_pos + 1, sig_len}; - } - else { - // Could be that we have a JWT instead of the old format - auto jwtSep = std::find(signed_token_begin, signed_token_end, '.'); - if (jwtSep != signed_token_end) { - return parseJWT(signed_token, token, error, verifier); - } - - token_base64 = signed_token; - signature_base64 = ""; // This will only ever pass verification if we're - // running without a public key. - } - - // Decode user token - std::vector token_buffer; - token_buffer.resize(base64_decoded_size(token_base64.size())); // Throws - StringData token_2; - { - Optional num_bytes = base64_decode(token_base64, token_buffer); - if (!num_bytes) { - error = ParseError::invalid_base64; - return false; - } - token_2 = StringData{token_buffer.data(), *num_bytes}; - } - - // Decode signature - if (verifier) { - std::vector buffer; - buffer.resize(base64_decoded_size(signature_base64.size())); - Optional num_bytes = base64_decode(signature_base64, buffer); - if (!num_bytes) { - error = ParseError::invalid_base64; - return false; - } - - // Verify signature - BinaryData signature{buffer.data(), *num_bytes}; - bool verified = verifier->verify(BinaryData{token_2.data(), token_2.size()}, - signature); // Throws - if (!verified) { - error = ParseError::invalid_signature; - return false; - } - } - - // Parse decoded user token - JSONParser parser{token_2}; - AccessTokenParser token_parser; - auto json_error = parser.parse(token_parser); - - if (json_error) { - error = ParseError::invalid_json; - return false; - } - - token = std::move(token_parser.m_token); - return true; -} diff --git a/src/realm/sync/noinst/server/access_token.hpp b/src/realm/sync/noinst/server/access_token.hpp deleted file mode 100644 index 9dedb103f7b..00000000000 --- a/src/realm/sync/noinst/server/access_token.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef REALM_SYNC_ACCESS_TOKEN_HPP -#define REALM_SYNC_ACCESS_TOKEN_HPP - -#include -#include -#include - -#include -#include -#include - -namespace realm { -namespace sync { - -using UserIdent = std::string; -using AppIdent = std::string; -using SyncLabel = std::string; -using RealmFileIdent = std::string; // path - -struct AccessToken { - class Verifier; - - enum class ParseError { - none = 0, // std::error_code compatibility - invalid_base64, - invalid_json, - invalid_signature, - invalid_jwt, - }; - - UserIdent identity; - - // If the admin_field is absent in the token, the token is of the old type. - // - // FIXME: Remove this field later. - bool admin_field = false; - bool admin = false; - - AppIdent app_id; - - // The label used for load balancing. It is only used by the server to - // implement the LoadBalancing feature gating. - util::Optional sync_label; - - /// If the access token is missing a 'path' field, the permissions encoded - /// therein are presumed to be valid for ALL paths! I.e. the user is an - /// admin or global listener. - util::Optional path; - - /// The number of seconds since Jan 1 00:00:00 UTC 1970 (UNIX epoch) - /// according to the Gregorian calendar, and while not taking leap seconds - /// into account. This agrees with the definition of UNIX time. For example, - /// 1483257600 means Jan 1 00:00:00 PST 2017. - std::int_fast64_t timestamp = 0; - std::int_fast64_t expires = 0; - - std::uint_least32_t access = 0; // bitfield, see sync::PrivilegeLevel - - bool expired(std::chrono::system_clock::time_point now) const noexcept; - - static bool parseJWT(StringData signed_access_token, AccessToken&, ParseError&, Verifier* = nullptr); - - static bool parse(StringData signed_access_token, AccessToken&, ParseError&, Verifier* = nullptr); -}; - - -class AccessToken::Verifier { -public: - virtual bool verify(BinaryData access_token, BinaryData signature) const = 0; - -protected: - ~Verifier() = default; -}; - - -// Implementation - -inline bool AccessToken::expired(std::chrono::system_clock::time_point now) const noexcept -{ - if (!expires) - return false; - - return now > std::chrono::system_clock::time_point{std::chrono::seconds{expires}}; -} - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_ACCESS_TOKEN_HPP diff --git a/src/realm/sync/noinst/server/clock.hpp b/src/realm/sync/noinst/server/clock.hpp deleted file mode 100644 index 781aa064edb..00000000000 --- a/src/realm/sync/noinst/server/clock.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef REALM_SYNC_CLOCK_HPP -#define REALM_SYNC_CLOCK_HPP - -#include - -namespace realm { -namespace sync { - -class Clock { -public: - using clock = std::chrono::system_clock; - using time_point = clock::time_point; - using duration = clock::duration; - - virtual ~Clock() {} - - /// Implementation must be thread-safe. - virtual time_point now() const noexcept = 0; -}; - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_CLOCK_HPP diff --git a/src/realm/sync/noinst/server/crypto_server.hpp b/src/realm/sync/noinst/server/crypto_server.hpp deleted file mode 100644 index b4d7ee4ae17..00000000000 --- a/src/realm/sync/noinst/server/crypto_server.hpp +++ /dev/null @@ -1,75 +0,0 @@ - -#ifndef REALM_SYNC_CRYPTO_SERVER_HPP -#define REALM_SYNC_CRYPTO_SERVER_HPP - -#include -#include - -#include -#include - -namespace realm { -namespace sync { - -struct CryptoError : std::runtime_error { - CryptoError(std::string message) - : std::runtime_error(std::move(message)) - { - } -}; - -/// This class represents a public/private keypair, or more commonly a single public -/// key used for verifying signatures. -/// -/// Only RSA keys are supported for now. -/// -/// Its methods correspond roughly to the EVP_PKEY_* set of functionality found in -/// the OpenSSL library. -class PKey { -public: - PKey(PKey&&); - PKey& operator=(PKey&&); - ~PKey(); - - /// Load RSA public key from \a pemfile. - static PKey load_public(const std::string& pemfile); - /// Load RSA public key from a PEM buffer - static PKey load_public(BinaryData pem_buffer); - - /// Load RSA public/private keypair from \a pemfile. - static PKey load_private(const std::string& pemfile); - /// Load RSA public/private keypair from a PEM buffer - static PKey load_private(BinaryData pem_buffer); - - /// Whether or not the key can be used for signing. - /// - /// True if the private part is loaded. - bool can_sign() const noexcept; - - /// Whether or not the key can be used for verifying. - /// - /// Always true for RSA keys. - bool can_verify() const noexcept; - - /// Sign \a message with the loaded key, if the private part is - /// loaded. Store the signed message as binary data in \a signature. - /// - /// If a private key is not loaded, throws an exception of type CryptoError. - void sign(BinaryData message, util::Buffer& signature) const; - - /// Verify that \a signature is a valid digest of \a message. - /// - /// Returns true if the signature is valid, otherwise false. If an error occurs while - /// attempting verification, an exception of type CryptoError is thrown. - bool verify(BinaryData message, BinaryData signature) const; - -private: - PKey(); - struct Impl; - std::unique_ptr m_impl; -}; - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_CRYPTO_SERVER_HPP diff --git a/src/realm/sync/noinst/server/crypto_server_apple.mm b/src/realm/sync/noinst/server/crypto_server_apple.mm deleted file mode 100644 index 518b76afdcd..00000000000 --- a/src/realm/sync/noinst/server/crypto_server_apple.mm +++ /dev/null @@ -1,135 +0,0 @@ -#include - -#include -#include -#include - -#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 - -#include -#include -#include -#include - -using namespace realm; -using namespace realm::sync; - -using util::CFPtr; -using util::adoptCF; - -struct PKey::Impl { - CFPtr public_key; - CFPtr private_key; -}; - -PKey::PKey() : m_impl(std::make_unique()) {} - -PKey::PKey(PKey &&) = default; -PKey &PKey::operator=(PKey &&) = default; - -PKey::~PKey() = default; - -static CFPtr load_public_from_data(CFDataRef pem_data) { -#if REALM_MOBILE - static_cast(pem_data); - REALM_UNREACHABLE(); -#else - CFArrayRef itemsCF = nullptr; - auto scope_exit = util::make_scope_exit([&]() noexcept { - if (itemsCF) - CFRelease(itemsCF); - }); - - SecExternalFormat format = kSecFormatPEMSequence; - SecExternalItemType itemType = kSecItemTypePublicKey; - OSStatus status = SecItemImport(pem_data, CFSTR(".pem"), &format, &itemType, 0, nullptr, nullptr, &itemsCF); - if (status != errSecSuccess) { - NSError* error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; - throw CryptoError(std::string("Could not import PEM data: ") + error.localizedDescription.UTF8String); - } - if (CFArrayGetCount(itemsCF) != 1) { - throw CryptoError(std::string("Loading PEM file produced unexpected number of keys.")); - } - SecKeyRef key = static_cast(const_cast(CFArrayGetValueAtIndex(itemsCF, 0))); - if (CFGetTypeID(key) != SecKeyGetTypeID()) { - throw CryptoError(std::string("Loading PEM file produced a key of unexpected type.")); - } - - return util::retainCF(key); -#endif -} - -PKey PKey::load_public(const std::string &pemfile) { - NSData *pem_data = [NSData dataWithContentsOfFile:@(pemfile.c_str())]; - if (!pem_data) { - throw CryptoError(std::string("Could not load PEM file: " + pemfile)); - } - - PKey pkey; - pkey.m_impl->public_key = load_public_from_data((__bridge CFDataRef)pem_data); - return pkey; -} - -PKey PKey::load_public(BinaryData pem_buffer) { - CFPtr pem_data = adoptCF(CFDataCreateWithBytesNoCopy( - kCFAllocatorDefault, reinterpret_cast(pem_buffer.data()), - pem_buffer.size(), kCFAllocatorNull)); - - PKey pkey; - pkey.m_impl->public_key = - load_public_from_data(static_cast(pem_data.get())); - return pkey; -} - -bool PKey::can_sign() const noexcept { - // Signing is not yet implemented. - return false; -} - -bool PKey::can_verify() const noexcept { return bool(m_impl->public_key); } - -bool PKey::verify(BinaryData message, BinaryData signature) const { - if (!can_verify()) { - throw CryptoError{"Cannot verify (no public key)."}; - } - - CFPtr signatureCF = adoptCF(CFDataCreateWithBytesNoCopy( - kCFAllocatorDefault, reinterpret_cast(signature.data()), - signature.size(), kCFAllocatorNull)); - CFPtr messageCF = adoptCF(CFDataCreateWithBytesNoCopy( - kCFAllocatorDefault, reinterpret_cast(message.data()), - message.size(), kCFAllocatorNull)); - if (!signatureCF || !messageCF) { - throw util::bad_alloc(); - } - - CFErrorRef error = nullptr; - if (@available(macOS 10.12, *)) { - bool result = - SecKeyVerifySignature(m_impl->public_key.get(), - kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256, - messageCF.get(), signatureCF.get(), &error); - if (result) { - return true; - } - } else { - // This is now only used in tests, so no need for a fallback for older - // macOS versions. - REALM_TERMINATE("Sync server requires macOS 10.12 or later"); - } - - auto errorCF = adoptCF(error); - auto errorNS = (__bridge NSError *)error; - if ([errorNS.domain isEqualToString:NSOSStatusErrorDomain] && - errorNS.code == errSecVerifyFailed) { - // Valid input, but the signature doesn't match - return false; - } - - std::string description; - @autoreleasepool { - description = util::format("Error verifying message: %1", - errorNS.description.UTF8String); - } - throw CryptoError(description); -} diff --git a/src/realm/sync/noinst/server/crypto_server_openssl.cpp b/src/realm/sync/noinst/server/crypto_server_openssl.cpp deleted file mode 100644 index 559e11cbe2a..00000000000 --- a/src/realm/sync/noinst/server/crypto_server_openssl.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include - -#include -#include -#include - -#if OPENSSL_VERSION_MAJOR >= 3 -#include -#else -#include -#endif - -using namespace realm; -using namespace realm::sync; - -namespace { - -template -std::unique_ptr as_unique_ptr(T* ptr, D&& deleter) -{ - return std::unique_ptr{ptr, std::forward(deleter)}; -} - -} // namespace - -using key_type = std::unique_ptr; - -struct PKey::Impl { - key_type key; - bool both_parts; // true if both public and private key are loaded - - Impl() - : key(nullptr, nullptr) - { - } -}; - -PKey::PKey() - : m_impl(new Impl) -{ - m_impl->key = nullptr; -} - -PKey::PKey(PKey&&) = default; -PKey& PKey::operator=(PKey&&) = default; - -PKey::~PKey() {} - -static key_type load_public_from_bio(BIO* bio) -{ -#if OPENSSL_VERSION_MAJOR >= 3 - EVP_PKEY* pkey = nullptr; - const char* format = "PEM"; /* NULL for any format */ - const char* structure = nullptr; /* any structure */ - const char* key_type = "RSA"; /* NULL for any key */ - auto ctx = as_unique_ptr(OSSL_DECODER_CTX_new_for_pkey(&pkey, format, structure, key_type, - OSSL_KEYMGMT_SELECT_PUBLIC_KEY, nullptr, nullptr), - OSSL_DECODER_CTX_free); - if (!OSSL_DECODER_from_bio(ctx.get(), bio)) { - throw CryptoError{"Error reading RSA key."}; - } - return as_unique_ptr(pkey, EVP_PKEY_free); -#else - pem_password_cb* password_cb = nullptr; // OpenSSL will display a prompt if necessary - void* password_cb_userdata = nullptr; - - void (*rsa_free)(RSA*) = RSA_free; // silences a warning on VS2017 - auto rsa = as_unique_ptr(PEM_read_bio_RSA_PUBKEY(bio, nullptr, password_cb, password_cb_userdata), rsa_free); - if (rsa == nullptr) - throw CryptoError{"Not a valid RSA public key."}; - - void (*evp_pkey_free)(EVP_PKEY*) = EVP_PKEY_free; // silences a warning on VS2017 - key_type key = as_unique_ptr(EVP_PKEY_new(), evp_pkey_free); - if (EVP_PKEY_assign_RSA(key.get(), rsa.get()) == 0) - throw CryptoError{"Error assigning RSA key."}; - rsa.release(); - return key; -#endif -} - -PKey PKey::load_public(const std::string& pemfile) -{ - int (*bio_free)(BIO*) = BIO_free; // silences warning on VS2017 - auto bio = as_unique_ptr(BIO_new_file(pemfile.c_str(), "r"), bio_free); - if (bio == nullptr) - throw CryptoError{std::string("Could not read PEM file: ") + pemfile}; - - PKey result; - result.m_impl->key = load_public_from_bio(bio.get()); - result.m_impl->both_parts = false; - - return result; -} - -PKey PKey::load_public(BinaryData pem_buffer) -{ - std::size_t size = pem_buffer.size(); - int (*bio_free)(BIO*) = BIO_free; // silences a warning on VS2017 - REALM_ASSERT_RELEASE(int(size) <= std::numeric_limits::max()); - auto bio = as_unique_ptr(BIO_new_mem_buf(const_cast(pem_buffer.data()), int(size)), bio_free); - - PKey result; - result.m_impl->key = load_public_from_bio(bio.get()); - result.m_impl->both_parts = false; - - return result; -} - -bool PKey::can_sign() const noexcept -{ - return m_impl->both_parts; -} - -bool PKey::can_verify() const noexcept -{ - return true; -} - -bool PKey::verify(BinaryData message, BinaryData signature) const -{ - if (!can_verify()) { - throw CryptoError{"Cannot verify (no public key)."}; - } - const EVP_MD* digest = EVP_sha256(); - - EVP_MD_CTX* ctx = EVP_MD_CTX_create(); - - EVP_VerifyInit(ctx, digest); - EVP_VerifyUpdate(ctx, message.data(), message.size()); - - const unsigned char* sig = reinterpret_cast(signature.data()); - std::size_t size = signature.size(); - REALM_ASSERT_RELEASE(int(size) <= std::numeric_limits::max()); - int ret = EVP_VerifyFinal(ctx, sig, int(size), m_impl->key.get()); - - EVP_MD_CTX_destroy(ctx); - - if (ret < 0) - throw CryptoError{"Error verifying message."}; - return ret == 1; -} diff --git a/src/realm/sync/noinst/server/crypto_server_stub.cpp b/src/realm/sync/noinst/server/crypto_server_stub.cpp deleted file mode 100644 index 049043de31a..00000000000 --- a/src/realm/sync/noinst/server/crypto_server_stub.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include - -using namespace realm; -using namespace realm::sync; - -struct PKey::Impl {}; - -PKey::PKey() -{ - throw std::runtime_error("PKey not implemented"); -} - -PKey::PKey(PKey&&) = default; -PKey& PKey::operator=(PKey&&) = default; - -PKey::~PKey() = default; - -PKey PKey::load_public(const std::string&) -{ - throw std::runtime_error("PKey not implemented"); -} - -PKey PKey::load_public(BinaryData) -{ - throw std::runtime_error("PKey not implemented"); -} - -PKey PKey::load_private(const std::string&) -{ - throw std::runtime_error("PKey not implemented"); -} - -PKey PKey::load_private(BinaryData) -{ - throw std::runtime_error("PKey not implemented"); -} - -bool PKey::can_sign() const noexcept -{ - return false; -} - -bool PKey::can_verify() const noexcept -{ - return false; -} - -void PKey::sign(BinaryData, util::Buffer&) const {} - -bool PKey::verify(BinaryData, BinaryData) const -{ - return false; -} diff --git a/src/realm/sync/noinst/server/permissions.hpp b/src/realm/sync/noinst/server/permissions.hpp deleted file mode 100644 index 645a360bc5c..00000000000 --- a/src/realm/sync/noinst/server/permissions.hpp +++ /dev/null @@ -1,121 +0,0 @@ - -#ifndef REALM_SYNC_PERMISSIONS_HPP -#define REALM_SYNC_PERMISSIONS_HPP - -#include - -namespace realm { -namespace sync { - -/// The Privilege enum is intended to be used in a bitfield. -enum class Privilege : uint_least32_t { - None = 0, - - /// The user can read the object (i.e. it can participate in the user's - /// subscription. - /// - /// NOTE: On objects, it is a prerequisite that the object's class is also - /// readable by the user. - /// - /// FIXME: Until we get asynchronous links, any object that is reachable - /// through links from another readable/queryable object is also readable, - /// regardless of whether the user specifically does not have read access. - Read = 1, - - /// The user can modify the fields of the object. - /// - /// NOTE: On objects, it is a prerequisite that the object's class is also - /// updatable by the user. When applied to a Class object, it does not - /// imply that the user can modify the schema of the class, only the - /// objects of that class. - /// - /// NOTE: This does not imply the SetPermissions privilege. - Update = 2, - - /// The user can delete the object. - /// - /// NOTE: When applied to a Class object, it has no effect on whether - /// objects of that class can be deleted by the user. - /// - /// NOTE: This implies the ability to implicitly nullify links pointing - /// to the object from other objects, even if the user does not have - /// permission to modify those objects in the normal way. - Delete = 4, - - //@{ - /// The user can modify the object's permissions. - /// - /// NOTE: The user will only be allowed to assign permissions at or below - /// their own privilege level. - SetPermissions = 8, - Share = SetPermissions, - //@} - - /// When applied to a Class object, the user can query objects in that - /// class. - /// - /// Has no effect when applied to objects other than Class. - Query = 16, - - /// When applied to a Class object, the user may create objects in that - /// class. - /// - /// NOTE: The user implicitly has Update and SetPermissions - /// (but not necessarily Delete permission) within the same - /// transaction as the object was created. - /// - /// NOTE: Even when a user has CreateObject rights, a CreateObject - /// operation may still be rejected by the server, if the object has a - /// primary key and the object already exists, but is not accessible by the - /// user. - Create = 32, - - /// When applied as a "Realm" privilege, the user can add classes and add - /// columns to classes. - /// - /// NOTE: When applied to a class or object, this has no effect. - ModifySchema = 64, - - /// - /// Aggregate permissions for compatibility: - /// - Download = Read | Query, - Upload = Update | Delete | Create, - DeleteRealm = Upload, // FIXME: This seems overly permissive -}; - -inline constexpr uint_least32_t operator|(Privilege a, Privilege b) -{ - return static_cast(a) | static_cast(b); -} - -inline constexpr uint_least32_t operator|(uint_least32_t a, Privilege b) -{ - return a | static_cast(b); -} - -inline constexpr uint_least32_t operator&(Privilege a, Privilege b) -{ - return static_cast(a) & static_cast(b); -} - -inline constexpr uint_least32_t operator&(uint_least32_t a, Privilege b) -{ - return a & static_cast(b); -} - -inline uint_least32_t& operator|=(uint_least32_t& a, Privilege b) -{ - return a |= static_cast(b); -} - -inline constexpr uint_least32_t operator~(Privilege p) -{ - return ~static_cast(p); -} - -} // namespace sync -} // namespace realm - - -#endif // REALM_SYNC_PERMISSIONS_HPP diff --git a/src/realm/sync/noinst/server/server.cpp b/src/realm/sync/noinst/server/server.cpp deleted file mode 100644 index 2aa26b47209..00000000000 --- a/src/realm/sync/noinst/server/server.cpp +++ /dev/null @@ -1,4844 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// NOTE: The protocol specification is in `/doc/protocol.md` - - -// FIXME: Verify that session identifier spoofing cannot be used to get access -// to sessions belonging to other network conections in any way. -// FIXME: Seems that server must close connection with zero sessions after a -// certain timeout. - - -using namespace realm; -using namespace realm::sync; -using namespace realm::util; - -// clang-format off -using ServerHistory = _impl::ServerHistory; -using ServerProtocol = _impl::ServerProtocol; -using ServerFileAccessCache = _impl::ServerFileAccessCache; -using ServerImplBase = _impl::ServerImplBase; - -using IntegratableChangeset = ServerHistory::IntegratableChangeset; -using IntegratableChangesetList = ServerHistory::IntegratableChangesetList; -using IntegratableChangesets = ServerHistory::IntegratableChangesets; -using IntegrationResult = ServerHistory::IntegrationResult; -using BootstrapError = ServerHistory::BootstrapError; -using ExtendedIntegrationError = ServerHistory::ExtendedIntegrationError; -using ClientType = ServerHistory::ClientType; -using FileIdentAllocSlot = ServerHistory::FileIdentAllocSlot; -using FileIdentAllocSlots = ServerHistory::FileIdentAllocSlots; - -using UploadChangeset = ServerProtocol::UploadChangeset; -// clang-format on - - -using UploadChangesets = std::vector; - -using EventLoopMetricsHandler = network::Service::EventLoopMetricsHandler; - - -static_assert(std::numeric_limits::digits >= 63, "Bad session identifier type"); -static_assert(std::numeric_limits::digits >= 63, "Bad file identifier type"); -static_assert(std::numeric_limits::digits >= 63, "Bad version type"); -static_assert(std::numeric_limits::digits >= 63, "Bad timestamp type"); - - -namespace { - -enum class SchedStatus { done = 0, pending, in_progress }; - -// Only used by the Sync Server to support older pbs sync clients (prior to protocol v8) -constexpr std::string_view get_old_pbs_websocket_protocol_prefix() noexcept -{ - return "com.mongodb.realm-sync/"; -} - -std::string short_token_fmt(const std::string& str, size_t cutoff = 30) -{ - if (str.size() > cutoff) { - return "..." + str.substr(str.size() - cutoff); - } - else { - return str; - } -} - - -class HttpListHeaderValueParser { -public: - HttpListHeaderValueParser(std::string_view string) noexcept - : m_string{string} - { - } - bool next(std::string_view& elem) noexcept - { - while (m_pos < m_string.size()) { - size_type i = m_pos; - size_type j = m_string.find(',', i); - if (j != std::string_view::npos) { - m_pos = j + 1; - } - else { - j = m_string.size(); - m_pos = j; - } - - // Exclude leading and trailing white space - while (i < j && is_http_lws(m_string[i])) - ++i; - while (j > i && is_http_lws(m_string[j - 1])) - --j; - - if (i != j) { - elem = m_string.substr(i, j - i); - return true; - } - } - return false; - } - -private: - using size_type = std::string_view::size_type; - const std::string_view m_string; - size_type m_pos = 0; - static bool is_http_lws(char ch) noexcept - { - return (ch == '\t' || ch == '\n' || ch == '\r' || ch == ' '); - } -}; - - -using SteadyClock = std::conditional::type; -using SteadyTimePoint = SteadyClock::time_point; - -SteadyTimePoint steady_clock_now() noexcept -{ - return SteadyClock::now(); -} - -milliseconds_type steady_duration(SteadyTimePoint start_time, SteadyTimePoint end_time = steady_clock_now()) noexcept -{ - auto duration = end_time - start_time; - auto millis_duration = std::chrono::duration_cast(duration).count(); - return milliseconds_type(millis_duration); -} - - -bool determine_try_again(ProtocolError error_code) noexcept -{ - return (error_code == ProtocolError::connection_closed); -} - - -class ServerFile; -class ServerImpl; -class HTTPConnection; -class SyncConnection; -class Session; - - -using Formatter = util::ResettableExpandableBufferOutputStream; -using OutputBuffer = util::ResettableExpandableBufferOutputStream; - -using ProtocolVersionRange = std::pair; - -class MiscBuffers { -public: - Formatter formatter; - OutputBuffer download_message; - - using ProtocolVersionRanges = std::vector; - ProtocolVersionRanges protocol_version_ranges; - - std::vector compress; - - MiscBuffers() - { - formatter.imbue(std::locale::classic()); - download_message.imbue(std::locale::classic()); - } -}; - - -struct DownloadCache { - std::unique_ptr body; - std::size_t uncompressed_body_size; - std::size_t compressed_body_size; - bool body_is_compressed; - version_type end_version; - DownloadCursor download_progress; - std::uint_fast64_t downloadable_bytes; - std::size_t num_changesets; - std::size_t accum_original_size; - std::size_t accum_compacted_size; -}; - - -// An unblocked work unit is comprised of one Work object for each of the files -// that contribute work to the work unit, generally one reference file and a -// number of partial files. -class Work { -public: - // In general, primary work is all forms of modifying work, including file - // deletion. - bool has_primary_work = false; - - // Only for reference files - bool might_produce_new_sync_version = false; - - bool produced_new_realm_version = false; - bool produced_new_sync_version = false; - bool expired_reference_version = false; - - // True if, and only if changesets_from_downstream contains at least one - // changeset. - bool have_changesets_from_downstream = false; - - FileIdentAllocSlots file_ident_alloc_slots; - std::vector> changeset_buffers; - IntegratableChangesets changesets_from_downstream; - - VersionInfo version_info; - - // Result of integration of changesets from downstream clients - IntegrationResult integration_result; - - void reset() noexcept - { - has_primary_work = false; - - might_produce_new_sync_version = false; - - produced_new_realm_version = false; - produced_new_sync_version = false; - expired_reference_version = false; - have_changesets_from_downstream = false; - - file_ident_alloc_slots.clear(); - changeset_buffers.clear(); - changesets_from_downstream.clear(); - - version_info = {}; - integration_result = {}; - } -}; - - -class WorkerState { -public: - FileIdentAllocSlots file_ident_alloc_slots; - util::ScratchMemory scratch_memory; - bool use_file_cache = true; - std::unique_ptr reference_hist; - DBRef reference_sg; -}; - - -// ============================ SessionQueue ============================ - -class SessionQueue { -public: - void push_back(Session*) noexcept; - Session* pop_front() noexcept; - void clear() noexcept; - -private: - Session* m_back = nullptr; -}; - - -// ============================ FileIdentReceiver ============================ - -class FileIdentReceiver { -public: - virtual void receive_file_ident(SaltedFileIdent) = 0; - -protected: - ~FileIdentReceiver() {} -}; - - -// ============================ WorkerBox ============================= - -class WorkerBox { -public: - using JobType = util::UniqueFunction; - void add_work(WorkerState& state, JobType job) - { - std::unique_lock lock(m_mutex); - if (m_jobs.size() >= m_queue_limit) { - // Once we have many queued jobs, it is better to use this thread to run a new job - // than to queue it. - run_a_job(lock, state, job); - } - else { - // Create worker threads on demand (if all existing threads are active): - if (m_threads.size() < m_max_num_threads && m_active >= m_threads.size()) { - m_threads.emplace_back([this]() { - WorkerState state; - state.use_file_cache = false; - JobType the_job; - std::unique_lock lock(m_mutex); - for (;;) { - while (m_jobs.empty() && !m_finish_up) - m_changes.wait(lock); - if (m_finish_up) - break; // terminate thread - the_job = std::move(m_jobs.back()); - m_jobs.pop_back(); - run_a_job(lock, state, the_job); - m_changes.notify_all(); - } - }); - } - - // Submit the job for execution: - m_jobs.emplace_back(std::move(job)); - m_changes.notify_all(); - } - } - - // You should call wait_completion() before trying to destroy a WorkerBox to get proper - // propagation of exceptions. - void wait_completion(WorkerState& state) - { - std::unique_lock lock(m_mutex); - while (!m_jobs.empty() || m_active > 0) { - if (!m_jobs.empty()) { // if possible, make this thread participate in running m_jobs - JobType the_job = std::move(m_jobs.back()); - m_jobs.pop_back(); - run_a_job(lock, state, the_job); - } - else { - m_changes.wait(lock); - } - } - if (m_epr) { - std::rethrow_exception(m_epr); - } - } - - WorkerBox(unsigned int num_threads) - { - m_queue_limit = num_threads * 10; // fudge factor for job size variation - m_max_num_threads = num_threads; - } - - ~WorkerBox() - { - { - std::unique_lock lock(m_mutex); - m_finish_up = true; - m_changes.notify_all(); - } - for (auto& e : m_threads) - e.join(); - } - -private: - std::mutex m_mutex; - std::condition_variable m_changes; - std::vector m_threads; - std::vector m_jobs; - unsigned int m_active = 0; - bool m_finish_up = false; - unsigned int m_queue_limit = 0; - unsigned int m_max_num_threads = 0; - std::exception_ptr m_epr; - - void run_a_job(std::unique_lock& lock, WorkerState& state, JobType& job) - { - ++m_active; - lock.unlock(); - try { - job(state); - lock.lock(); - } - catch (...) { - lock.lock(); - if (!m_epr) - m_epr = std::current_exception(); - } - --m_active; - } -}; - - -// ============================ ServerFile ============================ - -class ServerFile : public util::RefCountBase { -public: - util::PrefixLogger logger; - - // Logger to be used by the worker thread - util::PrefixLogger wlogger; - - ServerFile(ServerImpl& server, ServerFileAccessCache& cache, const std::string& virt_path, std::string real_path, - bool disable_sync_to_disk); - ~ServerFile() noexcept; - - void initialize(); - void activate(); - - ServerImpl& get_server() noexcept - { - return m_server; - } - - const std::string& get_real_path() const noexcept - { - return m_file.realm_path; - } - - const std::string& get_virt_path() const noexcept - { - return m_file.virt_path; - } - - ServerFileAccessCache::File& access() - { - return m_file.access(); // Throws - } - - ServerFileAccessCache::File& worker_access() - { - return m_worker_file.access(); // Throws - } - - version_type get_realm_version() const noexcept - { - return m_version_info.realm_version; - } - - version_type get_sync_version() const noexcept - { - return m_version_info.sync_version.version; - } - - SaltedVersion get_salted_sync_version() const noexcept - { - return m_version_info.sync_version; - } - - DownloadCache& get_download_cache() noexcept; - - void register_client_access(file_ident_type client_file_ident); - - using file_ident_request_type = std::int_fast64_t; - - // Initiate a request for a new client file identifier. - // - // Unless the request is cancelled, the identifier will be delivered to the - // receiver by way of an invocation of - // FileIdentReceiver::receive_file_ident(). - // - // FileIdentReceiver::receive_file_ident() is guaranteed to not be called - // until after request_file_ident() has returned (no callback reentrance). - // - // New client file identifiers will be delivered to receivers in the order - // that they were requested. - // - // The returned value is a nonzero integer that can be used to cancel the - // request before the file identifier is delivered using - // cancel_file_ident_request(). - auto request_file_ident(FileIdentReceiver&, file_ident_type proxy_file, ClientType) -> file_ident_request_type; - - // Cancel the specified file identifier request. - // - // It is an error to call this function after the identifier has been - // delivered. - void cancel_file_ident_request(file_ident_request_type) noexcept; - - void add_unidentified_session(Session*); - void identify_session(Session*, file_ident_type client_file_ident); - - void remove_unidentified_session(Session*) noexcept; - void remove_identified_session(file_ident_type client_file_ident) noexcept; - - Session* get_identified_session(file_ident_type client_file_ident) noexcept; - - bool can_add_changesets_from_downstream() const noexcept; - void add_changesets_from_downstream(file_ident_type client_file_ident, UploadCursor upload_progress, - version_type locked_server_version, const UploadChangeset*, - std::size_t num_changesets); - - // bootstrap_client_session calls the function of same name in server_history - // but corrects the upload_progress with information from pending - // integratable changesets. A situation can occur where a client terminates - // a session and starts a new session and re-uploads changesets that are known - // by the ServerFile object but not by the ServerHistory. - BootstrapError bootstrap_client_session(SaltedFileIdent client_file_ident, DownloadCursor download_progress, - SaltedVersion server_version, ClientType client_type, - UploadCursor& upload_progress, version_type& locked_server_version, - Logger&); - - // NOTE: This function is executed by the worker thread - void worker_process_work_unit(WorkerState&); - - void recognize_external_change(); - -private: - ServerImpl& m_server; - ServerFileAccessCache::Slot m_file; - - // In general, `m_version_info` refers to the last snapshot of the Realm - // file that is supposed to be visible to remote peers engaging in regular - // Realm file synchronization. - VersionInfo m_version_info; - - file_ident_request_type m_last_file_ident_request = 0; - - // The set of sessions whose client file identifier is not yet known, i.e., - // those for which an IDENT message has not yet been received, - std::set m_unidentified_sessions; - - // A map of the sessions whose client file identifier is known, i.e, those - // for which an IDENT message has been received. - std::map m_identified_sessions; - - // Used when a file used as partial view wants to allocate a client file - // identifier from the reference Realm. - file_ident_request_type m_file_ident_request = 0; - - struct FileIdentRequestInfo { - FileIdentReceiver* receiver; - file_ident_type proxy_file; - ClientType client_type; - }; - - // When nonempty, it counts towards outstanding blocked work (see - // `m_has_blocked_work`). - std::map m_file_ident_requests; - - // Changesets received from the downstream clients, and waiting to be - // integrated, as well as information about the clients progress in terms of - // integrating changesets received from the server. When nonempty, it counts - // towards outstanding blocked work (see `m_has_blocked_work`). - // - // At any given time, the set of changesets from a particular client-side - // file may be comprised of changesets received via distinct sessions. - // - // See also `m_num_changesets_from_downstream`. - IntegratableChangesets m_changesets_from_downstream; - - // Keeps track of the number of changesets in `m_changesets_from_downstream`. - // - // Its purpose is also to initialize - // `Work::have_changesets_from_downstream`. - std::size_t m_num_changesets_from_downstream = 0; - - // The total size, in bytes, of the changesets that were received from - // clients, are targeting this file, and are currently part of the blocked - // work unit. - // - // Together with `m_unblocked_changesets_from_downstream_byte_size`, its - // purpose is to allow the server to keep track of the accumulated size of - // changesets being processed, or waiting to be processed (metric - // `upload.pending.bytes`) (see - // ServerImpl::inc_byte_size_for_pending_downstream_changesets()). - // - // Its purpose is also to enable the "very poor man's" backpressure solution - // (see can_add_changesets_from_downstream()). - std::size_t m_blocked_changesets_from_downstream_byte_size = 0; - - // Same as `m_blocked_changesets_from_downstream_byte_size` but for the - // currently unblocked work unit. - std::size_t m_unblocked_changesets_from_downstream_byte_size = 0; - - // When nonempty, it counts towards outstanding blocked work (see - // `m_has_blocked_work`). - std::vector m_permission_changes; - - // True iff this file, or any of its associated partial files (when - // applicable), has a nonzero amount of outstanding work that is currently - // held back from being passed to the worker thread because a previously - // accumulated chunk of work related to this file is currently in progress. - bool m_has_blocked_work = false; - - // A file, that is not a partial file, is considered *exposed to the worker - // thread* from the point in time where it is submitted to the worker - // (Worker::enqueue()) and up until the point in time where - // group_postprocess_stage_1() starts to execute. A partial file is - // considered *exposed to the worker thread* precisely when the associated - // reference file is exposed to the worker thread, but only if it was in - // `m_reference_file->m_work.partial_files` at the point in time where the - // reference file was passed to the worker. - // - // While this file is exposed to the worker thread, all members of `m_work` - // other than `changesets_from_downstream` may be accessed and modified by - // the worker thread only. - // - // While this file is exposed to the worker thread, - // `m_work.changesets_from_downstream` may be accessed by all threads, but - // must not be modified by any thread. This special status of - // `m_work.changesets_from_downstream` is required to allow - // ServerFile::bootstrap_client_session() to read from it at any time. - Work m_work; - - // For reference files, set to true when work is unblocked, and reset back - // to false when the work finalization process completes - // (group_postprocess_stage_3()). Always zero for partial files. - bool m_has_work_in_progress = 0; - - // This one must only be accessed by the worker thread. - // - // More specifically, `m_worker_file.access()` must only be called by the - // worker thread, and if it was ever called, it must be closed by the worker - // thread before the ServerFile object is destroyed, if destruction happens - // before the destruction of the server object itself. - ServerFileAccessCache::Slot m_worker_file; - - std::vector m_deleting_connections; - - DownloadCache m_download_cache; - - void on_changesets_from_downstream_added(std::size_t num_changesets, std::size_t num_bytes); - void on_work_added(); - void group_unblock_work(); - void unblock_work(); - - /// Resume history scanning in all sessions bound to this file. To be called - /// after a successfull integration of a changeset. - void resume_download() noexcept; - - // NOTE: These functions are executed by the worker thread - void worker_allocate_file_identifiers(); - bool worker_integrate_changes_from_downstream(WorkerState&); - ServerHistory& get_client_file_history(WorkerState& state, std::unique_ptr& hist_ptr, - DBRef& sg_ptr); - ServerHistory& get_reference_file_history(WorkerState& state); - void group_postprocess_stage_1(); - void group_postprocess_stage_2(); - void group_postprocess_stage_3(); - void group_finalize_work_stage_1(); - void group_finalize_work_stage_2(); - void finalize_work_stage_1(); - void finalize_work_stage_2(); -}; - - -inline DownloadCache& ServerFile::get_download_cache() noexcept -{ - return m_download_cache; -} - -inline void ServerFile::group_finalize_work_stage_1() -{ - finalize_work_stage_1(); // Throws -} - -inline void ServerFile::group_finalize_work_stage_2() -{ - finalize_work_stage_2(); // Throws -} - - -// ============================ Worker ============================ - -// All write transaction on server-side Realm files performed on behalf of the -// server, must be performed by the worker thread, not the network event loop -// thread. This is to ensure that the network event loop thread never gets -// blocked waiting for the worker thread to end a long running write -// transaction. -// -// FIXME: Currently, the event loop thread does perform a number of write -// transactions, but only on subtier nodes of a star topology server cluster. -class Worker : public ServerHistory::Context { -public: - std::shared_ptr logger_ptr; - util::Logger& logger; - - explicit Worker(ServerImpl&); - - ServerFileAccessCache& get_file_access_cache() noexcept; - - void enqueue(ServerFile*); - - // Overriding members of ServerHistory::Context - std::mt19937_64& server_history_get_random() noexcept override final; - -private: - ServerImpl& m_server; - std::mt19937_64 m_random; - ServerFileAccessCache m_file_access_cache; - - util::Mutex m_mutex; - util::CondVar m_cond; // Protected by `m_mutex` - - bool m_stop = false; // Protected by `m_mutex` - - util::CircularBuffer m_queue; // Protected by `m_mutex` - - WorkerState m_state; - - void run(); - void stop() noexcept; - - friend class util::ThreadExecGuardWithParent; -}; - - -inline ServerFileAccessCache& Worker::get_file_access_cache() noexcept -{ - return m_file_access_cache; -} - - -// ============================ ServerImpl ============================ - -class ServerImpl : public ServerImplBase, public ServerHistory::Context { -public: - std::uint_fast64_t errors_seen = 0; - - std::atomic m_par_time; - std::atomic m_seq_time; - - util::Mutex last_client_accesses_mutex; - - const std::shared_ptr logger_ptr; - util::Logger& logger; - - network::Service& get_service() noexcept - { - return m_service; - } - - const network::Service& get_service() const noexcept - { - return m_service; - } - - std::mt19937_64& get_random() noexcept - { - return m_random; - } - - const Server::Config& get_config() const noexcept - { - return m_config; - } - - std::size_t get_max_upload_backlog() const noexcept - { - return m_max_upload_backlog; - } - - const std::string& get_root_dir() const noexcept - { - return m_root_dir; - } - - network::ssl::Context& get_ssl_context() noexcept - { - return *m_ssl_context; - } - - const AccessControl& get_access_control() const noexcept - { - return m_access_control; - } - - ProtocolVersionRange get_protocol_version_range() const noexcept - { - return m_protocol_version_range; - } - - ServerProtocol& get_server_protocol() noexcept - { - return m_server_protocol; - } - - compression::CompressMemoryArena& get_compress_memory_arena() noexcept - { - return m_compress_memory_arena; - } - - MiscBuffers& get_misc_buffers() noexcept - { - return m_misc_buffers; - } - - int_fast64_t get_current_server_session_ident() const noexcept - { - return m_current_server_session_ident; - } - - util::ScratchMemory& get_scratch_memory() noexcept - { - return m_scratch_memory; - } - - Worker& get_worker() noexcept - { - return m_worker; - } - - void get_workunit_timers(milliseconds_type& parallel_section, milliseconds_type& sequential_section) - { - parallel_section = m_par_time; - sequential_section = m_seq_time; - } - - ServerImpl(const std::string& root_dir, util::Optional, Server::Config); - ~ServerImpl() noexcept; - - void start(); - - void start(std::string listen_address, std::string listen_port, bool reuse_address) - { - m_config.listen_address = listen_address; - m_config.listen_port = listen_port; - m_config.reuse_address = reuse_address; - - start(); // Throws - } - - network::Endpoint listen_endpoint() const - { - return m_acceptor.local_endpoint(); - } - - void run(); - void stop() noexcept; - - void remove_http_connection(std::int_fast64_t conn_id) noexcept; - - void add_sync_connection(int_fast64_t connection_id, std::unique_ptr&& sync_conn); - void remove_sync_connection(int_fast64_t connection_id); - - size_t get_number_of_http_connections() - { - return m_http_connections.size(); - } - - size_t get_number_of_sync_connections() - { - return m_sync_connections.size(); - } - - bool is_sync_stopped() - { - return m_sync_stopped; - } - - const std::set& get_realm_names() const noexcept - { - return m_realm_names; - } - - // virt_path must be valid when get_or_create_file() is called. - util::bind_ptr get_or_create_file(const std::string& virt_path) - { - util::bind_ptr file = get_file(virt_path); - if (REALM_LIKELY(file)) - return file; - - _impl::VirtualPathComponents virt_path_components = - _impl::parse_virtual_path(m_root_dir, virt_path); // Throws - REALM_ASSERT(virt_path_components.is_valid); - - _impl::make_dirs(m_root_dir, virt_path); // Throws - m_realm_names.insert(virt_path); // Throws - { - bool disable_sync_to_disk = m_config.disable_sync_to_disk; - file.reset(new ServerFile(*this, m_file_access_cache, virt_path, virt_path_components.real_realm_path, - disable_sync_to_disk)); // Throws - } - - file->initialize(); - m_files[virt_path] = file; // Throws - file->activate(); // Throws - return file; - } - - std::unique_ptr make_history_for_path() - { - return std::make_unique(*this); - } - - util::bind_ptr get_file(const std::string& virt_path) noexcept - { - auto i = m_files.find(virt_path); - if (REALM_LIKELY(i != m_files.end())) - return i->second; - return {}; - } - - // Returns the number of seconds since the Epoch of - // std::chrono::system_clock. - std::chrono::system_clock::time_point token_expiration_clock_now() const noexcept - { - if (REALM_UNLIKELY(m_config.token_expiration_clock)) - return m_config.token_expiration_clock->now(); - return std::chrono::system_clock::now(); - } - - void set_connection_reaper_timeout(milliseconds_type); - - void close_connections(); - bool map_virtual_to_real_path(const std::string& virt_path, std::string& real_path); - - void recognize_external_change(const std::string& virt_path); - - void stop_sync_and_wait_for_backup_completion(util::UniqueFunction completion_handler, - milliseconds_type timeout); - - // Server global outputbuffers that can be reused. - // The server is single threaded, so there are no - // synchronization issues. - // output_buffers_count is equal to the - // maximum number of buffers needed at any point. - static constexpr int output_buffers_count = 1; - OutputBuffer output_buffers[output_buffers_count]; - - bool is_load_balancing_allowed() const - { - return m_allow_load_balancing; - } - - // inc_byte_size_for_pending_downstream_changesets() must be called by - // ServerFile objects when changesets from downstream clients have been - // received. - // - // dec_byte_size_for_pending_downstream_changesets() must be called by - // ServerFile objects when changesets from downstream clients have been - // processed or discarded. - // - // ServerImpl uses this information to keep a running tally (metric - // `upload.pending.bytes`) of the total byte size of pending changesets from - // downstream clients. - // - // These functions must be called on the network thread. - void inc_byte_size_for_pending_downstream_changesets(std::size_t byte_size); - void dec_byte_size_for_pending_downstream_changesets(std::size_t byte_size); - - // Overriding member functions in _impl::ServerHistory::Context - std::mt19937_64& server_history_get_random() noexcept override final; - -private: - Server::Config m_config; - network::Service m_service; - std::mt19937_64 m_random; - const std::size_t m_max_upload_backlog; - const std::string m_root_dir; - const AccessControl m_access_control; - const ProtocolVersionRange m_protocol_version_range; - - // The reserved files will be closed in situations where the server - // runs out of file descriptors. - std::unique_ptr m_reserved_files[5]; - - // The set of all Realm files known to this server, represented by their - // virtual path. - // - // INVARIANT: If a Realm file is in the servers directory (i.e., it would be - // reported by an invocation of _impl::get_realm_names()), then the - // corresponding virtual path is in `m_realm_names`, assuming no external - // file-system level intervention. - std::set m_realm_names; - - std::unique_ptr m_ssl_context; - ServerFileAccessCache m_file_access_cache; - Worker m_worker; - std::map> m_files; // Key is virtual path - network::Acceptor m_acceptor; - std::int_fast64_t m_next_conn_id = 0; - std::unique_ptr m_next_http_conn; - network::Endpoint m_next_http_conn_endpoint; - std::map> m_http_connections; - std::map> m_sync_connections; - ServerProtocol m_server_protocol; - compression::CompressMemoryArena m_compress_memory_arena; - MiscBuffers m_misc_buffers; - int_fast64_t m_current_server_session_ident; - Optional m_connection_reaper_timer; - bool m_allow_load_balancing = false; - - util::Mutex m_mutex; - - bool m_stopped = false; // Protected by `m_mutex` - - // m_sync_stopped is used by stop_sync_and_wait_for_backup_completion(). - // When m_sync_stopped is true, the server does not perform any sync. - bool m_sync_stopped = false; - - std::atomic m_running{false}; // Debugging facility - - std::size_t m_pending_changesets_from_downstream_byte_size = 0; - - util::CondVar m_wait_or_service_stopped_cond; // Protected by `m_mutex` - - util::ScratchMemory m_scratch_memory; - - void listen(); - void initiate_accept(); - void handle_accept(std::error_code); - - void reap_connections(); - void initiate_connection_reaper_timer(milliseconds_type timeout); - void do_close_connections(); - - static std::size_t determine_max_upload_backlog(Server::Config& config) noexcept - { - if (config.max_upload_backlog == 0) - return 4294967295; // 4GiB - 1 (largest allowable number on a 32-bit platform) - return config.max_upload_backlog; - } - - static ProtocolVersionRange determine_protocol_version_range(Server::Config& config) - { - const int actual_min = ServerImplBase::get_oldest_supported_protocol_version(); - const int actual_max = get_current_protocol_version(); - static_assert(actual_min <= actual_max, ""); - int min = actual_min; - int max = actual_max; - if (config.max_protocol_version != 0 && config.max_protocol_version < max) { - if (config.max_protocol_version < min) - throw Server::NoSupportedProtocolVersions(); - max = config.max_protocol_version; - } - return {min, max}; - } - - void do_recognize_external_change(const std::string& virt_path); - - void do_stop_sync_and_wait_for_backup_completion(util::UniqueFunction completion_handler, - milliseconds_type timeout); -}; - -// ============================ SyncConnection ============================ - -class SyncConnection : public websocket::Config { -public: - const std::shared_ptr logger_ptr; - util::Logger& logger; - - // Clients with sync protocol version 8 or greater support pbs->flx migration - static constexpr int PBS_FLX_MIGRATION_PROTOCOL_VERSION = 8; - // Clients with sync protocol version less than 10 do not support log messages - static constexpr int SERVER_LOG_PROTOCOL_VERSION = 10; - - SyncConnection(ServerImpl& serv, std::int_fast64_t id, std::unique_ptr&& socket, - std::unique_ptr&& ssl_stream, - std::unique_ptr&& read_ahead_buffer, int client_protocol_version, - std::string client_user_agent, std::string remote_endpoint, std::string appservices_request_id) - : logger_ptr{std::make_shared(util::LogCategory::server, make_logger_prefix(id), - serv.logger_ptr)} // Throws - , logger{*logger_ptr} - , m_server{serv} - , m_id{id} - , m_socket{std::move(socket)} - , m_ssl_stream{std::move(ssl_stream)} - , m_read_ahead_buffer{std::move(read_ahead_buffer)} - , m_websocket{*this} - , m_client_protocol_version{client_protocol_version} - , m_client_user_agent{std::move(client_user_agent)} - , m_remote_endpoint{std::move(remote_endpoint)} - , m_appservices_request_id{std::move(appservices_request_id)} - { - // Make the output buffer stream throw std::bad_alloc if it fails to - // expand the buffer - m_output_buffer.exceptions(std::ios_base::badbit | std::ios_base::failbit); - - network::Service& service = m_server.get_service(); - auto handler = [this](Status status) { - if (!status.is_ok()) - return; - if (!m_is_sending) - send_next_message(); // Throws - }; - m_send_trigger = std::make_unique>(&service, std::move(handler)); // Throws - } - - ~SyncConnection() noexcept; - - ServerImpl& get_server() noexcept - { - return m_server; - } - - ServerProtocol& get_server_protocol() noexcept - { - return m_server.get_server_protocol(); - } - - int get_client_protocol_version() - { - return m_client_protocol_version; - } - - const std::string& get_client_user_agent() const noexcept - { - return m_client_user_agent; - } - - const std::string& get_remote_endpoint() const noexcept - { - return m_remote_endpoint; - } - - const std::shared_ptr& websocket_get_logger() noexcept final - { - return logger_ptr; - } - - std::mt19937_64& websocket_get_random() noexcept final override - { - return m_server.get_random(); - } - - bool websocket_binary_message_received(const char* data, size_t size) final override - { - using sf = _impl::SimulatedFailure; - if (sf::check_trigger(sf::sync_server__read_head)) { - // Suicide - read_error(sf::sync_server__read_head); - return false; - } - // After a connection level error has occurred, all incoming messages - // will be ignored. By continuing to read until end of input, the server - // is able to know when the client closes the connection, which in - // general means that is has received the ERROR message. - if (REALM_LIKELY(!m_is_closing)) { - m_last_activity_at = steady_clock_now(); - handle_message_received(data, size); - } - return true; - } - - bool websocket_ping_message_received(const char* data, size_t size) final override - { - if (REALM_LIKELY(!m_is_closing)) { - m_last_activity_at = steady_clock_now(); - handle_ping_received(data, size); - } - return true; - } - - void async_write(const char* data, size_t size, websocket::WriteCompletionHandler handler) final override - { - if (m_ssl_stream) { - m_ssl_stream->async_write(data, size, std::move(handler)); // Throws - } - else { - m_socket->async_write(data, size, std::move(handler)); // Throws - } - } - - void async_read(char* buffer, size_t size, websocket::ReadCompletionHandler handler) final override - { - if (m_ssl_stream) { - m_ssl_stream->async_read(buffer, size, *m_read_ahead_buffer, std::move(handler)); // Throws - } - else { - m_socket->async_read(buffer, size, *m_read_ahead_buffer, std::move(handler)); // Throws - } - } - - void async_read_until(char* buffer, size_t size, char delim, - websocket::ReadCompletionHandler handler) final override - { - if (m_ssl_stream) { - m_ssl_stream->async_read_until(buffer, size, delim, *m_read_ahead_buffer, - std::move(handler)); // Throws - } - else { - m_socket->async_read_until(buffer, size, delim, *m_read_ahead_buffer, - std::move(handler)); // Throws - } - } - - void websocket_read_error_handler(std::error_code ec) final override - { - read_error(ec); - } - - void websocket_write_error_handler(std::error_code ec) final override - { - write_error(ec); - } - - void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, std::string_view) final override - { - // WebSocket class has already logged a message for this error - close_due_to_error(ec); // Throws - } - - void websocket_protocol_error_handler(std::error_code ec) final override - { - logger.error("WebSocket protocol error (%1): %2", ec, ec.message()); // Throws - close_due_to_error(ec); // Throws - } - - void websocket_handshake_completion_handler(const HTTPHeaders&) final override - { - // This is not called since we handle HTTP request in handle_request_for_sync() - REALM_TERMINATE("websocket_handshake_completion_handler should not have been called"); - } - - int_fast64_t get_id() const noexcept - { - return m_id; - } - - network::Socket& get_socket() noexcept - { - return *m_socket; - } - - void initiate(); - - // Commits suicide - template - void terminate(Logger::Level, const char* log_message, Params... log_params); - - // Commits suicide - void terminate_if_dead(SteadyTimePoint now); - - void enlist_to_send(Session*) noexcept; - - // Sessions should get the output_buffer and insert a message, after which - // they call initiate_write_output_buffer(). - OutputBuffer& get_output_buffer() - { - m_output_buffer.reset(); - return m_output_buffer; - } - - // More advanced memory strategies can be implemented if needed. - void release_output_buffer() {} - - // When this function is called, the connection will initiate a write with - // its output_buffer. Sessions use this method. - void initiate_write_output_buffer(); - - void initiate_pong_output_buffer(); - - void handle_protocol_error(Status status); - - void receive_bind_message(session_ident_type, std::string path, std::string signed_user_token, - bool need_client_file_ident, bool is_subserver); - - void receive_ident_message(session_ident_type, file_ident_type client_file_ident, - salt_type client_file_ident_salt, version_type scan_server_version, - version_type scan_client_version, version_type latest_server_version, - salt_type latest_server_version_salt); - - void receive_upload_message(session_ident_type, version_type progress_client_version, - version_type progress_server_version, version_type locked_server_version, - const UploadChangesets&); - - void receive_mark_message(session_ident_type, request_ident_type); - - void receive_unbind_message(session_ident_type); - - void receive_ping(milliseconds_type timestamp, milliseconds_type rtt); - - void receive_error_message(session_ident_type, int error_code, std::string_view error_body); - - void protocol_error(ProtocolError, Session* = nullptr); - - void initiate_soft_close(); - - void discard_session(session_ident_type) noexcept; - - void send_log_message(util::Logger::Level level, const std::string&& message, session_ident_type sess_ident = 0, - std::optional co_id = std::nullopt); - -private: - ServerImpl& m_server; - const int_fast64_t m_id; - std::unique_ptr m_socket; - std::unique_ptr m_ssl_stream; - std::unique_ptr m_read_ahead_buffer; - - websocket::Socket m_websocket; - std::unique_ptr m_input_body_buffer; - OutputBuffer m_output_buffer; - std::map> m_sessions; - - // The protocol version in use by the connected client. - const int m_client_protocol_version; - - // The user agent description passed by the client. - const std::string m_client_user_agent; - - const std::string m_remote_endpoint; - - const std::string m_appservices_request_id; - - // A queue of sessions that have enlisted for an opportunity to send a - // message. Sessions will be served in the order that they enlist. A session - // can only occur once in this queue (linked list). If the queue is not - // empty, and no message is currently being written to the socket, the first - // session is taken out of the queue, and then granted an opportunity to - // send a message. - // - // Sessions will never be destroyed while in this queue. This is ensured - // because the connection owns the sessions that are associated with it, and - // the connection only removes a session from m_sessions at points in time - // where that session is guaranteed to not be in m_sessions_enlisted_to_send - // (Connection::send_next_message() and Connection::~Connection()). - SessionQueue m_sessions_enlisted_to_send; - - Session* m_receiving_session = nullptr; - - bool m_is_sending = false; - bool m_is_closing = false; - - bool m_send_pong = false; - bool m_sending_pong = false; - - std::unique_ptr> m_send_trigger; - - milliseconds_type m_last_ping_timestamp = 0; - - // If `m_is_closing` is true, this is the time at which `m_is_closing` was - // set to true (initiation of soft close). Otherwise, if no messages have - // been received from the client, this is the time at which the connection - // object was initiated (completion of WebSocket handshake). Otherwise this - // is the time at which the last message was received from the client. - SteadyTimePoint m_last_activity_at; - - // These are initialized by do_initiate_soft_close(). - // - // With recent versions of the protocol (when the version is greater than, - // or equal to 23), `m_error_session_ident` is always zero. - ProtocolError m_error_code = {}; - session_ident_type m_error_session_ident = 0; - - struct LogMessage { - session_ident_type sess_ident; - util::Logger::Level level; - std::string message; - std::optional co_id; - }; - - std::mutex m_log_mutex; - std::queue m_log_messages; - - static std::string make_logger_prefix(int_fast64_t id) - { - std::ostringstream out; - out.imbue(std::locale::classic()); - out << "Sync Connection[" << id << "]: "; // Throws - return out.str(); // Throws - } - - // The return value of handle_message_received() designates whether - // message processing should continue. If the connection object is - // destroyed during execution of handle_message_received(), the return - // value must be false. - void handle_message_received(const char* data, size_t size); - - void handle_ping_received(const char* data, size_t size); - - void send_next_message(); - void send_pong(milliseconds_type timestamp); - void send_log_message(const LogMessage& log_msg); - - void handle_write_output_buffer(); - void handle_pong_output_buffer(); - - void initiate_write_error(ProtocolError, session_ident_type); - void handle_write_error(std::error_code ec); - - void do_initiate_soft_close(ProtocolError, session_ident_type); - void read_error(std::error_code); - void write_error(std::error_code); - - void close_due_to_close_by_client(std::error_code); - void close_due_to_error(std::error_code); - - void terminate_sessions(); - - void bad_session_ident(const char* message_type, session_ident_type); - void message_after_unbind(const char* message_type, session_ident_type); - void message_before_ident(const char* message_type, session_ident_type); -}; - - -inline void SyncConnection::read_error(std::error_code ec) -{ - REALM_ASSERT(ec != util::error::operation_aborted); - if (ec == util::MiscExtErrors::end_of_input || ec == util::error::connection_reset) { - // Suicide - close_due_to_close_by_client(ec); // Throws - return; - } - if (ec == util::MiscExtErrors::delim_not_found) { - logger.error("Input message head delimited not found"); // Throws - protocol_error(ProtocolError::limits_exceeded); // Throws - return; - } - - logger.error("Reading failed: %1", ec.message()); // Throws - - // Suicide - close_due_to_error(ec); // Throws -} - -inline void SyncConnection::write_error(std::error_code ec) -{ - REALM_ASSERT(ec != util::error::operation_aborted); - if (ec == util::error::broken_pipe || ec == util::error::connection_reset) { - // Suicide - close_due_to_close_by_client(ec); // Throws - return; - } - logger.error("Writing failed: %1", ec.message()); // Throws - - // Suicide - close_due_to_error(ec); // Throws -} - - -// ============================ HTTPConnection ============================ - -std::string g_user_agent = "User-Agent"; - -class HTTPConnection { -public: - const std::shared_ptr logger_ptr; - util::Logger& logger; - - HTTPConnection(ServerImpl& serv, int_fast64_t id, bool is_ssl) - : logger_ptr{std::make_shared(util::LogCategory::server, make_logger_prefix(id), - serv.logger_ptr)} // Throws - , logger{*logger_ptr} - , m_server{serv} - , m_id{id} - , m_socket{new network::Socket{serv.get_service()}} // Throws - , m_read_ahead_buffer{new network::ReadAheadBuffer} // Throws - , m_http_server{*this, logger_ptr} - { - // Make the output buffer stream throw std::bad_alloc if it fails to - // expand the buffer - m_output_buffer.exceptions(std::ios_base::badbit | std::ios_base::failbit); - - if (is_ssl) { - using namespace network::ssl; - Context& ssl_context = serv.get_ssl_context(); - m_ssl_stream = std::make_unique(*m_socket, ssl_context, - Stream::server); // Throws - } - } - - ServerImpl& get_server() noexcept - { - return m_server; - } - - int_fast64_t get_id() const noexcept - { - return m_id; - } - - network::Socket& get_socket() noexcept - { - return *m_socket; - } - - template - void async_write(const char* data, size_t size, H handler) - { - if (m_ssl_stream) { - m_ssl_stream->async_write(data, size, std::move(handler)); // Throws - } - else { - m_socket->async_write(data, size, std::move(handler)); // Throws - } - } - - template - void async_read(char* buffer, size_t size, H handler) - { - if (m_ssl_stream) { - m_ssl_stream->async_read(buffer, size, *m_read_ahead_buffer, - std::move(handler)); // Throws - } - else { - m_socket->async_read(buffer, size, *m_read_ahead_buffer, - std::move(handler)); // Throws - } - } - - template - void async_read_until(char* buffer, size_t size, char delim, H handler) - { - if (m_ssl_stream) { - m_ssl_stream->async_read_until(buffer, size, delim, *m_read_ahead_buffer, - std::move(handler)); // Throws - } - else { - m_socket->async_read_until(buffer, size, delim, *m_read_ahead_buffer, - std::move(handler)); // Throws - } - } - - void initiate(std::string remote_endpoint) - { - m_last_activity_at = steady_clock_now(); - m_remote_endpoint = std::move(remote_endpoint); - - logger.detail("Connection from %1", m_remote_endpoint); // Throws - - if (m_ssl_stream) { - initiate_ssl_handshake(); // Throws - } - else { - initiate_http(); // Throws - } - } - - void respond_200_ok() - { - handle_text_response(HTTPStatus::Ok, "OK"); // Throws - } - - void respond_404_not_found() - { - handle_text_response(HTTPStatus::NotFound, "Not found"); // Throws - } - - void respond_503_service_unavailable() - { - handle_text_response(HTTPStatus::ServiceUnavailable, "Service unavailable"); // Throws - } - - // Commits suicide - template - void terminate(Logger::Level log_level, const char* log_message, Params... log_params) - { - logger.log(log_level, log_message, log_params...); // Throws - m_ssl_stream.reset(); - m_socket.reset(); - m_server.remove_http_connection(m_id); // Suicide - } - - // Commits suicide - void terminate_if_dead(SteadyTimePoint now) - { - milliseconds_type time = steady_duration(m_last_activity_at, now); - const Server::Config& config = m_server.get_config(); - if (m_is_sending) { - if (time >= config.http_response_timeout) { - // Suicide - terminate(Logger::Level::detail, - "HTTP connection closed (request timeout)"); // Throws - } - } - else { - if (time >= config.http_request_timeout) { - // Suicide - terminate(Logger::Level::detail, - "HTTP connection closed (response timeout)"); // Throws - } - } - } - - std::string get_appservices_request_id() const - { - return m_appservices_request_id.to_string(); - } - -private: - ServerImpl& m_server; - const int_fast64_t m_id; - const ObjectId m_appservices_request_id = ObjectId::gen(); - std::unique_ptr m_socket; - std::unique_ptr m_ssl_stream; - std::unique_ptr m_read_ahead_buffer; - HTTPServer m_http_server; - OutputBuffer m_output_buffer; - bool m_is_sending = false; - SteadyTimePoint m_last_activity_at; - std::string m_remote_endpoint; - int m_negotiated_protocol_version = 0; - - void initiate_ssl_handshake() - { - auto handler = [this](std::error_code ec) { - if (ec != util::error::operation_aborted) - handle_ssl_handshake(ec); // Throws - }; - m_ssl_stream->async_handshake(std::move(handler)); // Throws - } - - void handle_ssl_handshake(std::error_code ec) - { - if (ec) { - logger.error("SSL handshake error (%1): %2", ec, ec.message()); // Throws - close_due_to_error(ec); // Throws - return; - } - initiate_http(); // Throws - } - - void initiate_http() - { - logger.debug("Connection initiates HTTP receipt"); - - auto handler = [this](HTTPRequest request, std::error_code ec) { - if (REALM_UNLIKELY(ec == util::error::operation_aborted)) - return; - if (REALM_UNLIKELY(ec == HTTPParserError::MalformedRequest)) { - logger.error("Malformed HTTP request"); - close_due_to_error(ec); // Throws - return; - } - if (REALM_UNLIKELY(ec == HTTPParserError::BadRequest)) { - logger.error("Bad HTTP request"); - const char* body = "The HTTP request was corrupted"; - handle_400_bad_request(body); // Throws - return; - } - if (REALM_UNLIKELY(ec)) { - read_error(ec); // Throws - return; - } - handle_http_request(std::move(request)); // Throws - }; - m_http_server.async_receive_request(std::move(handler)); // Throws - } - - void handle_http_request(const HTTPRequest& request) - { - StringData path = request.path; - - logger.debug("HTTP request received, request = %1", request); - - m_is_sending = true; - m_last_activity_at = steady_clock_now(); - - // FIXME: When thinking of this function as a switching device, it seem - // wrong that it requires a `%2F` after `/realm-sync/`. If `%2F` is - // supposed to be mandatory, then that check ought to be delegated to - // handle_request_for_sync(), as that will yield a sharper separation of - // concerns. - if (path == "/realm-sync" || path.begins_with("/realm-sync?") || path.begins_with("/realm-sync/%2F")) { - handle_request_for_sync(request); // Throws - } - else { - handle_404_not_found(request); // Throws - } - } - - void handle_request_for_sync(const HTTPRequest& request) - { - if (m_server.is_sync_stopped()) { - logger.debug("Attempt to create a sync connection to a server that has been " - "stopped"); // Throws - handle_503_service_unavailable(request, "The server does not accept sync " - "connections"); // Throws - return; - } - - util::Optional sec_websocket_protocol = websocket::read_sec_websocket_protocol(request); - - // Figure out whether there are any protocol versions supported by both - // the client and the server, and if so, choose the newest one of them. - MiscBuffers& misc_buffers = m_server.get_misc_buffers(); - using ProtocolVersionRanges = MiscBuffers::ProtocolVersionRanges; - ProtocolVersionRanges& protocol_version_ranges = misc_buffers.protocol_version_ranges; - { - protocol_version_ranges.clear(); - util::MemoryInputStream in; - in.imbue(std::locale::classic()); - in.unsetf(std::ios_base::skipws); - std::string_view value; - if (sec_websocket_protocol) - value = *sec_websocket_protocol; - HttpListHeaderValueParser parser{value}; - std::string_view elem; - while (parser.next(elem)) { - // FIXME: Use std::string_view::begins_with() in C++20. - const StringData protocol{elem}; - std::string_view prefix; - if (protocol.begins_with(get_pbs_websocket_protocol_prefix())) - prefix = get_pbs_websocket_protocol_prefix(); - else if (protocol.begins_with(get_old_pbs_websocket_protocol_prefix())) - prefix = get_old_pbs_websocket_protocol_prefix(); - if (!prefix.empty()) { - auto parse_version = [&](std::string_view str) { - in.set_buffer(str.data(), str.data() + str.size()); - int version = 0; - in >> version; - if (REALM_LIKELY(in && in.eof() && version >= 0)) - return version; - return -1; - }; - int min, max; - std::string_view range = elem.substr(prefix.size()); - auto i = range.find('-'); - if (i != std::string_view::npos) { - min = parse_version(range.substr(0, i)); - max = parse_version(range.substr(i + 1)); - } - else { - min = parse_version(range); - max = min; - } - if (REALM_LIKELY(min >= 0 && max >= 0 && min <= max)) { - protocol_version_ranges.emplace_back(min, max); // Throws - continue; - } - logger.error("Protocol version negotiation failed: Client sent malformed " - "specification of supported protocol versions: '%1'", - elem); // Throws - handle_400_bad_request("Protocol version negotiation failed: Malformed " - "specification of supported protocol " - "versions\n"); // Throws - return; - } - logger.warn("Unrecognized protocol token in HTTP response header " - "Sec-WebSocket-Protocol: '%1'", - elem); // Throws - } - if (protocol_version_ranges.empty()) { - logger.error("Protocol version negotiation failed: Client did not send a " - "specification of supported protocol versions"); // Throws - handle_400_bad_request("Protocol version negotiation failed: Missing specification " - "of supported protocol versions\n"); // Throws - return; - } - } - { - ProtocolVersionRange server_range = m_server.get_protocol_version_range(); - int server_min = server_range.first; - int server_max = server_range.second; - int best_match = 0; - int overall_client_min = std::numeric_limits::max(); - int overall_client_max = std::numeric_limits::min(); - for (const auto& range : protocol_version_ranges) { - int client_min = range.first; - int client_max = range.second; - if (client_max >= server_min && client_min <= server_max) { - // Overlap - int version = std::min(client_max, server_max); - if (version > best_match) { - best_match = version; - } - } - if (client_min < overall_client_min) - overall_client_min = client_min; - if (client_max > overall_client_max) - overall_client_max = client_max; - } - Formatter& formatter = misc_buffers.formatter; - if (REALM_UNLIKELY(best_match == 0)) { - const char* elaboration = "No version supported by both client and server"; - auto format_ranges = [&](const auto& list) { - bool nonfirst = false; - for (auto range : list) { - if (nonfirst) - formatter << ", "; // Throws - int min = range.first, max = range.second; - REALM_ASSERT(min <= max); - formatter << min; - if (max != min) - formatter << "-" << max; - nonfirst = true; - } - }; - using Range = ProtocolVersionRange; - formatter.reset(); - format_ranges(protocol_version_ranges); // Throws - logger.error("Protocol version negotiation failed: %1 " - "(client supports: %2)", - elaboration, std::string_view(formatter.data(), formatter.size())); // Throws - formatter.reset(); - formatter << "Protocol version negotiation failed: " - "" - << elaboration << ".\n\n"; // Throws - formatter << "Server supports: "; // Throws - format_ranges(std::initializer_list{{server_min, server_max}}); // Throws - formatter << "\n"; // Throws - formatter << "Client supports: "; // Throws - format_ranges(protocol_version_ranges); // Throws - formatter << "\n"; // Throws - handle_400_bad_request({formatter.data(), formatter.size()}); // Throws - return; - } - m_negotiated_protocol_version = best_match; - logger.debug("Received: Sync HTTP request (negotiated_protocol_version=%1)", - m_negotiated_protocol_version); // Throws - formatter.reset(); - } - - std::string sec_websocket_protocol_2; - { - std::string_view prefix = - m_negotiated_protocol_version < SyncConnection::PBS_FLX_MIGRATION_PROTOCOL_VERSION - ? get_old_pbs_websocket_protocol_prefix() - : get_pbs_websocket_protocol_prefix(); - std::ostringstream out; - out.imbue(std::locale::classic()); - out << prefix << m_negotiated_protocol_version; // Throws - sec_websocket_protocol_2 = std::move(out).str(); - } - - std::error_code ec; - util::Optional response = - websocket::make_http_response(request, sec_websocket_protocol_2, ec); // Throws - - if (ec) { - if (ec == websocket::HttpError::bad_request_header_upgrade) { - logger.error("There must be a header of the form 'Upgrade: websocket'"); - } - else if (ec == websocket::HttpError::bad_request_header_connection) { - logger.error("There must be a header of the form 'Connection: Upgrade'"); - } - else if (ec == websocket::HttpError::bad_request_header_websocket_version) { - logger.error("There must be a header of the form 'Sec-WebSocket-Version: 13'"); - } - else if (ec == websocket::HttpError::bad_request_header_websocket_key) { - logger.error("The header Sec-WebSocket-Key is missing"); - } - - logger.error("The HTTP request with the error is:\n%1", request); - logger.error("Check the proxy configuration and make sure that the " - "HTTP request is a valid Websocket request."); - close_due_to_error(ec); - return; - } - REALM_ASSERT(response); - add_common_http_response_headers(*response); - - std::string user_agent; - { - auto i = request.headers.find(g_user_agent); - if (i != request.headers.end()) - user_agent = i->second; // Throws (copy) - } - - auto handler = [protocol_version = m_negotiated_protocol_version, user_agent = std::move(user_agent), - this](std::error_code ec) { - // If the operation is aborted, the socket object may have been destroyed. - if (ec != util::error::operation_aborted) { - if (ec) { - write_error(ec); - return; - } - - std::unique_ptr sync_conn = std::make_unique( - m_server, m_id, std::move(m_socket), std::move(m_ssl_stream), std::move(m_read_ahead_buffer), - protocol_version, std::move(user_agent), std::move(m_remote_endpoint), - get_appservices_request_id()); // Throws - SyncConnection& sync_conn_ref = *sync_conn; - m_server.add_sync_connection(m_id, std::move(sync_conn)); - m_server.remove_http_connection(m_id); - sync_conn_ref.initiate(); - } - }; - m_http_server.async_send_response(*response, std::move(handler)); - } - - void handle_text_response(HTTPStatus http_status, std::string_view body) - { - std::string body_2 = std::string(body); // Throws - - HTTPResponse response; - response.status = http_status; - add_common_http_response_headers(response); - response.headers["Connection"] = "close"; - - if (!body_2.empty()) { - response.headers["Content-Length"] = util::to_string(body_2.size()); - response.body = std::move(body_2); - } - - auto handler = [this](std::error_code ec) { - if (REALM_UNLIKELY(ec == util::error::operation_aborted)) - return; - if (REALM_UNLIKELY(ec)) { - write_error(ec); - return; - } - terminate(Logger::Level::detail, "HTTP connection closed"); // Throws - }; - m_http_server.async_send_response(response, std::move(handler)); - } - - void handle_400_bad_request(std::string_view body) - { - logger.detail("400 Bad Request"); - handle_text_response(HTTPStatus::BadRequest, body); // Throws - } - - void handle_404_not_found(const HTTPRequest&) - { - logger.detail("404 Not Found"); // Throws - handle_text_response(HTTPStatus::NotFound, - "Realm sync server\n\nPage not found\n"); // Throws - } - - void handle_503_service_unavailable(const HTTPRequest&, std::string_view message) - { - logger.debug("503 Service Unavailable"); // Throws - handle_text_response(HTTPStatus::ServiceUnavailable, message); // Throws - } - - void add_common_http_response_headers(HTTPResponse& response) - { - response.headers["Server"] = "RealmSync/" REALM_VERSION_STRING; // Throws - if (m_negotiated_protocol_version < SyncConnection::SERVER_LOG_PROTOCOL_VERSION) { - // This isn't a real X-Appservices-Request-Id, but it should be enough to test with - response.headers["X-Appservices-Request-Id"] = get_appservices_request_id(); - } - } - - void read_error(std::error_code ec) - { - REALM_ASSERT(ec != util::error::operation_aborted); - if (ec == util::MiscExtErrors::end_of_input || ec == util::error::connection_reset) { - // Suicide - close_due_to_close_by_client(ec); // Throws - return; - } - if (ec == util::MiscExtErrors::delim_not_found) { - logger.error("Input message head delimited not found"); // Throws - close_due_to_error(ec); // Throws - return; - } - - logger.error("Reading failed: %1", ec.message()); // Throws - - // Suicide - close_due_to_error(ec); // Throws - } - - void write_error(std::error_code ec) - { - REALM_ASSERT(ec != util::error::operation_aborted); - if (ec == util::error::broken_pipe || ec == util::error::connection_reset) { - // Suicide - close_due_to_close_by_client(ec); // Throws - return; - } - logger.error("Writing failed: %1", ec.message()); // Throws - - // Suicide - close_due_to_error(ec); // Throws - } - - void close_due_to_close_by_client(std::error_code ec) - { - auto log_level = (ec == util::MiscExtErrors::end_of_input ? Logger::Level::detail : Logger::Level::info); - // Suicide - terminate(log_level, "HTTP connection closed by client: %1", ec.message()); // Throws - } - - void close_due_to_error(std::error_code ec) - { - // Suicide - terminate(Logger::Level::error, "HTTP connection closed due to error: %1", - ec.message()); // Throws - } - - static std::string make_logger_prefix(int_fast64_t id) - { - std::ostringstream out; - out.imbue(std::locale::classic()); - out << "HTTP Connection[" << id << "]: "; // Throws - return out.str(); // Throws - } -}; - - -class DownloadHistoryEntryHandler : public ServerHistory::HistoryEntryHandler { -public: - std::size_t num_changesets = 0; - std::size_t accum_original_size = 0; - std::size_t accum_compacted_size = 0; - - DownloadHistoryEntryHandler(ServerProtocol& protocol, OutputBuffer& buffer, util::Logger& logger) noexcept - : m_protocol{protocol} - , m_buffer{buffer} - , m_logger{logger} - { - } - - void handle(version_type server_version, const HistoryEntry& entry, size_t original_size) override - { - version_type client_version = entry.remote_version; - ServerProtocol::ChangesetInfo info{server_version, client_version, entry, original_size}; - m_protocol.insert_single_changeset_download_message(m_buffer, info, m_logger); // Throws - ++num_changesets; - accum_original_size += original_size; - accum_compacted_size += entry.changeset.size(); - } - -private: - ServerProtocol& m_protocol; - OutputBuffer& m_buffer; - util::Logger& m_logger; -}; - - -// ============================ Session ============================ - -// Need cli- Send IDENT UNBIND ERROR -// Protocol ent file IDENT message message Error message -// state identifier message received received occurred sent -// --------------------------------------------------------------------------------- -// AllocatingIdent yes yes no no no no -// SendIdent no yes no no no no -// WaitForIdent no no no no no no -// WaitForUnbind maybe no yes no no no -// SendError maybe maybe maybe no yes no -// WaitForUnbindErr maybe maybe maybe no yes yes -// SendUnbound maybe maybe maybe yes maybe no -// -// -// Condition Expression -// ---------------------------------------------------------- -// Need client file identifier need_client_file_ident() -// Send IDENT message must_send_ident_message() -// IDENT message received ident_message_received() -// UNBIND message received unbind_message_received() -// Error occurred error_occurred() -// ERROR message sent m_error_message_sent -// -// -// Protocol -// state Will send Can receive -// ----------------------------------------------------------------------- -// AllocatingIdent none UNBIND -// SendIdent IDENT UNBIND -// WaitForIdent none IDENT, UNBIND -// WaitForUnbind DOWNLOAD, TRANSACT, UPLOAD, TRANSACT, MARK, -// MARK, ALLOC ALLOC, UNBIND -// SendError ERROR any -// WaitForUnbindErr none any -// SendUnbound UNBOUND none -// -class Session final : private FileIdentReceiver { -public: - util::PrefixLogger logger; - - Session(SyncConnection& conn, session_ident_type session_ident) - : logger{util::LogCategory::server, make_logger_prefix(session_ident), conn.logger_ptr} // Throws - , m_connection{conn} - , m_session_ident{session_ident} - { - } - - ~Session() noexcept - { - REALM_ASSERT(!is_enlisted_to_send()); - detach_from_server_file(); - } - - SyncConnection& get_connection() noexcept - { - return m_connection; - } - - const Optional>& get_encryption_key() - { - return m_connection.get_server().get_config().encryption_key; - } - - session_ident_type get_session_ident() const noexcept - { - return m_session_ident; - } - - ServerProtocol& get_server_protocol() noexcept - { - return m_connection.get_server_protocol(); - } - - bool need_client_file_ident() const noexcept - { - return (m_file_ident_request != 0); - } - - bool must_send_ident_message() const noexcept - { - return m_send_ident_message; - } - - bool ident_message_received() const noexcept - { - return m_client_file_ident != 0; - } - - bool unbind_message_received() const noexcept - { - return m_unbind_message_received; - } - - bool error_occurred() const noexcept - { - return int(m_error_code) != 0; - } - - bool relayed_alloc_request_in_progress() const noexcept - { - return (need_client_file_ident() || m_allocated_file_ident.ident != 0); - } - - // Returns the file identifier (always a nonzero value) of the client side - // file if ident_message_received() returns true. Otherwise it returns zero. - file_ident_type get_client_file_ident() const noexcept - { - return m_client_file_ident; - } - - void initiate() - { - logger.detail("Session initiated", m_session_ident); // Throws - } - - void terminate() - { - logger.detail("Session terminated", m_session_ident); // Throws - } - - // Initiate the deactivation process, if it has not been initiated already - // by the client. - // - // IMPORTANT: This function must not be called with protocol versions - // earlier than 23. - // - // The deactivation process will eventually lead to termination of the - // session. - // - // The session will detach itself from the server file when the deactivation - // process is initiated, regardless of whether it is initiated by the - // client, or by calling this function. - void initiate_deactivation(ProtocolError error_code) - { - REALM_ASSERT(is_session_level_error(error_code)); - REALM_ASSERT(!error_occurred()); // Must only be called once - - // If the UNBIND message has been received, then the client has - // initiated the deactivation process already. - if (REALM_LIKELY(!unbind_message_received())) { - detach_from_server_file(); - m_error_code = error_code; - // Protocol state is now SendError - ensure_enlisted_to_send(); - return; - } - // Protocol state was SendUnbound, and remains unchanged - } - - bool is_enlisted_to_send() const noexcept - { - return m_next != nullptr; - } - - void ensure_enlisted_to_send() noexcept - { - if (!is_enlisted_to_send()) - enlist_to_send(); - } - - void enlist_to_send() noexcept - { - m_connection.enlist_to_send(this); - } - - // Overriding memeber function in FileIdentReceiver - void receive_file_ident(SaltedFileIdent file_ident) override final - { - // Protocol state must be AllocatingIdent or WaitForUnbind - if (!ident_message_received()) { - REALM_ASSERT(need_client_file_ident()); - REALM_ASSERT(m_send_ident_message); - } - else { - REALM_ASSERT(!m_send_ident_message); - } - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(!error_occurred()); - REALM_ASSERT(!m_error_message_sent); - - m_file_ident_request = 0; - m_allocated_file_ident = file_ident; - - // If the protocol state was AllocatingIdent, it is now SendIdent, - // otherwise it continues to be WaitForUnbind. - - logger.debug("Acquired outbound salted file identifier (%1, %2)", file_ident.ident, - file_ident.salt); // Throws - - ensure_enlisted_to_send(); - } - - // Called by the associated connection object when this session is granted - // an opportunity to initiate the sending of a message. - // - // This function may lead to the destruction of the session object - // (suicide). - void send_message() - { - if (REALM_LIKELY(!unbind_message_received())) { - if (REALM_LIKELY(!error_occurred())) { - if (REALM_LIKELY(ident_message_received())) { - // State is WaitForUnbind. - bool relayed_alloc = (m_allocated_file_ident.ident != 0); - if (REALM_LIKELY(!relayed_alloc)) { - // Send DOWNLOAD or MARK. - continue_history_scan(); // Throws - // Session object may have been - // destroyed at this point (suicide) - return; - } - send_alloc_message(); // Throws - return; - } - // State is SendIdent - send_ident_message(); // Throws - return; - } - // State is SendError - send_error_message(); // Throws - return; - } - // State is SendUnbound - send_unbound_message(); // Throws - terminate(); // Throws - m_connection.discard_session(m_session_ident); - // This session is now destroyed! - } - - bool receive_bind_message(std::string path, std::string signed_user_token, bool need_client_file_ident, - bool is_subserver, ProtocolError& error) - { - if (logger.would_log(util::Logger::Level::info)) { - logger.detail("Received: BIND(server_path=%1, signed_user_token='%2', " - "need_client_file_ident=%3, is_subserver=%4)", - path, short_token_fmt(signed_user_token), int(need_client_file_ident), - int(is_subserver)); // Throws - } - - ServerImpl& server = m_connection.get_server(); - _impl::VirtualPathComponents virt_path_components = - _impl::parse_virtual_path(server.get_root_dir(), path); // Throws - - if (!virt_path_components.is_valid) { - logger.error("Bad virtual path (message_type='bind', path='%1', " - "signed_user_token='%2')", - path, - short_token_fmt(signed_user_token)); // Throws - error = ProtocolError::illegal_realm_path; - return false; - } - - // The user has proper permissions at this stage. - - m_server_file = server.get_or_create_file(path); // Throws - - m_server_file->add_unidentified_session(this); // Throws - - logger.info("Client info: (path='%1', from=%2, protocol=%3) %4", path, m_connection.get_remote_endpoint(), - m_connection.get_client_protocol_version(), - m_connection.get_client_user_agent()); // Throws - - m_is_subserver = is_subserver; - if (REALM_LIKELY(!need_client_file_ident)) { - // Protocol state is now WaitForUnbind - return true; - } - - // FIXME: We must make a choice about client file ident for read only - // sessions. They should have a special read-only client file ident. - file_ident_type proxy_file = 0; // No proxy - ClientType client_type = (is_subserver ? ClientType::subserver : ClientType::regular); - m_file_ident_request = m_server_file->request_file_ident(*this, proxy_file, client_type); // Throws - m_send_ident_message = true; - // Protocol state is now AllocatingIdent - - return true; - } - - bool receive_ident_message(file_ident_type client_file_ident, salt_type client_file_ident_salt, - version_type scan_server_version, version_type scan_client_version, - version_type latest_server_version, salt_type latest_server_version_salt, - ProtocolError& error) - { - // Protocol state must be WaitForIdent - REALM_ASSERT(!need_client_file_ident()); - REALM_ASSERT(!m_send_ident_message); - REALM_ASSERT(!ident_message_received()); - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(!error_occurred()); - REALM_ASSERT(!m_error_message_sent); - - logger.debug("Received: IDENT(client_file_ident=%1, client_file_ident_salt=%2, " - "scan_server_version=%3, scan_client_version=%4, latest_server_version=%5, " - "latest_server_version_salt=%6)", - client_file_ident, client_file_ident_salt, scan_server_version, scan_client_version, - latest_server_version, latest_server_version_salt); // Throws - - SaltedFileIdent client_file_ident_2 = {client_file_ident, client_file_ident_salt}; - DownloadCursor download_progress = {scan_server_version, scan_client_version}; - SaltedVersion server_version_2 = {latest_server_version, latest_server_version_salt}; - ClientType client_type = (m_is_subserver ? ClientType::subserver : ClientType::regular); - UploadCursor upload_threshold = {0, 0}; - version_type locked_server_version = 0; - BootstrapError error_2 = - m_server_file->bootstrap_client_session(client_file_ident_2, download_progress, server_version_2, - client_type, upload_threshold, locked_server_version, - logger); // Throws - switch (error_2) { - case BootstrapError::no_error: - break; - case BootstrapError::client_file_expired: - logger.warn("Client (%1) expired", client_file_ident); // Throws - error = ProtocolError::client_file_expired; - return false; - case BootstrapError::bad_client_file_ident: - logger.error("Bad client file ident (%1) in IDENT message", - client_file_ident); // Throws - error = ProtocolError::bad_client_file_ident; - return false; - case BootstrapError::bad_client_file_ident_salt: - logger.error("Bad client file identifier salt (%1) in IDENT message", - client_file_ident_salt); // Throws - error = ProtocolError::diverging_histories; - return false; - case BootstrapError::bad_download_server_version: - logger.error("Bad download progress server version in IDENT message"); // Throws - error = ProtocolError::bad_server_version; - return false; - case BootstrapError::bad_download_client_version: - logger.error("Bad download progress client version in IDENT message"); // Throws - error = ProtocolError::bad_client_version; - return false; - case BootstrapError::bad_server_version: - logger.error("Bad server version (message_type='ident')"); // Throws - error = ProtocolError::bad_server_version; - return false; - case BootstrapError::bad_server_version_salt: - logger.error("Bad server version salt in IDENT message"); // Throws - error = ProtocolError::diverging_histories; - return false; - case BootstrapError::bad_client_type: - logger.error("Bad client type (%1) in IDENT message", int(client_type)); // Throws - error = ProtocolError::bad_client_file_ident; // FIXME: Introduce new protocol-level error - // `bad_client_type`. - return false; - } - - // Make sure there is no other session currently associcated with the - // same client-side file - if (Session* other_sess = m_server_file->get_identified_session(client_file_ident)) { - SyncConnection& other_conn = other_sess->get_connection(); - // It is a protocol violation if the other session is associated - // with the same connection - if (&other_conn == &m_connection) { - logger.error("Client file already bound in other session associated with " - "the same connection"); // Throws - error = ProtocolError::bound_in_other_session; - return false; - } - // When the other session is associated with a different connection - // (`other_conn`), the clash may be due to the server not yet having - // realized that the other connection has been closed by the - // client. If so, the other connention is a "zombie". In the - // interest of getting rid of zombie connections as fast as - // possible, we shall assume that a clash with a session in another - // connection is always due to that other connection being a - // zombie. And when such a situation is detected, we want to close - // the zombie connection immediately. - auto log_level = Logger::Level::detail; - other_conn.terminate(log_level, - "Sync connection closed (superseded session)"); // Throws - } - - logger.info("Bound to client file (client_file_ident=%1)", client_file_ident); // Throws - - send_log_message(util::Logger::Level::debug, util::format("Session %1 bound to client file ident %2", - m_session_ident, client_file_ident)); - - m_server_file->identify_session(this, client_file_ident); // Throws - - m_client_file_ident = client_file_ident; - m_download_progress = download_progress; - m_upload_threshold = upload_threshold; - m_locked_server_version = locked_server_version; - - ServerImpl& server = m_connection.get_server(); - const Server::Config& config = server.get_config(); - m_disable_download = (config.disable_download_for.count(client_file_ident) != 0); - - if (REALM_UNLIKELY(config.session_bootstrap_callback)) { - config.session_bootstrap_callback(m_server_file->get_virt_path(), - client_file_ident); // Throws - } - - // Protocol state is now WaitForUnbind - enlist_to_send(); - return true; - } - - bool receive_upload_message(version_type progress_client_version, version_type progress_server_version, - version_type locked_server_version, const UploadChangesets& upload_changesets, - ProtocolError& error) - { - // Protocol state must be WaitForUnbind - REALM_ASSERT(!m_send_ident_message); - REALM_ASSERT(ident_message_received()); - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(!error_occurred()); - REALM_ASSERT(!m_error_message_sent); - - logger.detail("Received: UPLOAD(progress_client_version=%1, progress_server_version=%2, " - "locked_server_version=%3, num_changesets=%4)", - progress_client_version, progress_server_version, locked_server_version, - upload_changesets.size()); // Throws - - // We are unable to reproduce the cursor object for the upload progress - // when the protocol version is less than 29, because the client does - // not provide the required information. When the protocol version is - // less than 25, we can always get a consistent cursor by taking it from - // the changeset that was uploaded last, but in protocol versions 25, - // 26, 27, and 28, things are more complicated. Here, we receive new - // values for `last_integrated_server_version` which we cannot afford to - // ignore, but we do not know what client versions they correspond - // to. Fortunately, we can produce a cursor that works, and is mutually - // consistent with previous cursors, by simply bumping - // `upload_progress.client_version` when - // `upload_progress.last_intgerated_server_version` grows. - // - // To see that this scheme works, consider the last changeset, A, that - // will have already been uploaded and integrated at the beginning of - // the next session, and the first changeset, B, that follows A in the - // client side history, and is not upload skippable (of local origin and - // nonempty). We then need to show that A will be skipped, if uploaded - // in the next session, but B will not. - // - // Let V be the client version produced by A, and let T be the value of - // `upload_progress.client_version` as determined in this session, which - // is used as threshold in the next session. Then we know that A is - // skipped during the next session if V is less than, or equal to T. If - // the protocol version is at least 29, the protocol requires that T is - // greater than, or equal to V. If the protocol version is less than 25, - // T will be equal to V. Finally, if the protocol version is 25, 26, 27, - // or 28, we construct T such that it is always greater than, or equal - // to V, so in all cases, A will be skipped during the next session. - // - // Let W be the client version on which B is based. We then know that B - // will be retained if, and only if W is greater than, or equalto T. If - // the protocol version is at least 29, we know that T is less than, or - // equal to W, since B is not integrated until the next session. If the - // protocol version is less tahn 25, we know that T is V. Since V must - // be less than, or equal to W, we again know that T is less than, or - // equal to W. Finally, if the protocol version is 25, 26, 27, or 28, we - // construct T such that it is equal to V + N, where N is the number of - // observed increments in `last_integrated_server_version` since the - // client version prodiced by A. For each of these observed increments, - // there must have been a distinct new client version, but all these - // client versions must be less than, or equal to W, since B is not - // integrated until the next session. Therefore, we know that T = V + N - // is less than, or qual to W. So, in all cases, B will not skipped - // during the next session. - int protocol_version = m_connection.get_client_protocol_version(); - static_cast(protocol_version); // No protocol diversion (yet) - - UploadCursor upload_progress; - upload_progress = {progress_client_version, progress_server_version}; - - // `upload_progress.client_version` must be nondecreasing across the - // session. - bool good_1 = (upload_progress.client_version >= m_upload_progress.client_version); - if (REALM_UNLIKELY(!good_1)) { - logger.error("Decreasing client version in upload progress (%1 < %2)", upload_progress.client_version, - m_upload_progress.client_version); // Throws - error = ProtocolError::bad_client_version; - return false; - } - // `upload_progress.last_integrated_server_version` must be a version - // that the client can have heard about. - bool good_2 = (upload_progress.last_integrated_server_version <= m_download_progress.server_version); - if (REALM_UNLIKELY(!good_2)) { - logger.error("Bad last integrated server version in upload progress (%1 > %2)", - upload_progress.last_integrated_server_version, - m_download_progress.server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - - // `upload_progress` must be consistent. - if (REALM_UNLIKELY(!is_consistent(upload_progress))) { - logger.error("Upload progress is inconsistent (%1, %2)", upload_progress.client_version, - upload_progress.last_integrated_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - // `upload_progress` and `m_upload_threshold` must be mutually - // consistent. - if (REALM_UNLIKELY(!are_mutually_consistent(upload_progress, m_upload_threshold))) { - logger.error("Upload progress (%1, %2) is mutually inconsistent with " - "threshold (%3, %4)", - upload_progress.client_version, upload_progress.last_integrated_server_version, - m_upload_threshold.client_version, - m_upload_threshold.last_integrated_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - // `upload_progress` and `m_upload_progress` must be mutually - // consistent. - if (REALM_UNLIKELY(!are_mutually_consistent(upload_progress, m_upload_progress))) { - logger.error("Upload progress (%1, %2) is mutually inconsistent with previous " - "upload progress (%3, %4)", - upload_progress.client_version, upload_progress.last_integrated_server_version, - m_upload_progress.client_version, - m_upload_progress.last_integrated_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - - version_type locked_server_version_2 = locked_server_version; - - // `locked_server_version_2` must be nondecreasing over the lifetime of - // the client-side file. - if (REALM_UNLIKELY(locked_server_version_2 < m_locked_server_version)) { - logger.error("Decreasing locked server version (%1 < %2)", locked_server_version_2, - m_locked_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - // `locked_server_version_2` must be a version that the client can have - // heard about. - if (REALM_UNLIKELY(locked_server_version_2 > m_download_progress.server_version)) { - logger.error("Bad locked server version (%1 > %2)", locked_server_version_2, - m_download_progress.server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - - std::size_t num_previously_integrated_changesets = 0; - if (!upload_changesets.empty()) { - UploadCursor up = m_upload_progress; - for (const ServerProtocol::UploadChangeset& uc : upload_changesets) { - // `uc.upload_cursor.client_version` must be increasing across - // all the changesets in this UPLOAD message, and all must be - // greater than upload_progress.client_version of previous - // UPLOAD message. - if (REALM_UNLIKELY(uc.upload_cursor.client_version <= up.client_version)) { - logger.error("Nonincreasing client version in upload cursor of uploaded " - "changeset (%1 <= %2)", - uc.upload_cursor.client_version, - up.client_version); // Throws - error = ProtocolError::bad_client_version; - return false; - } - // `uc.upload_progress` must be consistent. - if (REALM_UNLIKELY(!is_consistent(uc.upload_cursor))) { - logger.error("Upload cursor of uploaded changeset is inconsistent (%1, %2)", - uc.upload_cursor.client_version, - uc.upload_cursor.last_integrated_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - // `uc.upload_progress` must be mutually consistent with - // previous upload cursor. - if (REALM_UNLIKELY(!are_mutually_consistent(uc.upload_cursor, up))) { - logger.error("Upload cursor of uploaded changeset (%1, %2) is mutually " - "inconsistent with previous upload cursor (%3, %4)", - uc.upload_cursor.client_version, uc.upload_cursor.last_integrated_server_version, - up.client_version, up.last_integrated_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - // `uc.upload_progress` must be mutually consistent with - // threshold, that is, for changesets that have not previously - // been integrated, it is important that the specified value of - // `last_integrated_server_version` is greater than, or equal to - // the reciprocal history base version. - bool consistent_with_threshold = are_mutually_consistent(uc.upload_cursor, m_upload_threshold); - if (REALM_UNLIKELY(!consistent_with_threshold)) { - logger.error("Upload cursor of uploaded changeset (%1, %2) is mutually " - "inconsistent with threshold (%3, %4)", - uc.upload_cursor.client_version, uc.upload_cursor.last_integrated_server_version, - m_upload_threshold.client_version, - m_upload_threshold.last_integrated_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - bool previously_integrated = (uc.upload_cursor.client_version <= m_upload_threshold.client_version); - if (previously_integrated) - ++num_previously_integrated_changesets; - up = uc.upload_cursor; - } - // `upload_progress.client_version` must be greater than, or equal - // to client versions produced by each of the changesets in this - // UPLOAD message. - if (REALM_UNLIKELY(up.client_version > upload_progress.client_version)) { - logger.error("Upload progress less than client version produced by uploaded " - "changeset (%1 > %2)", - up.client_version, - upload_progress.client_version); // Throws - error = ProtocolError::bad_client_version; - return false; - } - // The upload cursor of last uploaded changeset must be mutually - // consistent with the reported upload progress. - if (REALM_UNLIKELY(!are_mutually_consistent(up, upload_progress))) { - logger.error("Upload cursor (%1, %2) of last uploaded changeset is mutually " - "inconsistent with upload progress (%3, %4)", - up.client_version, up.last_integrated_server_version, upload_progress.client_version, - upload_progress.last_integrated_server_version); // Throws - error = ProtocolError::bad_server_version; - return false; - } - } - - // FIXME: Part of a very poor man's substitute for a proper backpressure - // scheme. - if (REALM_UNLIKELY(!m_server_file->can_add_changesets_from_downstream())) { - logger.debug("Terminating uploading session because buffer is full"); // Throws - // Using this exact error code, because it causes `try_again` flag - // to be set to true, which causes the client to wait for about 5 - // minuites before trying to connect again. - error = ProtocolError::connection_closed; - return false; - } - - m_upload_progress = upload_progress; - - bool have_real_upload_progress = (upload_progress.client_version > m_upload_threshold.client_version); - bool bump_locked_server_version = (locked_server_version_2 > m_locked_server_version); - - std::size_t num_changesets_to_integrate = upload_changesets.size() - num_previously_integrated_changesets; - REALM_ASSERT(have_real_upload_progress || num_changesets_to_integrate == 0); - - bool have_anything_to_do = (have_real_upload_progress || bump_locked_server_version); - if (!have_anything_to_do) - return true; - - if (!have_real_upload_progress) - upload_progress = m_upload_threshold; - - if (num_previously_integrated_changesets > 0) { - logger.detail("Ignoring %1 previously integrated changesets", - num_previously_integrated_changesets); // Throws - } - if (num_changesets_to_integrate > 0) { - logger.detail("Initiate integration of %1 remote changesets", - num_changesets_to_integrate); // Throws - } - - REALM_ASSERT(m_server_file); - ServerFile& file = *m_server_file; - std::size_t offset = num_previously_integrated_changesets; - file.add_changesets_from_downstream(m_client_file_ident, upload_progress, locked_server_version_2, - upload_changesets.data() + offset, num_changesets_to_integrate); // Throws - - m_locked_server_version = locked_server_version_2; - return true; - } - - bool receive_mark_message(request_ident_type request_ident, ProtocolError&) - { - // Protocol state must be WaitForUnbind - REALM_ASSERT(!m_send_ident_message); - REALM_ASSERT(ident_message_received()); - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(!error_occurred()); - REALM_ASSERT(!m_error_message_sent); - - logger.debug("Received: MARK(request_ident=%1)", request_ident); // Throws - - m_download_completion_request = request_ident; - - ensure_enlisted_to_send(); - return true; - } - - // Returns true if the deactivation process has been completed, at which - // point the caller (SyncConnection::receive_unbind_message()) should - // terminate the session. - // - // CAUTION: This function may commit suicide! - void receive_unbind_message() - { - // Protocol state may be anything but SendUnbound - REALM_ASSERT(!m_unbind_message_received); - - logger.detail("Received: UNBIND"); // Throws - - detach_from_server_file(); - m_unbind_message_received = true; - - // Detect completion of the deactivation process - if (m_error_message_sent) { - // Deactivation process completed - terminate(); // Throws - m_connection.discard_session(m_session_ident); - // This session is now destroyed! - return; - } - - // Protocol state is now SendUnbound - ensure_enlisted_to_send(); - } - - void receive_error_message(session_ident_type, int, std::string_view) - { - REALM_ASSERT(!m_unbind_message_received); - - logger.detail("Received: ERROR"); // Throws - } - -private: - SyncConnection& m_connection; - - const session_ident_type m_session_ident; - - // Not null if, and only if this session is in - // m_connection.m_sessions_enlisted_to_send. - Session* m_next = nullptr; - - // Becomes nonnull when the BIND message is received, if no error occurs. Is - // reset to null when the deactivation process is initiated, either when the - // UNBIND message is recieved, or when initiate_deactivation() is called. - util::bind_ptr m_server_file; - - bool m_disable_download = false; - bool m_is_subserver = false; - - using file_ident_request_type = ServerFile::file_ident_request_type; - - // When nonzero, this session has an outstanding request for a client file - // identifier. - file_ident_request_type m_file_ident_request = 0; - - // Payload for next outgoing ALLOC message. - SaltedFileIdent m_allocated_file_ident = {0, 0}; - - // Zero until the session receives an IDENT message from the client. - file_ident_type m_client_file_ident = 0; - - // Zero until initiate_deactivation() is called. - ProtocolError m_error_code = {}; - - // The current point of progression of the download process. Set to (, ) of the IDENT message when the IDENT message - // is received. At the time of return from continue_history_scan(), it - // points to the latest server version such that all preceding changesets in - // the server-side history have been downloaded, are currently being - // downloaded, or are *download excluded*. - DownloadCursor m_download_progress = {0, 0}; - - request_ident_type m_download_completion_request = 0; - - // Records the progress of the upload process. Used to check that the client - // uploads changesets in order. Also, when m_upload_progress > - // m_upload_threshold, m_upload_progress works as a cache of the persisted - // version of the upload progress. - UploadCursor m_upload_progress = {0, 0}; - - // Initialized on reception of the IDENT message. Specifies the actual - // upload progress (as recorded on the server-side) at the beginning of the - // session, and it remains fixed throughout the session. - // - // m_upload_threshold includes the progress resulting from the received - // changesets that have not yet been integrated (only relevant for - // synchronous backup). - UploadCursor m_upload_threshold = {0, 0}; - - // Works partially as a cache of the persisted value, and partially as a way - // of checking that the client respects that it can never decrease. - version_type m_locked_server_version = 0; - - bool m_send_ident_message = false; - bool m_unbind_message_received = false; - bool m_error_message_sent = false; - - /// m_one_download_message_sent denotes whether at least one DOWNLOAD message - /// has been sent in the current session. The variable is used to ensure - /// that a DOWNLOAD message is always sent in a session. The received - /// DOWNLOAD message is needed by the client to ensure that its current - /// download progress is up to date. - bool m_one_download_message_sent = false; - - static std::string make_logger_prefix(session_ident_type session_ident) - { - std::ostringstream out; - out.imbue(std::locale::classic()); - out << "Session[" << session_ident << "]: "; // Throws - return out.str(); // Throws - } - - // Scan the history for changesets to be downloaded. - // If the history is longer than the end point of the previous scan, - // a DOWNLOAD message will be sent. - // A MARK message is sent if no DOWNLOAD message is sent, and the client has - // requested to be notified about download completion. - // In case neither a DOWNLOAD nor a MARK is sent, no message is sent. - // - // This function may lead to the destruction of the session object - // (suicide). - void continue_history_scan() - { - // Protocol state must be WaitForUnbind - REALM_ASSERT(!m_send_ident_message); - REALM_ASSERT(ident_message_received()); - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(!error_occurred()); - REALM_ASSERT(!m_error_message_sent); - REALM_ASSERT(!is_enlisted_to_send()); - - SaltedVersion last_server_version = m_server_file->get_salted_sync_version(); - REALM_ASSERT(last_server_version.version >= m_download_progress.server_version); - - ServerImpl& server = m_connection.get_server(); - const Server::Config& config = server.get_config(); - if (REALM_UNLIKELY(m_disable_download)) - return; - - bool have_more_to_scan = - (last_server_version.version > m_download_progress.server_version || !m_one_download_message_sent); - if (have_more_to_scan) { - m_server_file->register_client_access(m_client_file_ident); // Throws - const ServerHistory& history = m_server_file->access().history; // Throws - const char* body; - std::size_t uncompressed_body_size; - std::size_t compressed_body_size = 0; - bool body_is_compressed = false; - version_type end_version = last_server_version.version; - DownloadCursor download_progress; - UploadCursor upload_progress = {0, 0}; - std::uint_fast64_t downloadable_bytes = 0; - std::size_t num_changesets; - std::size_t accum_original_size; - std::size_t accum_compacted_size; - ServerProtocol& protocol = get_server_protocol(); - bool enable_cache = (config.enable_download_bootstrap_cache && m_download_progress.server_version == 0 && - m_upload_progress.client_version == 0 && m_upload_threshold.client_version == 0); - DownloadCache& cache = m_server_file->get_download_cache(); - bool fetch_from_cache = (enable_cache && cache.body && end_version == cache.end_version); - if (fetch_from_cache) { - body = cache.body.get(); - uncompressed_body_size = cache.uncompressed_body_size; - compressed_body_size = cache.compressed_body_size; - body_is_compressed = cache.body_is_compressed; - download_progress = cache.download_progress; - downloadable_bytes = cache.downloadable_bytes; - num_changesets = cache.num_changesets; - accum_original_size = cache.accum_original_size; - accum_compacted_size = cache.accum_compacted_size; - } - else { - // Discard the old cached DOWNLOAD body before generating a new - // one to be cached. This can make a big difference because the - // size of that body can be very large (10GiB has been seen in a - // real-world case). - if (enable_cache) - cache.body = {}; - - OutputBuffer& out = server.get_misc_buffers().download_message; - out.reset(); - download_progress = m_download_progress; - auto fetch_and_compress = [&](std::size_t max_download_size) { - DownloadHistoryEntryHandler handler{protocol, out, logger}; - std::uint_fast64_t cumulative_byte_size_current; - std::uint_fast64_t cumulative_byte_size_total; - bool not_expired = history.fetch_download_info( - m_client_file_ident, download_progress, end_version, upload_progress, handler, - cumulative_byte_size_current, cumulative_byte_size_total, - max_download_size); // Throws - REALM_ASSERT(upload_progress.client_version >= download_progress.last_integrated_client_version); - SyncConnection& conn = get_connection(); - if (REALM_UNLIKELY(!not_expired)) { - logger.debug("History scanning failed: Client file entry " - "expired during session"); // Throws - conn.protocol_error(ProtocolError::client_file_expired, this); - // Session object may have been destroyed at this point - // (suicide). - return false; - } - - downloadable_bytes = cumulative_byte_size_total - cumulative_byte_size_current; - uncompressed_body_size = out.size(); - BinaryData uncompressed = {out.data(), uncompressed_body_size}; - body = uncompressed.data(); - std::size_t max_uncompressed = 1024; - if (uncompressed.size() > max_uncompressed) { - compression::CompressMemoryArena& arena = server.get_compress_memory_arena(); - std::vector& buffer = server.get_misc_buffers().compress; - compression::allocate_and_compress(arena, uncompressed, buffer); // Throws - if (buffer.size() < uncompressed.size()) { - body = buffer.data(); - compressed_body_size = buffer.size(); - body_is_compressed = true; - } - } - num_changesets = handler.num_changesets; - accum_original_size = handler.accum_original_size; - accum_compacted_size = handler.accum_compacted_size; - return true; - }; - if (enable_cache) { - std::size_t max_download_size = std::numeric_limits::max(); - if (!fetch_and_compress(max_download_size)) { // Throws - // Session object may have been destroyed at this point - // (suicide). - return; - } - REALM_ASSERT(upload_progress.client_version == 0); - std::size_t body_size = (body_is_compressed ? compressed_body_size : uncompressed_body_size); - cache.body = std::make_unique(body_size); // Throws - std::copy(body, body + body_size, cache.body.get()); - cache.uncompressed_body_size = uncompressed_body_size; - cache.compressed_body_size = compressed_body_size; - cache.body_is_compressed = body_is_compressed; - cache.end_version = end_version; - cache.download_progress = download_progress; - cache.downloadable_bytes = downloadable_bytes; - cache.num_changesets = num_changesets; - cache.accum_original_size = accum_original_size; - cache.accum_compacted_size = accum_compacted_size; - } - else { - std::size_t max_download_size = config.max_download_size; - if (!fetch_and_compress(max_download_size)) { // Throws - // Session object may have been destroyed at this point - // (suicide). - return; - } - } - } - - OutputBuffer& out = m_connection.get_output_buffer(); - protocol.make_download_message( - m_connection.get_client_protocol_version(), out, m_session_ident, download_progress.server_version, - download_progress.last_integrated_client_version, last_server_version.version, - last_server_version.salt, upload_progress.client_version, - upload_progress.last_integrated_server_version, downloadable_bytes, num_changesets, body, - uncompressed_body_size, compressed_body_size, body_is_compressed, logger); // Throws - - m_download_progress = download_progress; - logger.debug("Setting of m_download_progress.server_version = %1", - m_download_progress.server_version); // Throws - send_download_message(); - m_one_download_message_sent = true; - - enlist_to_send(); - } - else if (m_download_completion_request) { - // Send a MARK message - request_ident_type request_ident = m_download_completion_request; - send_mark_message(request_ident); // Throws - m_download_completion_request = 0; // Request handled - enlist_to_send(); - } - } - - void send_ident_message() - { - // Protocol state must be SendIdent - REALM_ASSERT(!need_client_file_ident()); - REALM_ASSERT(m_send_ident_message); - REALM_ASSERT(!ident_message_received()); - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(!error_occurred()); - REALM_ASSERT(!m_error_message_sent); - - REALM_ASSERT(m_allocated_file_ident.ident != 0); - - file_ident_type client_file_ident = m_allocated_file_ident.ident; - salt_type client_file_ident_salt = m_allocated_file_ident.salt; - - logger.debug("Sending: IDENT(client_file_ident=%1, client_file_ident_salt=%2)", client_file_ident, - client_file_ident_salt); // Throws - - ServerProtocol& protocol = get_server_protocol(); - OutputBuffer& out = m_connection.get_output_buffer(); - int protocol_version = m_connection.get_client_protocol_version(); - protocol.make_ident_message(protocol_version, out, m_session_ident, client_file_ident, - client_file_ident_salt); // Throws - m_connection.initiate_write_output_buffer(); // Throws - - m_allocated_file_ident.ident = 0; // Consumed - m_send_ident_message = false; - // Protocol state is now WaitForStateRequest or WaitForIdent - } - - void send_download_message() - { - m_connection.initiate_write_output_buffer(); // Throws - } - - void send_mark_message(request_ident_type request_ident) - { - logger.debug("Sending: MARK(request_ident=%1)", request_ident); // Throws - - ServerProtocol& protocol = get_server_protocol(); - OutputBuffer& out = m_connection.get_output_buffer(); - protocol.make_mark_message(out, m_session_ident, request_ident); // Throws - m_connection.initiate_write_output_buffer(); // Throws - } - - void send_alloc_message() - { - // Protocol state must be WaitForUnbind - REALM_ASSERT(!m_send_ident_message); - REALM_ASSERT(ident_message_received()); - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(!error_occurred()); - REALM_ASSERT(!m_error_message_sent); - - REALM_ASSERT(m_allocated_file_ident.ident != 0); - - // Relayed allocations are only allowed from protocol version 23 (old protocol). - REALM_ASSERT(false); - - file_ident_type file_ident = m_allocated_file_ident.ident; - - logger.debug("Sending: ALLOC(file_ident=%1)", file_ident); // Throws - - ServerProtocol& protocol = get_server_protocol(); - OutputBuffer& out = m_connection.get_output_buffer(); - protocol.make_alloc_message(out, m_session_ident, file_ident); // Throws - m_connection.initiate_write_output_buffer(); // Throws - - m_allocated_file_ident.ident = 0; // Consumed - - // Other messages may be waiting to be sent. - enlist_to_send(); - } - - void send_unbound_message() - { - // Protocol state must be SendUnbound - REALM_ASSERT(unbind_message_received()); - REALM_ASSERT(!m_error_message_sent); - - logger.debug("Sending: UNBOUND"); // Throws - - ServerProtocol& protocol = get_server_protocol(); - OutputBuffer& out = m_connection.get_output_buffer(); - protocol.make_unbound_message(out, m_session_ident); // Throws - m_connection.initiate_write_output_buffer(); // Throws - } - - void send_error_message() - { - // Protocol state must be SendError - REALM_ASSERT(!unbind_message_received()); - REALM_ASSERT(error_occurred()); - REALM_ASSERT(!m_error_message_sent); - - REALM_ASSERT(is_session_level_error(m_error_code)); - - ProtocolError error_code = m_error_code; - const char* message = get_protocol_error_message(int(error_code)); - std::size_t message_size = std::strlen(message); - bool try_again = determine_try_again(error_code); - - logger.detail("Sending: ERROR(error_code=%1, message_size=%2, try_again=%3)", int(error_code), message_size, - try_again); // Throws - - ServerProtocol& protocol = get_server_protocol(); - OutputBuffer& out = m_connection.get_output_buffer(); - int protocol_version = m_connection.get_client_protocol_version(); - protocol.make_error_message(protocol_version, out, error_code, message, message_size, try_again, - m_session_ident); // Throws - m_connection.initiate_write_output_buffer(); // Throws - - m_error_message_sent = true; - // Protocol state is now WaitForUnbindErr - } - - void send_log_message(util::Logger::Level level, const std::string&& message) - { - if (m_connection.get_client_protocol_version() < SyncConnection::SERVER_LOG_PROTOCOL_VERSION) { - return logger.log(level, message.c_str()); - } - - m_connection.send_log_message(level, std::move(message), m_session_ident); - } - - // Idempotent - void detach_from_server_file() noexcept - { - if (!m_server_file) - return; - ServerFile& file = *m_server_file; - if (ident_message_received()) { - file.remove_identified_session(m_client_file_ident); - } - else { - file.remove_unidentified_session(this); - } - if (m_file_ident_request != 0) - file.cancel_file_ident_request(m_file_ident_request); - m_server_file.reset(); - } - - friend class SessionQueue; -}; - - -// ============================ SessionQueue implementation ============================ - -void SessionQueue::push_back(Session* sess) noexcept -{ - REALM_ASSERT(!sess->m_next); - if (m_back) { - sess->m_next = m_back->m_next; - m_back->m_next = sess; - } - else { - sess->m_next = sess; - } - m_back = sess; -} - - -Session* SessionQueue::pop_front() noexcept -{ - Session* sess = nullptr; - if (m_back) { - sess = m_back->m_next; - if (sess != m_back) { - m_back->m_next = sess->m_next; - } - else { - m_back = nullptr; - } - sess->m_next = nullptr; - } - return sess; -} - - -void SessionQueue::clear() noexcept -{ - if (m_back) { - Session* sess = m_back; - for (;;) { - Session* next = sess->m_next; - sess->m_next = nullptr; - if (next == m_back) - break; - sess = next; - } - m_back = nullptr; - } -} - - -// ============================ ServerFile implementation ============================ - -ServerFile::ServerFile(ServerImpl& server, ServerFileAccessCache& cache, const std::string& virt_path, - std::string real_path, bool disable_sync_to_disk) - : logger{util::LogCategory::server, "ServerFile[" + virt_path + "]: ", server.logger_ptr} // Throws - , wlogger{util::LogCategory::server, "ServerFile[" + virt_path + "]: ", server.get_worker().logger_ptr} // Throws - , m_server{server} - , m_file{cache, real_path, virt_path, false, disable_sync_to_disk} // Throws - , m_worker_file{server.get_worker().get_file_access_cache(), real_path, virt_path, true, disable_sync_to_disk} -{ -} - - -ServerFile::~ServerFile() noexcept -{ - REALM_ASSERT(m_unidentified_sessions.empty()); - REALM_ASSERT(m_identified_sessions.empty()); - REALM_ASSERT(m_file_ident_request == 0); -} - - -void ServerFile::initialize() -{ - const ServerHistory& history = access().history; // Throws - file_ident_type partial_file_ident = 0; - version_type partial_progress_reference_version = 0; - bool has_upstream_sync_status; - history.get_status(m_version_info, has_upstream_sync_status, partial_file_ident, - partial_progress_reference_version); // Throws - REALM_ASSERT(!has_upstream_sync_status); - REALM_ASSERT(partial_file_ident == 0); -} - - -void ServerFile::activate() {} - - -// This function must be called only after a completed invocation of -// initialize(). Both functinos must only ever be called by the network event -// loop thread. -void ServerFile::register_client_access(file_ident_type) {} - - -auto ServerFile::request_file_ident(FileIdentReceiver& receiver, file_ident_type proxy_file, - ClientType client_type) -> file_ident_request_type -{ - auto request = ++m_last_file_ident_request; - m_file_ident_requests[request] = {&receiver, proxy_file, client_type}; // Throws - - on_work_added(); // Throws - return request; -} - - -void ServerFile::cancel_file_ident_request(file_ident_request_type request) noexcept -{ - auto i = m_file_ident_requests.find(request); - REALM_ASSERT(i != m_file_ident_requests.end()); - FileIdentRequestInfo& info = i->second; - REALM_ASSERT(info.receiver); - info.receiver = nullptr; -} - - -void ServerFile::add_unidentified_session(Session* sess) -{ - REALM_ASSERT(m_unidentified_sessions.count(sess) == 0); - m_unidentified_sessions.insert(sess); // Throws -} - - -void ServerFile::identify_session(Session* sess, file_ident_type client_file_ident) -{ - REALM_ASSERT(m_unidentified_sessions.count(sess) == 1); - REALM_ASSERT(m_identified_sessions.count(client_file_ident) == 0); - - m_identified_sessions[client_file_ident] = sess; // Throws - m_unidentified_sessions.erase(sess); -} - - -void ServerFile::remove_unidentified_session(Session* sess) noexcept -{ - REALM_ASSERT(m_unidentified_sessions.count(sess) == 1); - m_unidentified_sessions.erase(sess); -} - - -void ServerFile::remove_identified_session(file_ident_type client_file_ident) noexcept -{ - REALM_ASSERT(m_identified_sessions.count(client_file_ident) == 1); - m_identified_sessions.erase(client_file_ident); -} - - -Session* ServerFile::get_identified_session(file_ident_type client_file_ident) noexcept -{ - auto i = m_identified_sessions.find(client_file_ident); - if (i == m_identified_sessions.end()) - return nullptr; - return i->second; -} - -bool ServerFile::can_add_changesets_from_downstream() const noexcept -{ - return (m_blocked_changesets_from_downstream_byte_size < m_server.get_max_upload_backlog()); -} - - -void ServerFile::add_changesets_from_downstream(file_ident_type client_file_ident, UploadCursor upload_progress, - version_type locked_server_version, const UploadChangeset* changesets, - std::size_t num_changesets) -{ - register_client_access(client_file_ident); // Throws - - bool dirty = false; - - IntegratableChangesetList& list = m_changesets_from_downstream[client_file_ident]; // Throws - std::size_t num_bytes = 0; - for (std::size_t i = 0; i < num_changesets; ++i) { - const UploadChangeset& uc = changesets[i]; - auto& changesets = list.changesets; - changesets.emplace_back(client_file_ident, uc.origin_timestamp, uc.origin_file_ident, uc.upload_cursor, - uc.changeset); // Throws - num_bytes += uc.changeset.size(); - dirty = true; - } - - REALM_ASSERT(upload_progress.client_version >= list.upload_progress.client_version); - REALM_ASSERT(are_mutually_consistent(upload_progress, list.upload_progress)); - if (upload_progress.client_version > list.upload_progress.client_version) { - list.upload_progress = upload_progress; - dirty = true; - } - - REALM_ASSERT(locked_server_version >= list.locked_server_version); - if (locked_server_version > list.locked_server_version) { - list.locked_server_version = locked_server_version; - dirty = true; - } - - if (REALM_LIKELY(dirty)) { - if (num_changesets > 0) { - on_changesets_from_downstream_added(num_changesets, num_bytes); // Throws - } - else { - on_work_added(); // Throws - } - } -} - - -BootstrapError ServerFile::bootstrap_client_session(SaltedFileIdent client_file_ident, - DownloadCursor download_progress, SaltedVersion server_version, - ClientType client_type, UploadCursor& upload_progress, - version_type& locked_server_version, Logger& logger) -{ - // The Realm file may contain a later snapshot than the one reflected by - // `m_sync_version`, but if so, the client cannot "legally" know about it. - if (server_version.version > m_version_info.sync_version.version) - return BootstrapError::bad_server_version; - - const ServerHistory& hist = access().history; // Throws - BootstrapError error = hist.bootstrap_client_session(client_file_ident, download_progress, server_version, - client_type, upload_progress, locked_server_version, - logger); // Throws - - // FIXME: Rather than taking previously buffered changesets from the same - // client file into account when determining the upload progress, and then - // allowing for an error during the integration of those changesets to be - // reported to, and terminate the new session, consider to instead postpone - // the bootstrapping of the new session until all previously buffered - // changesets from same client file have been fully processed. - - if (error == BootstrapError::no_error) { - register_client_access(client_file_ident.ident); // Throws - - // If upload, or releaseing of server versions progressed further during - // previous sessions than the persisted points, take that into account - auto i = m_work.changesets_from_downstream.find(client_file_ident.ident); - if (i != m_work.changesets_from_downstream.end()) { - const IntegratableChangesetList& list = i->second; - REALM_ASSERT(list.upload_progress.client_version >= upload_progress.client_version); - upload_progress = list.upload_progress; - REALM_ASSERT(list.locked_server_version >= locked_server_version); - locked_server_version = list.locked_server_version; - } - auto j = m_changesets_from_downstream.find(client_file_ident.ident); - if (j != m_changesets_from_downstream.end()) { - const IntegratableChangesetList& list = j->second; - REALM_ASSERT(list.upload_progress.client_version >= upload_progress.client_version); - upload_progress = list.upload_progress; - REALM_ASSERT(list.locked_server_version >= locked_server_version); - locked_server_version = list.locked_server_version; - } - } - - return error; -} - -// NOTE: This function is executed by the worker thread -void ServerFile::worker_process_work_unit(WorkerState& state) -{ - SteadyTimePoint start_time = steady_clock_now(); - milliseconds_type parallel_time = 0; - - Work& work = m_work; - wlogger.debug("Work unit execution started"); // Throws - - if (work.has_primary_work) { - if (REALM_UNLIKELY(!m_work.file_ident_alloc_slots.empty())) - worker_allocate_file_identifiers(); // Throws - - if (!m_work.changesets_from_downstream.empty()) - worker_integrate_changes_from_downstream(state); // Throws - } - - wlogger.debug("Work unit execution completed"); // Throws - - milliseconds_type time = steady_duration(start_time); - milliseconds_type seq_time = time - parallel_time; - m_server.m_seq_time.fetch_add(seq_time, std::memory_order_relaxed); - m_server.m_par_time.fetch_add(parallel_time, std::memory_order_relaxed); - - // Pass control back to the network event loop thread - network::Service& service = m_server.get_service(); - service.post([this](Status) { - // FIXME: The safety of capturing `this` here, relies on the fact - // that ServerFile objects currently are not destroyed until the - // server object is destroyed. - group_postprocess_stage_1(); // Throws - // Suicide may have happened at this point - }); // Throws -} - - -void ServerFile::on_changesets_from_downstream_added(std::size_t num_changesets, std::size_t num_bytes) -{ - m_num_changesets_from_downstream += num_changesets; - - if (num_bytes > 0) { - m_blocked_changesets_from_downstream_byte_size += num_bytes; - get_server().inc_byte_size_for_pending_downstream_changesets(num_bytes); // Throws - } - - on_work_added(); // Throws -} - - -void ServerFile::on_work_added() -{ - if (m_has_blocked_work) - return; - m_has_blocked_work = true; - // Reference file - if (m_has_work_in_progress) - return; - group_unblock_work(); // Throws -} - - -void ServerFile::group_unblock_work() -{ - REALM_ASSERT(!m_has_work_in_progress); - if (REALM_LIKELY(!m_server.is_sync_stopped())) { - unblock_work(); // Throws - const Work& work = m_work; - if (REALM_LIKELY(work.has_primary_work)) { - logger.trace("Work unit unblocked"); // Throws - m_has_work_in_progress = true; - Worker& worker = m_server.get_worker(); - worker.enqueue(this); // Throws - } - } -} - - -void ServerFile::unblock_work() -{ - REALM_ASSERT(m_has_blocked_work); - - m_work.reset(); - - // Discard requests for file identifiers whose receiver is no longer - // waiting. - { - auto i = m_file_ident_requests.begin(); - auto end = m_file_ident_requests.end(); - while (i != end) { - auto j = i++; - const FileIdentRequestInfo& info = j->second; - if (!info.receiver) - m_file_ident_requests.erase(j); - } - } - std::size_t n = m_file_ident_requests.size(); - if (n > 0) { - m_work.file_ident_alloc_slots.resize(n); // Throws - std::size_t i = 0; - for (const auto& pair : m_file_ident_requests) { - const FileIdentRequestInfo& info = pair.second; - FileIdentAllocSlot& slot = m_work.file_ident_alloc_slots[i]; - slot.proxy_file = info.proxy_file; - slot.client_type = info.client_type; - ++i; - } - m_work.has_primary_work = true; - } - - // FIXME: `ServerFile::m_changesets_from_downstream` and - // `Work::changesets_from_downstream` should be renamed to something else, - // as it may contain kinds of data other than changesets. - - using std::swap; - swap(m_changesets_from_downstream, m_work.changesets_from_downstream); - m_work.have_changesets_from_downstream = (m_num_changesets_from_downstream > 0); - bool has_changesets = !m_work.changesets_from_downstream.empty(); - if (has_changesets) { - m_work.has_primary_work = true; - } - - // Keep track of the size of pending changesets - REALM_ASSERT(m_unblocked_changesets_from_downstream_byte_size == 0); - m_unblocked_changesets_from_downstream_byte_size = m_blocked_changesets_from_downstream_byte_size; - m_blocked_changesets_from_downstream_byte_size = 0; - - m_num_changesets_from_downstream = 0; - m_has_blocked_work = false; -} - - -void ServerFile::resume_download() noexcept -{ - for (const auto& entry : m_identified_sessions) { - Session& sess = *entry.second; - sess.ensure_enlisted_to_send(); - } -} - - -void ServerFile::recognize_external_change() -{ - VersionInfo prev_version_info = m_version_info; - const ServerHistory& history = access().history; // Throws - bool has_upstream_status; // Dummy - sync::file_ident_type partial_file_ident; // Dummy - sync::version_type partial_progress_reference_version; // Dummy - history.get_status(m_version_info, has_upstream_status, partial_file_ident, - partial_progress_reference_version); // Throws - - REALM_ASSERT(m_version_info.realm_version >= prev_version_info.realm_version); - REALM_ASSERT(m_version_info.sync_version.version >= prev_version_info.sync_version.version); - if (m_version_info.sync_version.version > prev_version_info.sync_version.version) { - REALM_ASSERT(m_version_info.realm_version > prev_version_info.realm_version); - resume_download(); - } -} - - -// NOTE: This function is executed by the worker thread -void ServerFile::worker_allocate_file_identifiers() -{ - Work& work = m_work; - REALM_ASSERT(!work.file_ident_alloc_slots.empty()); - ServerHistory& hist = worker_access().history; // Throws - hist.allocate_file_identifiers(m_work.file_ident_alloc_slots, m_work.version_info); // Throws - m_work.produced_new_realm_version = true; -} - - -// Returns true when, and only when this function produces a new sync version -// (adds a new entry to the sync history). -// -// NOTE: This function is executed by the worker thread -bool ServerFile::worker_integrate_changes_from_downstream(WorkerState& state) -{ - REALM_ASSERT(!m_work.changesets_from_downstream.empty()); - - std::unique_ptr hist_ptr; - DBRef sg_ptr; - ServerHistory& hist = get_client_file_history(state, hist_ptr, sg_ptr); - bool backup_whole_realm = false; - bool produced_new_realm_version = hist.integrate_client_changesets( - m_work.changesets_from_downstream, m_work.version_info, backup_whole_realm, m_work.integration_result, - wlogger); // Throws - bool produced_new_sync_version = !m_work.integration_result.integrated_changesets.empty(); - REALM_ASSERT(!produced_new_sync_version || produced_new_realm_version); - if (produced_new_realm_version) { - m_work.produced_new_realm_version = true; - if (produced_new_sync_version) { - m_work.produced_new_sync_version = true; - } - } - return produced_new_sync_version; -} - -ServerHistory& ServerFile::get_client_file_history(WorkerState& state, std::unique_ptr& hist_ptr, - DBRef& sg_ptr) -{ - if (state.use_file_cache) - return worker_access().history; // Throws - const std::string& path = m_worker_file.realm_path; - hist_ptr = m_server.make_history_for_path(); // Throws - DBOptions options = m_worker_file.make_shared_group_options(); // Throws - sg_ptr = DB::create(*hist_ptr, path, options); // Throws - sg_ptr->claim_sync_agent(); // Throws - return *hist_ptr; // Throws -} - - -// When worker thread finishes work unit. -void ServerFile::group_postprocess_stage_1() -{ - REALM_ASSERT(m_has_work_in_progress); - - group_finalize_work_stage_1(); // Throws - group_finalize_work_stage_2(); // Throws - group_postprocess_stage_2(); // Throws -} - - -void ServerFile::group_postprocess_stage_2() -{ - REALM_ASSERT(m_has_work_in_progress); - group_postprocess_stage_3(); // Throws - // Suicide may have happened at this point -} - - -// When all files, including the reference file, have been backed up. -void ServerFile::group_postprocess_stage_3() -{ - REALM_ASSERT(m_has_work_in_progress); - m_has_work_in_progress = false; - - logger.trace("Work unit postprocessing complete"); // Throws - if (m_has_blocked_work) - group_unblock_work(); // Throws -} - - -void ServerFile::finalize_work_stage_1() -{ - if (m_unblocked_changesets_from_downstream_byte_size > 0) { - // Report the byte size of completed downstream changesets. - std::size_t byte_size = m_unblocked_changesets_from_downstream_byte_size; - get_server().dec_byte_size_for_pending_downstream_changesets(byte_size); // Throws - m_unblocked_changesets_from_downstream_byte_size = 0; - } - - // Deal with errors (bad changesets) pertaining to downstream clients - std::size_t num_changesets_removed = 0; - std::size_t num_bytes_removed = 0; - for (const auto& entry : m_work.integration_result.excluded_client_files) { - file_ident_type client_file_ident = entry.first; - ExtendedIntegrationError error = entry.second; - ProtocolError error_2 = ProtocolError::other_session_error; - switch (error) { - case ExtendedIntegrationError::client_file_expired: - logger.debug("Changeset integration failed: Client file entry " - "expired during session"); // Throws - error_2 = ProtocolError::client_file_expired; - break; - case ExtendedIntegrationError::bad_origin_file_ident: - error_2 = ProtocolError::bad_origin_file_ident; - break; - case ExtendedIntegrationError::bad_changeset: - error_2 = ProtocolError::bad_changeset; - break; - } - auto i = m_identified_sessions.find(client_file_ident); - if (i != m_identified_sessions.end()) { - Session& sess = *i->second; - SyncConnection& conn = sess.get_connection(); - conn.protocol_error(error_2, &sess); // Throws - } - const IntegratableChangesetList& list = m_changesets_from_downstream[client_file_ident]; - std::size_t num_changesets = list.changesets.size(); - std::size_t num_bytes = 0; - for (const IntegratableChangeset& ic : list.changesets) - num_bytes += ic.changeset.size(); - logger.info("Excluded %1 changesets of combined byte size %2 for client file %3", num_changesets, num_bytes, - client_file_ident); // Throws - num_changesets_removed += num_changesets; - num_bytes_removed += num_bytes; - m_changesets_from_downstream.erase(client_file_ident); - } - - REALM_ASSERT(num_changesets_removed <= m_num_changesets_from_downstream); - REALM_ASSERT(num_bytes_removed <= m_blocked_changesets_from_downstream_byte_size); - - if (num_changesets_removed == 0) - return; - - m_num_changesets_from_downstream -= num_changesets_removed; - - // The byte size of the blocked changesets must be decremented. - if (num_bytes_removed > 0) { - m_blocked_changesets_from_downstream_byte_size -= num_bytes_removed; - get_server().dec_byte_size_for_pending_downstream_changesets(num_bytes_removed); // Throws - } -} - - -void ServerFile::finalize_work_stage_2() -{ - // Expose new snapshot to remote peers - REALM_ASSERT(m_work.produced_new_realm_version || m_work.version_info.realm_version == 0); - if (m_work.version_info.realm_version > m_version_info.realm_version) { - REALM_ASSERT(m_work.version_info.sync_version.version >= m_version_info.sync_version.version); - m_version_info = m_work.version_info; - } - - bool resume_download_and_upload = m_work.produced_new_sync_version; - - // Deliver allocated file identifiers to requesters - REALM_ASSERT(m_file_ident_requests.size() >= m_work.file_ident_alloc_slots.size()); - auto begin = m_file_ident_requests.begin(); - auto i = begin; - for (const FileIdentAllocSlot& slot : m_work.file_ident_alloc_slots) { - FileIdentRequestInfo& info = i->second; - REALM_ASSERT(info.proxy_file == slot.proxy_file); - REALM_ASSERT(info.client_type == slot.client_type); - if (FileIdentReceiver* receiver = info.receiver) { - info.receiver = nullptr; - receiver->receive_file_ident(slot.file_ident); // Throws - } - ++i; - } - m_file_ident_requests.erase(begin, i); - - // Resume download to downstream clients - if (resume_download_and_upload) { - resume_download(); - } -} - -// ============================ Worker implementation ============================ - -Worker::Worker(ServerImpl& server) - : logger_ptr{std::make_shared(util::LogCategory::server, "Worker: ", server.logger_ptr)} - // Throws - , logger(*logger_ptr) - , m_server{server} - , m_file_access_cache{server.get_config().max_open_files, logger, *this, server.get_config().encryption_key} -{ - util::seed_prng_nondeterministically(m_random); // Throws -} - - -void Worker::enqueue(ServerFile* file) -{ - util::LockGuard lock{m_mutex}; - m_queue.push_back(file); // Throws - m_cond.notify_all(); -} - - -std::mt19937_64& Worker::server_history_get_random() noexcept -{ - return m_random; -} - - -void Worker::run() -{ - for (;;) { - ServerFile* file = nullptr; - { - util::LockGuard lock{m_mutex}; - for (;;) { - if (REALM_UNLIKELY(m_stop)) - return; - if (!m_queue.empty()) { - file = m_queue.front(); - m_queue.pop_front(); - break; - } - m_cond.wait(lock); - } - } - file->worker_process_work_unit(m_state); // Throws - } -} - - -void Worker::stop() noexcept -{ - util::LockGuard lock{m_mutex}; - m_stop = true; - m_cond.notify_all(); -} - - -// ============================ ServerImpl implementation ============================ - -ServerImpl::ServerImpl(const std::string& root_dir, util::Optional pkey, Server::Config config) - : logger_ptr{std::make_shared(util::LogCategory::server, std::move(config.logger))} - , logger{*logger_ptr} - , m_config{std::move(config)} - , m_max_upload_backlog{determine_max_upload_backlog(config)} - , m_root_dir{root_dir} // Throws - , m_access_control{std::move(pkey)} - , m_protocol_version_range{determine_protocol_version_range(config)} // Throws - , m_file_access_cache{m_config.max_open_files, logger, *this, config.encryption_key} // Throws - , m_worker{*this} // Throws - , m_acceptor{get_service()} - , m_server_protocol{} // Throws - , m_compress_memory_arena{} // Throws -{ - if (m_config.ssl) { - m_ssl_context = std::make_unique(); // Throws - m_ssl_context->use_certificate_chain_file(m_config.ssl_certificate_path); // Throws - m_ssl_context->use_private_key_file(m_config.ssl_certificate_key_path); // Throws - } -} - - -ServerImpl::~ServerImpl() noexcept -{ - bool server_destroyed_while_still_running = m_running; - REALM_ASSERT_RELEASE(!server_destroyed_while_still_running); -} - - -void ServerImpl::start() -{ - logger.info("Realm sync server started (%1)", REALM_VER_CHUNK); // Throws - logger.info("Supported protocol versions: %1-%2 (%3-%4 configured)", - ServerImplBase::get_oldest_supported_protocol_version(), get_current_protocol_version(), - m_protocol_version_range.first, - m_protocol_version_range.second); // Throws - logger.info("Platform: %1", util::get_platform_info()); - bool is_debug_build = false; -#if REALM_DEBUG - is_debug_build = true; -#endif - { - const char* lead_text = "Build mode"; - if (is_debug_build) { - logger.info("%1: Debug", lead_text); // Throws - } - else { - logger.info("%1: Release", lead_text); // Throws - } - } - if (is_debug_build) { - logger.warn("Build mode is Debug! CAN SEVERELY IMPACT PERFORMANCE - " - "NOT RECOMMENDED FOR PRODUCTION"); // Throws - } - logger.info("Directory holding persistent state: %1", m_root_dir); // Throws - logger.info("Maximum number of open files: %1", m_config.max_open_files); // Throws - { - const char* lead_text = "Encryption"; - if (m_config.encryption_key) { - logger.info("%1: Yes", lead_text); // Throws - } - else { - logger.info("%1: No", lead_text); // Throws - } - } - logger.info("Log level: %1", logger.get_level_threshold()); // Throws - { - const char* lead_text = "Disable sync to disk"; - if (m_config.disable_sync_to_disk) { - logger.info("%1: All files", lead_text); // Throws - } - else { - logger.info("%1: No", lead_text); // Throws - } - } - if (m_config.disable_sync_to_disk) { - logger.warn("Testing/debugging feature 'disable sync to disk' enabled - " - "never do this in production!"); // Throws - } - logger.info("Download bootstrap caching: %1", - (m_config.enable_download_bootstrap_cache ? "Yes" : "No")); // Throws - logger.info("Max download size: %1 bytes", m_config.max_download_size); // Throws - logger.info("Max upload backlog: %1 bytes", m_max_upload_backlog); // Throws - logger.info("HTTP request timeout: %1 ms", m_config.http_request_timeout); // Throws - logger.info("HTTP response timeout: %1 ms", m_config.http_response_timeout); // Throws - logger.info("Connection reaper timeout: %1 ms", m_config.connection_reaper_timeout); // Throws - logger.info("Connection reaper interval: %1 ms", m_config.connection_reaper_interval); // Throws - logger.info("Connection soft close timeout: %1 ms", m_config.soft_close_timeout); // Throws - logger.debug("Authorization header name: %1", m_config.authorization_header_name); // Throws - - m_realm_names = _impl::find_realm_files(m_root_dir); // Throws - - initiate_connection_reaper_timer(m_config.connection_reaper_interval); // Throws - - listen(); // Throws -} - - -void ServerImpl::run() -{ - auto ta = util::make_temp_assign(m_running, true); - - { - auto worker_thread = util::make_thread_exec_guard(m_worker, *this); // Throws - std::string name; - if (util::Thread::get_name(name)) { - name += "-worker"; - worker_thread.start_with_signals_blocked(name); // Throws - } - else { - worker_thread.start_with_signals_blocked(); // Throws - } - - m_service.run(); // Throws - - worker_thread.stop_and_rethrow(); // Throws - } - - logger.info("Realm sync server stopped"); -} - - -void ServerImpl::stop() noexcept -{ - util::LockGuard lock{m_mutex}; - if (m_stopped) - return; - m_stopped = true; - m_wait_or_service_stopped_cond.notify_all(); - m_service.stop(); -} - - -void ServerImpl::inc_byte_size_for_pending_downstream_changesets(std::size_t byte_size) -{ - m_pending_changesets_from_downstream_byte_size += byte_size; - logger.debug("Byte size for pending downstream changesets incremented by " - "%1 to reach a total of %2", - byte_size, - m_pending_changesets_from_downstream_byte_size); // Throws -} - - -void ServerImpl::dec_byte_size_for_pending_downstream_changesets(std::size_t byte_size) -{ - REALM_ASSERT(byte_size <= m_pending_changesets_from_downstream_byte_size); - m_pending_changesets_from_downstream_byte_size -= byte_size; - logger.debug("Byte size for pending downstream changesets decremented by " - "%1 to reach a total of %2", - byte_size, - m_pending_changesets_from_downstream_byte_size); // Throws -} - - -std::mt19937_64& ServerImpl::server_history_get_random() noexcept -{ - return get_random(); -} - - -void ServerImpl::listen() -{ - network::Resolver resolver{get_service()}; - network::Resolver::Query query(m_config.listen_address, m_config.listen_port, - network::Resolver::Query::passive | network::Resolver::Query::address_configured); - network::Endpoint::List endpoints = resolver.resolve(query); // Throws - - auto i = endpoints.begin(); - auto end = endpoints.end(); - for (;;) { - std::error_code ec; - m_acceptor.open(i->protocol(), ec); - if (!ec) { - using SocketBase = network::SocketBase; - m_acceptor.set_option(SocketBase::reuse_address(m_config.reuse_address), ec); - if (!ec) { - m_acceptor.bind(*i, ec); - if (!ec) - break; - } - m_acceptor.close(); - } - if (i + 1 == end) { - for (auto i2 = endpoints.begin(); i2 != i; ++i2) { - // FIXME: We don't have the error code for previous attempts, so - // can't print a nice message. - logger.error("Failed to bind to %1:%2", i2->address(), - i2->port()); // Throws - } - logger.error("Failed to bind to %1:%2: %3", i->address(), i->port(), - ec.message()); // Throws - throw std::runtime_error("Could not create a listening socket: All endpoints failed"); - } - } - - m_acceptor.listen(m_config.listen_backlog); - - network::Endpoint local_endpoint = m_acceptor.local_endpoint(); - const char* ssl_mode = (m_ssl_context ? "TLS" : "non-TLS"); - logger.info("Listening on %1:%2 (max backlog is %3, %4)", local_endpoint.address(), local_endpoint.port(), - m_config.listen_backlog, ssl_mode); // Throws - - initiate_accept(); -} - - -void ServerImpl::initiate_accept() -{ - auto handler = [this](std::error_code ec) { - if (ec != util::error::operation_aborted) - handle_accept(ec); - }; - bool is_ssl = bool(m_ssl_context); - m_next_http_conn.reset(new HTTPConnection(*this, ++m_next_conn_id, is_ssl)); // Throws - m_acceptor.async_accept(m_next_http_conn->get_socket(), m_next_http_conn_endpoint, std::move(handler)); // Throws -} - - -void ServerImpl::handle_accept(std::error_code ec) -{ - if (ec) { - if (ec != util::error::connection_aborted) { - REALM_ASSERT(ec != util::error::operation_aborted); - - // We close the reserved files to get a few extra file descriptors. - for (size_t i = 0; i < sizeof(m_reserved_files) / sizeof(m_reserved_files[0]); ++i) { - m_reserved_files[i].reset(); - } - - // FIXME: There are probably errors that need to be treated - // specially, and not cause the server to "crash". - - if (ec == make_basic_system_error_code(EMFILE)) { - logger.error("Failed to accept a connection due to the file descriptor limit, " - "consider increasing the limit in your system config"); // Throws - throw OutOfFilesError(ec); - } - else { - throw std::system_error(ec); - } - } - logger.debug("Skipping aborted connection"); // Throws - } - else { - HTTPConnection& conn = *m_next_http_conn; - if (m_config.tcp_no_delay) - conn.get_socket().set_option(network::SocketBase::no_delay(true)); // Throws - m_http_connections.emplace(conn.get_id(), std::move(m_next_http_conn)); // Throws - Formatter& formatter = m_misc_buffers.formatter; - formatter.reset(); - formatter << "[" << m_next_http_conn_endpoint.address() << "]:" << m_next_http_conn_endpoint.port(); // Throws - std::string remote_endpoint = {formatter.data(), formatter.size()}; // Throws - conn.initiate(std::move(remote_endpoint)); // Throws - } - initiate_accept(); // Throws -} - - -void ServerImpl::remove_http_connection(std::int_fast64_t conn_id) noexcept -{ - m_http_connections.erase(conn_id); -} - - -void ServerImpl::add_sync_connection(int_fast64_t connection_id, std::unique_ptr&& sync_conn) -{ - m_sync_connections.emplace(connection_id, std::move(sync_conn)); -} - - -void ServerImpl::remove_sync_connection(int_fast64_t connection_id) -{ - m_sync_connections.erase(connection_id); -} - - -void ServerImpl::set_connection_reaper_timeout(milliseconds_type timeout) -{ - get_service().post([this, timeout](Status) { - m_config.connection_reaper_timeout = timeout; - }); -} - - -void ServerImpl::close_connections() -{ - get_service().post([this](Status) { - do_close_connections(); // Throws - }); -} - - -bool ServerImpl::map_virtual_to_real_path(const std::string& virt_path, std::string& real_path) -{ - return _impl::map_virt_to_real_realm_path(m_root_dir, virt_path, real_path); // Throws -} - - -void ServerImpl::recognize_external_change(const std::string& virt_path) -{ - std::string virt_path_2 = virt_path; // Throws (copy) - get_service().post([this, virt_path = std::move(virt_path_2)](Status) { - do_recognize_external_change(virt_path); // Throws - }); // Throws -} - - -void ServerImpl::stop_sync_and_wait_for_backup_completion( - util::UniqueFunction completion_handler, milliseconds_type timeout) -{ - logger.info("stop_sync_and_wait_for_backup_completion() called with " - "timeout = %1", - timeout); // Throws - - get_service().post([this, completion_handler = std::move(completion_handler), timeout](Status) mutable { - do_stop_sync_and_wait_for_backup_completion(std::move(completion_handler), - timeout); // Throws - }); -} - - -void ServerImpl::initiate_connection_reaper_timer(milliseconds_type timeout) -{ - m_connection_reaper_timer.emplace(get_service()); - m_connection_reaper_timer->async_wait(std::chrono::milliseconds(timeout), [this, timeout](Status status) { - if (status != ErrorCodes::OperationAborted) { - reap_connections(); // Throws - initiate_connection_reaper_timer(timeout); // Throws - } - }); // Throws -} - - -void ServerImpl::reap_connections() -{ - logger.debug("Discarding dead connections"); // Throws - SteadyTimePoint now = steady_clock_now(); - { - auto end = m_http_connections.end(); - auto i = m_http_connections.begin(); - while (i != end) { - HTTPConnection& conn = *i->second; - ++i; - // Suicide - conn.terminate_if_dead(now); // Throws - } - } - { - auto end = m_sync_connections.end(); - auto i = m_sync_connections.begin(); - while (i != end) { - SyncConnection& conn = *i->second; - ++i; - // Suicide - conn.terminate_if_dead(now); // Throws - } - } -} - - -void ServerImpl::do_close_connections() -{ - for (auto& entry : m_sync_connections) { - SyncConnection& conn = *entry.second; - conn.initiate_soft_close(); // Throws - } -} - - -void ServerImpl::do_recognize_external_change(const std::string& virt_path) -{ - auto i = m_files.find(virt_path); - if (i == m_files.end()) - return; - ServerFile& file = *i->second; - file.recognize_external_change(); -} - - -void ServerImpl::do_stop_sync_and_wait_for_backup_completion( - util::UniqueFunction completion_handler, milliseconds_type timeout) -{ - static_cast(timeout); - if (m_sync_stopped) - return; - do_close_connections(); // Throws - m_sync_stopped = true; - bool completion_reached = false; - completion_handler(completion_reached); // Throws -} - - -// ============================ SyncConnection implementation ============================ - -SyncConnection::~SyncConnection() noexcept -{ - m_sessions_enlisted_to_send.clear(); - m_sessions.clear(); -} - - -void SyncConnection::initiate() -{ - m_last_activity_at = steady_clock_now(); - logger.debug("Sync Connection initiated"); - m_websocket.initiate_server_websocket_after_handshake(); - send_log_message(util::Logger::Level::info, "Client connection established with server", 0, - m_appservices_request_id); -} - - -template -void SyncConnection::terminate(Logger::Level log_level, const char* log_message, Params... log_params) -{ - terminate_sessions(); // Throws - logger.log(log_level, log_message, log_params...); // Throws - m_websocket.stop(); - m_ssl_stream.reset(); - m_socket.reset(); - // Suicide - m_server.remove_sync_connection(m_id); -} - - -void SyncConnection::terminate_if_dead(SteadyTimePoint now) -{ - milliseconds_type time = steady_duration(m_last_activity_at, now); - const Server::Config& config = m_server.get_config(); - if (m_is_closing) { - if (time >= config.soft_close_timeout) { - // Suicide - terminate(Logger::Level::detail, - "Sync connection closed (timeout during soft close)"); // Throws - } - } - else { - if (time >= config.connection_reaper_timeout) { - // Suicide - terminate(Logger::Level::detail, - "Sync connection closed (no heartbeat)"); // Throws - } - } -} - - -void SyncConnection::enlist_to_send(Session* sess) noexcept -{ - REALM_ASSERT(m_send_trigger); - REALM_ASSERT(!m_is_closing); - REALM_ASSERT(!sess->is_enlisted_to_send()); - m_sessions_enlisted_to_send.push_back(sess); - m_send_trigger->trigger(); -} - - -void SyncConnection::handle_protocol_error(Status status) -{ - logger.error("%1", status); - switch (status.code()) { - case ErrorCodes::SyncProtocolInvariantFailed: - protocol_error(ProtocolError::bad_syntax); // Throws - break; - case ErrorCodes::LimitExceeded: - protocol_error(ProtocolError::limits_exceeded); // Throws - break; - default: - protocol_error(ProtocolError::other_error); - break; - } -} - -void SyncConnection::receive_bind_message(session_ident_type session_ident, std::string path, - std::string signed_user_token, bool need_client_file_ident, - bool is_subserver) -{ - auto p = m_sessions.emplace(session_ident, nullptr); // Throws - bool was_inserted = p.second; - if (REALM_UNLIKELY(!was_inserted)) { - logger.error("Overlapping reuse of session identifier %1 in BIND message", - session_ident); // Throws - protocol_error(ProtocolError::reuse_of_session_ident); // Throws - return; - } - try { - p.first->second.reset(new Session(*this, session_ident)); // Throws - } - catch (...) { - m_sessions.erase(p.first); - throw; - } - - Session& sess = *p.first->second; - sess.initiate(); // Throws - ProtocolError error; - bool success = - sess.receive_bind_message(std::move(path), std::move(signed_user_token), need_client_file_ident, is_subserver, - error); // Throws - if (REALM_UNLIKELY(!success)) // Throws - protocol_error(error, &sess); // Throws -} - - -void SyncConnection::receive_ident_message(session_ident_type session_ident, file_ident_type client_file_ident, - salt_type client_file_ident_salt, version_type scan_server_version, - version_type scan_client_version, version_type latest_server_version, - salt_type latest_server_version_salt) -{ - auto i = m_sessions.find(session_ident); - if (REALM_UNLIKELY(i == m_sessions.end())) { - bad_session_ident("IDENT", session_ident); // Throws - return; - } - Session& sess = *i->second; - if (REALM_UNLIKELY(sess.unbind_message_received())) { - message_after_unbind("IDENT", session_ident); // Throws - return; - } - if (REALM_UNLIKELY(sess.error_occurred())) { - // Protocol state is SendError or WaitForUnbindErr. In these states, all - // messages, other than UNBIND, must be ignored. - return; - } - if (REALM_UNLIKELY(sess.must_send_ident_message())) { - logger.error("Received IDENT message before IDENT message was sent"); // Throws - protocol_error(ProtocolError::bad_message_order); // Throws - return; - } - if (REALM_UNLIKELY(sess.ident_message_received())) { - logger.error("Received second IDENT message for session"); // Throws - protocol_error(ProtocolError::bad_message_order); // Throws - return; - } - - ProtocolError error = {}; - bool success = sess.receive_ident_message(client_file_ident, client_file_ident_salt, scan_server_version, - scan_client_version, latest_server_version, latest_server_version_salt, - error); // Throws - if (REALM_UNLIKELY(!success)) // Throws - protocol_error(error, &sess); // Throws -} - -void SyncConnection::receive_upload_message(session_ident_type session_ident, version_type progress_client_version, - version_type progress_server_version, version_type locked_server_version, - const UploadChangesets& upload_changesets) -{ - auto i = m_sessions.find(session_ident); - if (REALM_UNLIKELY(i == m_sessions.end())) { - bad_session_ident("UPLOAD", session_ident); // Throws - return; - } - Session& sess = *i->second; - if (REALM_UNLIKELY(sess.unbind_message_received())) { - message_after_unbind("UPLOAD", session_ident); // Throws - return; - } - if (REALM_UNLIKELY(sess.error_occurred())) { - // Protocol state is SendError or WaitForUnbindErr. In these states, all - // messages, other than UNBIND, must be ignored. - return; - } - if (REALM_UNLIKELY(!sess.ident_message_received())) { - message_before_ident("UPLOAD", session_ident); // Throws - return; - } - - ProtocolError error = {}; - bool success = sess.receive_upload_message(progress_client_version, progress_server_version, - locked_server_version, upload_changesets, error); // Throws - if (REALM_UNLIKELY(!success)) // Throws - protocol_error(error, &sess); // Throws -} - - -void SyncConnection::receive_mark_message(session_ident_type session_ident, request_ident_type request_ident) -{ - auto i = m_sessions.find(session_ident); - if (REALM_UNLIKELY(i == m_sessions.end())) { - bad_session_ident("MARK", session_ident); - return; - } - Session& sess = *i->second; - if (REALM_UNLIKELY(sess.unbind_message_received())) { - message_after_unbind("MARK", session_ident); // Throws - return; - } - if (REALM_UNLIKELY(sess.error_occurred())) { - // Protocol state is SendError or WaitForUnbindErr. In these states, all - // messages, other than UNBIND, must be ignored. - return; - } - if (REALM_UNLIKELY(!sess.ident_message_received())) { - message_before_ident("MARK", session_ident); // Throws - return; - } - - ProtocolError error; - bool success = sess.receive_mark_message(request_ident, error); // Throws - if (REALM_UNLIKELY(!success)) // Throws - protocol_error(error, &sess); // Throws -} - - -void SyncConnection::receive_unbind_message(session_ident_type session_ident) -{ - auto i = m_sessions.find(session_ident); // Throws - if (REALM_UNLIKELY(i == m_sessions.end())) { - bad_session_ident("UNBIND", session_ident); // Throws - return; - } - Session& sess = *i->second; - if (REALM_UNLIKELY(sess.unbind_message_received())) { - message_after_unbind("UNBIND", session_ident); // Throws - return; - } - - sess.receive_unbind_message(); // Throws - // NOTE: The session might have gotten destroyed at this time! -} - - -void SyncConnection::receive_ping(milliseconds_type timestamp, milliseconds_type rtt) -{ - logger.debug("Received: PING(timestamp=%1, rtt=%2)", timestamp, rtt); // Throws - m_send_pong = true; - m_last_ping_timestamp = timestamp; - if (!m_is_sending) - send_next_message(); -} - - -void SyncConnection::receive_error_message(session_ident_type session_ident, int error_code, - std::string_view error_body) -{ - logger.debug("Received: ERROR(error_code=%1, message_size=%2, session_ident=%3)", error_code, error_body.size(), - session_ident); // Throws - auto i = m_sessions.find(session_ident); - if (REALM_UNLIKELY(i == m_sessions.end())) { - bad_session_ident("ERROR", session_ident); - return; - } - Session& sess = *i->second; - if (REALM_UNLIKELY(sess.unbind_message_received())) { - message_after_unbind("ERROR", session_ident); // Throws - return; - } - - sess.receive_error_message(session_ident, error_code, error_body); // Throws -} - -void SyncConnection::send_log_message(util::Logger::Level level, const std::string&& message, - session_ident_type sess_ident, std::optional co_id) -{ - if (get_client_protocol_version() < SyncConnection::SERVER_LOG_PROTOCOL_VERSION) { - return logger.log(level, message.c_str()); - } - - LogMessage log_msg{sess_ident, level, std::move(message), std::move(co_id)}; - { - std::lock_guard lock(m_log_mutex); - m_log_messages.push(std::move(log_msg)); - } - m_send_trigger->trigger(); -} - - -void SyncConnection::bad_session_ident(const char* message_type, session_ident_type session_ident) -{ - logger.error("Bad session identifier in %1 message, session_ident = %2", message_type, - session_ident); // Throws - protocol_error(ProtocolError::bad_session_ident); // Throws -} - - -void SyncConnection::message_after_unbind(const char* message_type, session_ident_type session_ident) -{ - logger.error("Received %1 message after UNBIND message, session_ident = %2", message_type, - session_ident); // Throws - protocol_error(ProtocolError::bad_message_order); // Throws -} - - -void SyncConnection::message_before_ident(const char* message_type, session_ident_type session_ident) -{ - logger.error("Received %1 message before IDENT message, session_ident = %2", message_type, - session_ident); // Throws - protocol_error(ProtocolError::bad_message_order); // Throws -} - - -void SyncConnection::handle_message_received(const char* data, size_t size) -{ - // parse_message_received() parses the message and calls the - // proper handler on the SyncConnection object (this). - get_server_protocol().parse_message_received(*this, std::string_view(data, size)); - return; -} - - -void SyncConnection::handle_ping_received(const char* data, size_t size) -{ - // parse_message_received() parses the message and calls the - // proper handler on the SyncConnection object (this). - get_server_protocol().parse_ping_received(*this, std::string_view(data, size)); - return; -} - - -void SyncConnection::send_next_message() -{ - REALM_ASSERT(!m_is_sending); - REALM_ASSERT(!m_sending_pong); - if (m_send_pong) { - send_pong(m_last_ping_timestamp); - if (m_sending_pong) - return; - } - for (;;) { - Session* sess = m_sessions_enlisted_to_send.pop_front(); - if (!sess) { - // No sessions were enlisted to send - if (REALM_LIKELY(!m_is_closing)) - break; // Check to see if there are any log messages to go out - // Send a connection level ERROR - REALM_ASSERT(!is_session_level_error(m_error_code)); - initiate_write_error(m_error_code, m_error_session_ident); // Throws - return; - } - sess->send_message(); // Throws - // NOTE: The session might have gotten destroyed at this time! - - // At this point, `m_is_sending` is true if, and only if the session - // chose to send a message. If it chose to not send a message, we must - // loop back and give the next session in `m_sessions_enlisted_to_send` - // a chance. - if (m_is_sending) - return; - } - { - std::lock_guard lock(m_log_mutex); - if (!m_log_messages.empty()) { - send_log_message(m_log_messages.front()); - m_log_messages.pop(); - } - } - // Otherwise, nothing to do -} - - -void SyncConnection::initiate_write_output_buffer() -{ - auto handler = [this](std::error_code ec, size_t) { - if (!ec) { - handle_write_output_buffer(); - } - }; - - m_websocket.async_write_binary(m_output_buffer.data(), m_output_buffer.size(), - std::move(handler)); // Throws - m_is_sending = true; -} - - -void SyncConnection::initiate_pong_output_buffer() -{ - auto handler = [this](std::error_code ec, size_t) { - if (!ec) { - handle_pong_output_buffer(); - } - }; - - REALM_ASSERT(!m_is_sending); - REALM_ASSERT(!m_sending_pong); - m_websocket.async_write_binary(m_output_buffer.data(), m_output_buffer.size(), - std::move(handler)); // Throws - - m_is_sending = true; - m_sending_pong = true; -} - - -void SyncConnection::send_pong(milliseconds_type timestamp) -{ - REALM_ASSERT(m_send_pong); - REALM_ASSERT(!m_sending_pong); - m_send_pong = false; - logger.debug("Sending: PONG(timestamp=%1)", timestamp); // Throws - - OutputBuffer& out = get_output_buffer(); - get_server_protocol().make_pong(out, timestamp); // Throws - - initiate_pong_output_buffer(); // Throws -} - -void SyncConnection::send_log_message(const LogMessage& log_msg) -{ - OutputBuffer& out = get_output_buffer(); - get_server_protocol().make_log_message(out, log_msg.level, log_msg.message, log_msg.sess_ident, - log_msg.co_id); // Throws - - initiate_write_output_buffer(); // Throws -} - - -void SyncConnection::handle_write_output_buffer() -{ - release_output_buffer(); - m_is_sending = false; - send_next_message(); // Throws -} - - -void SyncConnection::handle_pong_output_buffer() -{ - release_output_buffer(); - REALM_ASSERT(m_is_sending); - REALM_ASSERT(m_sending_pong); - m_is_sending = false; - m_sending_pong = false; - send_next_message(); // Throws -} - - -void SyncConnection::initiate_write_error(ProtocolError error_code, session_ident_type session_ident) -{ - const char* message = get_protocol_error_message(int(error_code)); - std::size_t message_size = std::strlen(message); - bool try_again = determine_try_again(error_code); - - logger.detail("Sending: ERROR(error_code=%1, message_size=%2, try_again=%3, session_ident=%4)", int(error_code), - message_size, try_again, session_ident); // Throws - - OutputBuffer& out = get_output_buffer(); - int protocol_version = get_client_protocol_version(); - get_server_protocol().make_error_message(protocol_version, out, error_code, message, message_size, try_again, - session_ident); // Throws - - auto handler = [this](std::error_code ec, size_t) { - handle_write_error(ec); // Throws - }; - m_websocket.async_write_binary(out.data(), out.size(), std::move(handler)); - m_is_sending = true; -} - - -void SyncConnection::handle_write_error(std::error_code ec) -{ - m_is_sending = false; - REALM_ASSERT(m_is_closing); - if (!m_ssl_stream) { - m_socket->shutdown(network::Socket::shutdown_send, ec); - if (ec && ec != make_basic_system_error_code(ENOTCONN)) - throw std::system_error(ec); - } -} - - -// For connection level errors, `sess` is ignored. For session level errors, a -// session must be specified. -// -// If a session is specified, that session object will have been detached from -// the ServerFile object (and possibly destroyed) upon return from -// protocol_error(). -// -// If a session is specified for a protocol level error, that session object -// will have been destroyed upon return from protocol_error(). For session level -// errors, the specified session will have been destroyed upon return from -// protocol_error() if, and only if the negotiated protocol version is less than -// 23. -void SyncConnection::protocol_error(ProtocolError error_code, Session* sess) -{ - REALM_ASSERT(!m_is_closing); - bool session_level = is_session_level_error(error_code); - REALM_ASSERT(!session_level || sess); - REALM_ASSERT(!sess || m_sessions.count(sess->get_session_ident()) == 1); - if (logger.would_log(util::Logger::Level::debug)) { - const char* message = get_protocol_error_message(int(error_code)); - Logger& logger_2 = (session_level ? sess->logger : logger); - logger_2.debug("Protocol error: %1 (error_code=%2)", message, int(error_code)); // Throws - } - session_ident_type session_ident = (session_level ? sess->get_session_ident() : 0); - if (session_level) { - sess->initiate_deactivation(error_code); // Throws - return; - } - do_initiate_soft_close(error_code, session_ident); // Throws -} - - -void SyncConnection::do_initiate_soft_close(ProtocolError error_code, session_ident_type session_ident) -{ - REALM_ASSERT(get_protocol_error_message(int(error_code))); - - // With recent versions of the protocol (when the version is greater than, - // or equal to 23), this function will only be called for connection level - // errors, never for session specific errors. However, for the purpose of - // emulating earlier protocol versions, this function might be called for - // session specific errors too. - REALM_ASSERT(is_session_level_error(error_code) == (session_ident != 0)); - REALM_ASSERT(!is_session_level_error(error_code)); - - REALM_ASSERT(m_send_trigger); - REALM_ASSERT(!m_is_closing); - m_is_closing = true; - - m_error_code = error_code; - m_error_session_ident = session_ident; - - // Don't waste time and effort sending any other messages - m_send_pong = false; - m_sessions_enlisted_to_send.clear(); - - m_receiving_session = nullptr; - - terminate_sessions(); // Throws - - m_send_trigger->trigger(); -} - - -void SyncConnection::close_due_to_close_by_client(std::error_code ec) -{ - auto log_level = (ec == util::MiscExtErrors::end_of_input ? Logger::Level::detail : Logger::Level::info); - // Suicide - terminate(log_level, "Sync connection closed by client: %1", ec.message()); // Throws -} - - -void SyncConnection::close_due_to_error(std::error_code ec) -{ - // Suicide - terminate(Logger::Level::error, "Sync connection closed due to error: %1", - ec.message()); // Throws -} - - -void SyncConnection::terminate_sessions() -{ - for (auto& entry : m_sessions) { - Session& sess = *entry.second; - sess.terminate(); // Throws - } - m_sessions_enlisted_to_send.clear(); - m_sessions.clear(); -} - - -void SyncConnection::initiate_soft_close() -{ - if (!m_is_closing) { - session_ident_type session_ident = 0; // Not session specific - do_initiate_soft_close(ProtocolError::connection_closed, session_ident); // Throws - } -} - - -void SyncConnection::discard_session(session_ident_type session_ident) noexcept -{ - m_sessions.erase(session_ident); -} - -} // anonymous namespace - - -// ============================ sync::Server implementation ============================ - -class Server::Implementation : public ServerImpl { -public: - Implementation(const std::string& root_dir, util::Optional pkey, Server::Config config) - : ServerImpl{root_dir, std::move(pkey), std::move(config)} // Throws - { - } - virtual ~Implementation() {} -}; - - -Server::Server(const std::string& root_dir, util::Optional pkey, Config config) - : m_impl{new Implementation{root_dir, std::move(pkey), std::move(config)}} // Throws -{ -} - - -Server::Server(Server&& serv) noexcept - : m_impl{std::move(serv.m_impl)} -{ -} - - -Server::~Server() noexcept {} - - -void Server::start() -{ - m_impl->start(); // Throws -} - - -void Server::start(const std::string& listen_address, const std::string& listen_port, bool reuse_address) -{ - m_impl->start(listen_address, listen_port, reuse_address); // Throws -} - - -network::Endpoint Server::listen_endpoint() const -{ - return m_impl->listen_endpoint(); // Throws -} - - -void Server::run() -{ - m_impl->run(); // Throws -} - - -void Server::stop() noexcept -{ - m_impl->stop(); -} - - -uint_fast64_t Server::errors_seen() const noexcept -{ - return m_impl->errors_seen; -} - - -void Server::stop_sync_and_wait_for_backup_completion(util::UniqueFunction completion_handler, - milliseconds_type timeout) -{ - m_impl->stop_sync_and_wait_for_backup_completion(std::move(completion_handler), timeout); // Throws -} - - -void Server::set_connection_reaper_timeout(milliseconds_type timeout) -{ - m_impl->set_connection_reaper_timeout(timeout); -} - - -void Server::close_connections() -{ - m_impl->close_connections(); -} - - -bool Server::map_virtual_to_real_path(const std::string& virt_path, std::string& real_path) -{ - return m_impl->map_virtual_to_real_path(virt_path, real_path); // Throws -} - - -void Server::recognize_external_change(const std::string& virt_path) -{ - m_impl->recognize_external_change(virt_path); // Throws -} - - -void Server::get_workunit_timers(milliseconds_type& parallel_section, milliseconds_type& sequential_section) -{ - m_impl->get_workunit_timers(parallel_section, sequential_section); -} diff --git a/src/realm/sync/noinst/server/server.hpp b/src/realm/sync/noinst/server/server.hpp deleted file mode 100644 index b455f7b41da..00000000000 --- a/src/realm/sync/noinst/server/server.hpp +++ /dev/null @@ -1,370 +0,0 @@ -#ifndef REALM_SYNC_SERVER_HPP -#define REALM_SYNC_SERVER_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace realm { -namespace sync { - -// FIXME: Currently this exception is only used when the server runs out of -// file descriptors at connection accept. -class OutOfFilesError : public std::exception { -public: - OutOfFilesError(const std::error_code ec) - : m_ec{ec} - { - } - std::error_code code() const noexcept - { - return m_ec; - } - const char* what() const noexcept override - { - return "Out of file despriptors (EMFILE)"; - } - -private: - std::error_code m_ec; -}; - - -/// \brief Server of the Realm synchronization protocol. -/// -/// Instances of this class are servers of the WebSocket-based Realm -/// synchronization protocol (`/doc/protocol.md`), and are generally referred to -/// simply as *sync servers*. -/// -/// No agent external to a sync server is allowed to open Realm files belonging -/// to that sync server (in \a root_dir as passed to the constructor) while that -/// sync server is running. -class Server { -public: - using SessionBootstrapCallback = void(std::string_view virt_path, file_ident_type client_file_ident); - - // FIXME: The default values for `http_request_timeout`, - // `http_response_timeout`, `connection_reaper_timeout`, and - // `soft_close_timeout` ought to be much lower (1 minute, 30 seconds, 3 - // minutes, and 30 seconds respectively) than they are. Their current values - // are due to the fact that the server is single threaded, and that some - // operations take more than 5 minutes to complete. - static constexpr milliseconds_type default_http_request_timeout = 600000; // 10 minutes - static constexpr milliseconds_type default_http_response_timeout = 600000; // 10 minutes - static constexpr milliseconds_type default_connection_reaper_timeout = 1800000; // 30 minutes - static constexpr milliseconds_type default_connection_reaper_interval = 60000; // 1 minute - static constexpr milliseconds_type default_soft_close_timeout = 600000; // 10 minutes - - struct Config { - Config() {} - - /// The maximum number of Realm files that will be kept open - /// concurrently by each major thread inside the server. The server - /// currently has two major threads (foreground and background). The - /// server keeps a cache of open Realm files for efficiency reasons (one - /// for each major thread). - long max_open_files = 256; - - /// An optional custom clock to be used for token expiration checks. If - /// no clock is specified, the server will use the system clock. - Clock* token_expiration_clock = nullptr; - - /// An optional thread-safe logger to be used by the server. If no - /// logger is specified, the server will use an instance of - /// util::StderrLogger with the log level threshold set to - /// util::Logger::Level::info. - std::shared_ptr logger; - - /// A unique id of this server. Used in the backup protocol to tell - /// slaves apart. - std::string id = "unknown"; - - /// The address at which the listening socket is bound. - /// The address can be a name or on numerical form. - /// Use "localhost" to listen on the loopback interface. - std::string listen_address; - - /// The port at which the listening socket is bound. - /// The port can be a name or on numerical form. - /// Use the empty string to have the system assign a dynamic - /// listening port. - std::string listen_port; - - bool reuse_address = true; - - /// authorization_header_name sets the name of the HTTP header used to - /// receive the Realm access token. The value of the HTTP header is - /// "Bearer " - std::string authorization_header_name = "Authorization"; - - /// The listening socket accepts TLS/SSL connections if `ssl` is - /// true, and non-secure tcp connections otherwise. - bool ssl = false; - - /// The path of the certificate that will be sent to clients during - /// the SSL/TLS handshake. - /// - /// From the point of view of OpenSSL, this file will be passed to - /// `SSL_CTX_use_certificate_chain_file()`. - /// - /// This option is ignored if `ssl` is false. - std::string ssl_certificate_path; - - /// The path of the private key corresponding to the certificate. - /// - /// From the point of view of OpenSSL, this file will be passed to - /// `SSL_CTX_use_PrivateKey_file()`. - /// - /// This option is ignored if `ssl` is false. - std::string ssl_certificate_key_path; - - /// The time allotted to the reception of a complete HTTP request. This - /// counts from the point in time where the raw TCP connection is - /// accepted by the server, or, in case of HTTP pipelining, from the - /// point in time where writing of the previous response completed. If - /// this time is exceeded, the connection will be terminated by the - /// server. - milliseconds_type http_request_timeout = default_http_request_timeout; - - /// The time allotted to the transmission of the complete HTTP - /// response. If this time is exceeded, the connection will be - /// terminated by the server. - milliseconds_type http_response_timeout = default_http_response_timeout; - - /// If no heartbeat, and no other message has been received via a - /// connection for a certain amount of time, that connection will be - /// discarded by the connection reaper. This option specifies that - /// amount of time. See also \ref connection_reaper_interval. - milliseconds_type connection_reaper_timeout = default_connection_reaper_timeout; - - /// The time between activations of the connection reaper. On each - /// activation, every connection is checked for vitality (see \ref - /// connection_reaper_timeout). - milliseconds_type connection_reaper_interval = default_connection_reaper_interval; - - /// In some cases, the server attempts so send an ERROR message to the - /// client before closing the connection (a soft close). The server will - /// then wait for the client to close the - /// connection. `soft_close_timeout` specifies the maximum amount of - /// time, that the server will wait before terminating the connection - /// itself. This counts from when writing of the ERROR message is - /// initiated. - milliseconds_type soft_close_timeout = default_soft_close_timeout; - - /// If set to true, the server will cache the contents of the DOWNLOAD - /// message(s) used for client bootstrapping. - bool enable_download_bootstrap_cache = false; - - /// The accumulated size of changesets that are included in download - /// messages. The size of the changesets is calculated before log - /// compaction (if enabled). A larger value leads to more efficient - /// log compaction and download, at the expense of higher memory pressure, - /// higher latency for sending the first changeset, and a higher probability - /// for the need to resend the same changes after network disconnects. - std::size_t max_download_size = 0x1000000; // 16 MiB - - /// The maximum number of connections that can be queued up waiting to - /// be accepted by the server. This corresponds to the `backlog` - /// argument of the `listen()` function as described by POSIX. - /// - /// On Linux, the specified value will be clamped to the value of the - /// kernel parameter `net.core.somaxconn` (also available as - /// `/proc/sys/net/core/somaxconn`). You can change the value of that - /// parameter using `sysctl -w net.core.somaxconn=...`. It is usually - /// 128 by default. - int listen_backlog = network::Acceptor::max_connections; - - /// Set the `TCP_NODELAY` option on all TCP/IP sockets. This disables - /// the Nagle algorithm. Disabling it, can in some cases be used to - /// decrease latencies, but possibly at the expense of scalability. Be - /// sure to research the subject before you enable this option. - bool tcp_no_delay = false; - - /// An optional 64 byte key to encrypt all files with. - std::optional> encryption_key; - - /// Sets a limit on the allowed accumulated size in bytes of buffered - /// incoming changesets waiting to be processed. If left at zero, an - /// implementation defined default value will be chosen. - /// - /// If the accumulated size of the currently buffered incoming - /// changesets exceeds this limit, then no additional UPLOAD messages - /// will be accepted by the server. Instead, if an UPLOAD message is - /// received, the server will terminate the session, and its - /// corresponding connection by sending - /// `ProtocolError::connection_closed`, which in this case, can be taken - /// to mean, "try again later". - /// - /// FIXME: Part of a very poor man's substitute for a proper - /// backpressure scheme. - std::size_t max_upload_backlog = 0; - - /// Disable sync to disk (fsync(), msync()) for all realm files managed - /// by this server. - /// - /// Testing/debugging feature. Should never be enabled in production. - bool disable_sync_to_disk = false; - - /// Restrict the range of protocol versions that the server will offer - /// during negotiation with clients. - /// - /// A value of zero means 'unspecified'. - /// - /// If a nonzero value is specified, then all versions that are actually - /// supported, but are greater than the specified value, will be - /// excluded from the set of effectively supported versions. - /// - /// If this leaves the effective set of supported versions empty, the - /// server constructor will throw NoSupportedProtocolVersions. - /// - /// \sa get_current_protocol_version() - int max_protocol_version = 0; - - /// Disable the download process for the specified client files. This - /// includes the sending of empty DOWNLOAD messages. - /// - /// This feature exists exclusively for testing purposes. - std::set disable_download_for; - - /// If specified, this function will be called for each synchronization - /// session that is successfully bootstrapped at the time of reception - /// of the IDENT message. - /// - /// This feature exists exclusively for testing purposes. - std::function session_bootstrap_callback; - }; - - /// See Config::max_protocol_version. - class NoSupportedProtocolVersions; - - /// \throw NoSupportedProtocolVersions See Config::max_protocol_version. - Server(const std::string& root_dir, util::Optional public_key, Config = {}); - - Server(Server&&) noexcept; - virtual ~Server() noexcept; - - /// start() binds a listening socket to the address and port specified in - /// Config and starts accepting connections. - /// The resolved endpoint (including the dynamically assigned port, if requested) - /// can be obtained by calling listen_endpoint(). - /// This can be done immediately after start() returns. - void start(); - - /// A helper function, for backwards compatibility, that starts a listening - /// socket without SSL at the specified address and port. - void start(const std::string& listen_address, const std::string& listen_port, bool reuse_address = true); - - /// Return the resolved and bound endpoint of the listening socket. - network::Endpoint listen_endpoint() const; - - /// Run the internal network event-loop of the server. At most one thread - /// may execute run() at any given time. It is an error if run() is called - /// before start() has been successfully executed. The call to run() will - /// not return until somebody calls stop() or an exception is thrown. - void run(); - - /// Stop any thread that is currently executing run(). This function may be - /// called by any thread. - void stop() noexcept; - - /// Must not be called while run() is executing. - std::uint_fast64_t errors_seen() const noexcept; - - /// stop_sync_and_wait_for_backup_completion() will immediately drop all - /// client connections, and wait for backup completion. New client - /// connections will be rejected. The completion handler is called either - /// when a backup slave has a full copy of all Realms or after timing out. - /// The function is only supposed to be used on the backup master. - /// - /// The function times out depending on the value of the argument - /// 'timeout'. If 'timeout' is non-negative, the function times out after - /// 'timeout' milliseconds. The completion handler is guaranteed to be - /// called after time out. If 'timeout' is -1, the function never times - /// out. - /// - /// The signature of the completion handler is void(bool did_complete), - /// where did_complete is true if the master knows that the slave is up to - /// date, and false otherwise. If the server is not a backup master, the - /// completion handler will be called with did_complete set to false. - /// - /// If there is no connected backup slave, or the backup slave disconnects, - /// the server will wait for the backup slave to reconnect and obtain a - /// full copy of all Realms. - /// - /// After the completion handler is called, the user will still have to stop - /// the server event loop by calling stop() and destroy the server - /// object. stop_sync_and_wait_for_backup_completion() can be called from - /// any thread. If the function is called multiple times before completion, - /// only one of the completion handlers will be called. - /// - /// The completion handler will be called by the thread that executes run(). - /// - /// CAUTION: The completion handler may be called before - /// stop_sync_and_wait_for_backup_completion() returns. - void stop_sync_and_wait_for_backup_completion(util::UniqueFunction completion_handler, - milliseconds_type timeout); - - /// See Config::connection_reaper_timeout.. - void set_connection_reaper_timeout(milliseconds_type); - - /// Close all connections with error code ProtocolError::connection_closed. - /// - /// This function exists mainly for debugging purposes. - void close_connections(); - - /// Map the specified virtual Realm path to a real file system path. The - /// returned path will be absolute if, and only if the root directory path - /// passed to the server constructor was absolute. - /// - /// If the specified virtual path is valid, this function assigns the - /// corresponding file system path to `real_path` and returns - /// true. Otherwise it returns false. - /// - /// This function is fully thread-safe and may be called at any time during - /// the life of the server object. - bool map_virtual_to_real_path(const std::string& virt_path, std::string& real_path); - - /// Inform the server about an external change to one of the Realm files - /// managed by the server. - /// - /// CAUTION: On a server where backup is enabled, Realm files are not - /// allowed to be modified by agents external to the server, i.e., they are - /// not allowed to be modified, except by the server itself. - /// - /// This function is fully thread-safe and may be called at any time during - /// the life of the server object. - void recognize_external_change(const std::string& virt_path); - - /// Get accumulated time spent on runs of the worker thread(s) since start - /// of the server. - void get_workunit_timers(milliseconds_type& parallel_section, milliseconds_type& sequential_section); - -private: - class Implementation; - std::unique_ptr m_impl; -}; - - -class Server::NoSupportedProtocolVersions : public std::exception { -public: - const char* what() const noexcept override final - { - return "No supported protocol versions"; - } -}; - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_SERVER_HPP diff --git a/src/realm/sync/noinst/server/server_dir.cpp b/src/realm/sync/noinst/server/server_dir.cpp deleted file mode 100644 index 4d8f7e00f13..00000000000 --- a/src/realm/sync/noinst/server/server_dir.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include - -#include - -using namespace realm; - -namespace { - -bool valid_virt_path_segment(const std::string& seg) -{ - if (seg.empty()) - return false; - // Prevent `.`, `..`, and `.foo` (hidden files) - if (seg.front() == '.') - return false; - // Prevent spurious clashes between directory names and file names - // created by appending `.realm`, `.realm.lock`, or `.realm.management` - // to the last component of client specified virtual paths. - bool possible_clash = (StringData(seg).ends_with(".realm") || StringData(seg).ends_with(".realm.lock") || - StringData(seg).ends_with(".realm.management")); - if (possible_clash) - return false; - std::locale c_loc = std::locale::classic(); - for (char ch : seg) { - if (std::isalnum(ch, c_loc)) // A-Za-z0-9 - continue; - if (ch == '_' || ch == '-' || ch == '.' || ch == '"') - continue; - return false; - } - return true; -} - -} // unnamed namespace - - -_impl::VirtualPathComponents _impl::parse_virtual_path(const std::string& root_path, const std::string& virt_path) -{ - VirtualPathComponents result; - if (virt_path.empty()) - return result; - - std::string real_path = root_path; - size_t prev_pos = 0; - if (virt_path.front() != '/') { - --prev_pos; - real_path += '/'; - } - for (;;) { - ++prev_pos; // Skip previous slash - size_t pos = virt_path.find('/', prev_pos); - bool last = (pos == std::string::npos); - if (last) - pos = virt_path.size(); - std::string segment = virt_path.substr(prev_pos, pos - prev_pos); - // Parition key style paths will be surrounded in quotes, which Windows - // doesn't allow in paths. - segment.erase(std::remove(segment.begin(), segment.end(), '"'), segment.end()); - - if (!valid_virt_path_segment(segment)) - return result; - - real_path = util::File::resolve(segment, real_path); - if (last) - break; - prev_pos = pos; - } - result.is_valid = true; - result.real_realm_path = real_path + ".realm"; - return result; -} - - -bool _impl::map_virt_to_real_realm_path(const std::string& root_path, const std::string& virt_path, - std::string& real_path) -{ - VirtualPathComponents result = parse_virtual_path(root_path, virt_path); // Throws - if (result.is_valid) { - real_path = std::move(result.real_realm_path); - return true; - } - return false; -} - - -bool _impl::map_partial_to_reference_virt_path(const std::string& partial_path, std::string& reference_path) -{ - std::string root_path = ""; // Immaterial - VirtualPathComponents result = parse_virtual_path(root_path, partial_path); // Throws - if (result.is_valid && result.is_partial_view) { - reference_path = std::move(result.reference_path); - return true; - } - return false; -} - - -void _impl::make_dirs(const std::string& root_path, const std::string& virt_path) -{ - REALM_ASSERT(!virt_path.empty()); - size_t prev_pos = 0; - std::string real_path = root_path; - if (virt_path.front() != '/') { - real_path += '/'; - --prev_pos; - } - for (;;) { - ++prev_pos; // Skip previous slash - size_t pos = virt_path.find('/', prev_pos); - if (pos == std::string::npos) - break; - std::string name = virt_path.substr(prev_pos, pos - prev_pos); - REALM_ASSERT(valid_virt_path_segment(name)); - real_path = util::File::resolve(name, real_path); - util::try_make_dir(real_path); - prev_pos = pos; - } -} diff --git a/src/realm/sync/noinst/server/server_dir.hpp b/src/realm/sync/noinst/server/server_dir.hpp deleted file mode 100644 index c233c0c09f6..00000000000 --- a/src/realm/sync/noinst/server/server_dir.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef REALM_NOINST_SERVER_DIR_HPP -#define REALM_NOINST_SERVER_DIR_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - - -namespace realm { -namespace _impl { - -struct VirtualPathComponents { - bool is_valid = false; - std::string real_realm_path; - bool is_partial_view = false; - std::string reference_path; - std::string user_identity; -}; - -// parse_virtual_path() validates and parses a virtual path. The format of a -// virtual path, also called a server path, is described in doc/protocol.md. -// -// The return value is a VirtualPathComponents struct. If the member is_valid -// is false, no other members must be used. reference_path and user_identity -// only make sense if is_partial_view is true. -// -// The argument root_path can be any valid path and is only used as a base -// directory for realm_realm_path. -VirtualPathComponents parse_virtual_path(const std::string& root_path, const std::string& virt_path); - - -// If virt_path is valid, the return value is the local Realm path corresponding to the -// virtual path. If the virtual path is invalid, the return value is util::none. -bool map_virt_to_real_realm_path(const std::string& root_path, const std::string& virt_path, std::string& real_path); - -std::string map_virt_to_real_realm_path(const std::string& root_path, const std::string& virt_path); - -bool map_partial_to_reference_virt_path(const std::string& partial_path, std::string& reference_path); - -std::string map_partial_to_reference_virt_path(const std::string& partial_path); - -void make_dirs(const std::string& root_path, const std::string& virt_path); - -/// Returns the set of virtual paths corresponding to the Realm files found -/// under the specified root directory. -std::set find_realm_files(const std::string& root_dir); - -/// Invoke the specified handler for each Realm file found under the specified -/// root directory. The handler will be invoked by an explression on the form -/// `handler(std::move(real_path), std::move(virt_path))`. Both `real_path` and -/// `virt_path` are of type `std::string`. The first argument is the real path -/// of the Realm file, which is an extension of the specified root directory -/// path. The second argument is the virtual path of the Realm file relative to -/// the specified root directory. -template -void find_realm_files(const std::string& root_dir, H handler); - - -// Implementation - -inline std::string map_virt_to_real_realm_path(const std::string& root_path, const std::string& virt_path) -{ - std::string real_path; - if (map_virt_to_real_realm_path(root_path, virt_path, real_path)) - return real_path; - throw util::runtime_error("Bad virtual path"); -} - -inline std::string map_partial_to_reference_virt_path(const std::string& partial_path) -{ - std::string reference_path; - if (map_partial_to_reference_virt_path(partial_path, reference_path)) - return reference_path; - throw util::runtime_error("Not a virtual path of a partial file"); -} - -inline std::set find_realm_files(const std::string& root_dir) -{ - std::set virt_paths; - auto handler = [&virt_paths](std::string, std::string virt_path) { - virt_paths.insert(std::move(virt_path)); // Throws - }; - find_realm_files(root_dir, handler); // Throws - return virt_paths; -} - -template -void find_realm_files(const std::string& root_dir, H handler) -{ - StringData realm_suffix = ".realm"; - util::UniqueFunction scan_dir; - scan_dir = [&](const std::string& real_path, const std::string& virt_path) { - util::DirScanner ds{real_path}; // Throws - std::string name; - while (ds.next(name)) { // Throws - std::string real_subpath = util::File::resolve(name, real_path); // Throws - if (util::File::is_dir(real_subpath)) { // Throws - if (StringData{name}.ends_with(realm_suffix)) - throw std::runtime_error("Illegal directory path: " + real_subpath); - std::string virt_subpath = virt_path + "/" + name; // Throws - scan_dir(real_subpath, virt_subpath); // Throws - } - else if (StringData{name}.ends_with(realm_suffix)) { - std::string base_name = name.substr(0, name.size() - realm_suffix.size()); // Throws - std::string virt_subpath = virt_path + "/" + base_name; // Throws - handler(std::move(real_subpath), std::move(virt_subpath)); // Throws - } - } - return true; - }; - scan_dir(root_dir, ""); // Throws -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_NOINST_SERVER_DIR_HPP diff --git a/src/realm/sync/noinst/server/server_file_access_cache.cpp b/src/realm/sync/noinst/server/server_file_access_cache.cpp deleted file mode 100644 index 0c2dd814479..00000000000 --- a/src/realm/sync/noinst/server/server_file_access_cache.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include - -using namespace realm; -using namespace _impl; - - -void ServerFileAccessCache::proper_close_all() -{ - while (m_first_open_file) - m_first_open_file->proper_close(); // Throws -} - - -void ServerFileAccessCache::access(Slot& slot) -{ - if (slot.is_open()) { - m_logger.trace(util::LogCategory::server, "Using already open Realm file: %1", slot.realm_path); // Throws - - // Move to front - REALM_ASSERT(m_first_open_file); - if (&slot != m_first_open_file) { - remove(slot); - insert(slot); // At front - m_first_open_file = &slot; - } - return; - } - - // Close least recently accessed Realm file - if (m_num_open_files == m_max_open_files) { - REALM_ASSERT(m_first_open_file); - Slot& least_recently_accessed = *m_first_open_file->m_prev_open_file; - least_recently_accessed.proper_close(); // Throws - } - - slot.open(); // Throws -} - -void ServerFileAccessCache::Slot::proper_close() -{ - if (is_open()) { - m_cache.m_logger.detail("Closing Realm file: %1", realm_path); // Throws - do_close(); - } -} - - -void ServerFileAccessCache::Slot::open() -{ - REALM_ASSERT(!is_open()); - - m_cache.m_logger.detail("Opening Realm file: %1", realm_path); // Throws - - m_file.reset(new File{*this}); // Throws - - m_cache.insert(*this); - m_cache.m_first_open_file = this; - ++m_cache.m_num_open_files; -} diff --git a/src/realm/sync/noinst/server/server_file_access_cache.hpp b/src/realm/sync/noinst/server/server_file_access_cache.hpp deleted file mode 100644 index 24af8a4adc4..00000000000 --- a/src/realm/sync/noinst/server/server_file_access_cache.hpp +++ /dev/null @@ -1,255 +0,0 @@ - -#ifndef REALM_NOINST_SERVER_FILE_ACCESS_CACHE_HPP -#define REALM_NOINST_SERVER_FILE_ACCESS_CACHE_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace realm { -namespace _impl { - -/// This class maintains a list of open Realm files ordered according to the -/// time when they were last accessed. -class ServerFileAccessCache { -public: - class Slot; - class File; - - /// \param max_open_files The maximum number of Realm files to keep open - /// concurrently. Must be greater than or equal to 1. - /// - /// The specified history context will not be accessed on behalf of this - /// cache object before the first invocation of Slot::access() on an - /// associated file file slot. - ServerFileAccessCache(long max_open_files, util::Logger&, ServerHistory::Context&, - util::Optional> encryption_key); - - ~ServerFileAccessCache() noexcept; - - void proper_close_all(); - -private: - /// Null if `m_num_open_files == 0`, otherwise it points to the most - /// recently accessed open Realm file. `m_first_open_file->m_next_open_file` - /// is the next most recently accessed open Realm - /// file. `m_first__open_file->m_prev_open_file` is the least recently - /// accessed open Realm file. - Slot* m_first_open_file = nullptr; - - /// Current number of open Realm files. - long m_num_open_files = 0; - - const long m_max_open_files; - const util::Optional> m_encryption_key; - // The ServerFileAccessCache is tied to the lifetime of the Server, so no shared_ptr needed - util::Logger& m_logger; - ServerHistory::Context& m_history_context; - - void access(Slot&); - void remove(Slot&) noexcept; - void insert(Slot&) noexcept; -}; - - -/// ServerFileAccessCache::Slot objects are associated with a particular -/// ServerFileAccessCache object, and the application must ensure that all slot -/// objects associated with a particular cache object are destroyed before the -/// cache object is destroyed. -class ServerFileAccessCache::Slot { -public: - const std::string realm_path; - const std::string virt_path; - - Slot(ServerFileAccessCache&, std::string realm_path, std::string virt_path, bool claim_sync_agent, - bool disable_sync_to_disk) noexcept; - - Slot(Slot&&) = default; - - /// Closes the file if it is open (as if by calling close()). - ~Slot() noexcept; - - /// Returns true if the associated Realm file is currently open. - bool is_open() const noexcept; - - /// Open the Realm file at `realm_path` if it is not already open. The - /// returned reference is guaranteed to remain valid until access() is - /// called again on this slot or on any other slot associated with the same - /// ServerFileAccessCache object, or until close() is called on this slot, - /// or the Slot object is destroyed, whichever comes first. - /// - /// Calling this function may cause Realm files associated with other Slot - /// objects of the same ServerFileAccessCache object to be closed. - File& access(); - - /// Same as close() but also generates a log message. This function throws - /// if logging throws. - void proper_close(); - - /// Close the Realm file now if it is open (idempotency). - void close() noexcept; - - DBOptions make_shared_group_options() const noexcept; - -private: - ServerFileAccessCache& m_cache; - const bool m_disable_sync_to_disk; - const bool m_claim_sync_agent; - - Slot* m_prev_open_file = nullptr; - Slot* m_next_open_file = nullptr; - - std::unique_ptr m_file; - - void open(); - void do_close() noexcept; - - friend class ServerFileAccessCache; -}; - - -class ServerFileAccessCache::File { -public: - ServerHistory history; - DBRef shared_group; - -private: - File(const Slot&); - - friend class Slot; -}; - - -// Implementation - -inline ServerFileAccessCache::ServerFileAccessCache(long max_open_files, util::Logger& logger, - ServerHistory::Context& history_context, - util::Optional> encryption_key) - : m_max_open_files{max_open_files} - , m_encryption_key{encryption_key} - , m_logger{logger} - , m_history_context{history_context} -{ - REALM_ASSERT(m_max_open_files >= 1); -} - -inline ServerFileAccessCache::~ServerFileAccessCache() noexcept -{ - REALM_ASSERT(!m_first_open_file); -} - -inline void ServerFileAccessCache::remove(Slot& slot) noexcept -{ - // FIXME: Consider using a generic intrusive double-linked list instead. - - REALM_ASSERT(m_first_open_file); - if (&slot == m_first_open_file) { - bool no_other_open_file = (slot.m_next_open_file == &slot); - if (no_other_open_file) { - m_first_open_file = nullptr; - } - else { - m_first_open_file = slot.m_next_open_file; - } - } - slot.m_prev_open_file->m_next_open_file = slot.m_next_open_file; - slot.m_next_open_file->m_prev_open_file = slot.m_prev_open_file; - slot.m_prev_open_file = nullptr; - slot.m_next_open_file = nullptr; -} - -inline void ServerFileAccessCache::insert(Slot& slot) noexcept -{ - REALM_ASSERT(!slot.m_next_open_file); - REALM_ASSERT(!slot.m_prev_open_file); - if (m_first_open_file) { - slot.m_prev_open_file = m_first_open_file->m_prev_open_file; - slot.m_next_open_file = m_first_open_file; - slot.m_prev_open_file->m_next_open_file = &slot; - slot.m_next_open_file->m_prev_open_file = &slot; - } - else { - slot.m_prev_open_file = &slot; - slot.m_next_open_file = &slot; - } - m_first_open_file = &slot; -} - -inline ServerFileAccessCache::Slot::Slot(ServerFileAccessCache& cache, std::string rp, std::string vp, - bool claim_sync_agent, bool dstd) noexcept - : realm_path{std::move(rp)} - , virt_path{std::move(vp)} - , m_cache{cache} - , m_disable_sync_to_disk{dstd} - , m_claim_sync_agent{claim_sync_agent} -{ -} - -inline ServerFileAccessCache::Slot::~Slot() noexcept -{ - close(); -} - -inline bool ServerFileAccessCache::Slot::is_open() const noexcept -{ - if (m_file) { - REALM_ASSERT(m_prev_open_file); - REALM_ASSERT(m_next_open_file); - return true; - } - - REALM_ASSERT(!m_prev_open_file); - REALM_ASSERT(!m_next_open_file); - return false; -} - -inline auto ServerFileAccessCache::Slot::access() -> File& -{ - m_cache.access(*this); // Throws - return *m_file; -} - -inline void ServerFileAccessCache::Slot::close() noexcept -{ - if (is_open()) - do_close(); -} - -inline DBOptions ServerFileAccessCache::Slot::make_shared_group_options() const noexcept -{ - DBOptions options; - if (m_cache.m_encryption_key) - options.encryption_key = m_cache.m_encryption_key->data(); - if (m_disable_sync_to_disk) - options.durability = DBOptions::Durability::Unsafe; - return options; -} - -inline void ServerFileAccessCache::Slot::do_close() noexcept -{ - REALM_ASSERT(is_open()); - --m_cache.m_num_open_files; - m_cache.remove(*this); - m_file.reset(); -} - -inline ServerFileAccessCache::File::File(const Slot& slot) - : history{slot.m_cache.m_history_context} // Throws - , shared_group{DB::create(history, slot.realm_path, slot.make_shared_group_options())} // Throws -{ - if (slot.m_claim_sync_agent) { - shared_group->claim_sync_agent(); - } -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_NOINST_SERVER_FILE_ACCESS_CACHE_HPP diff --git a/src/realm/sync/noinst/server/server_history.cpp b/src/realm/sync/noinst/server/server_history.cpp deleted file mode 100644 index 902e71d0bae..00000000000 --- a/src/realm/sync/noinst/server/server_history.cpp +++ /dev/null @@ -1,2304 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace realm; -using namespace realm::sync; -using namespace realm::util; -using namespace _impl; - - -/* - -Client file Entry Client Identifier Proxy Reciprocal Last seen Locked server -entry type index type Salt file history (1) timestamp (2) version ---------------------------------------------------------------------------------------------------- -Special (3) 0 0 no no YES no no -Root (4) 1 0 no no no no no -Upstream (5) 2+ 0 no no no no no -Self (6) 2+ 6 no no no no no -Indirect client 2+ 1 no YES no no no -Legacy (7) 2+ 5 YES no YES YES YES -Direct regular client 2+ 2 YES no YES YES YES -Direct subserver 2+ 4 YES no YES YES YES -Direct partial view 2+ 3 YES no YES YES YES - -1) Entries that may have a reciprocal history may also have nonzero - `client_version` and nonzero `rh_base_version`. The reciprocal history is - absent in expired entries. - -2) An entry that represents a direct client has expired if and only if this - timestamp is zero. Here, subservers, partial views, and legacy entries are - considered to be direct clients. - -3) This is a special entry used to represent the upstream server on a subtier - node of a star topology server cluster. Note that there is no valid client - file identifier associated with this entry, because a valid client file - identifier is strictly greater than zero. - -4) This entry represents the root of a star topology server cluster. This entry - is always present. For files that do not function as partial views (or have - not yet been initialized as such) and do not reside on a subtier node of a - star topology server cluster (or have not yet been initialized as such), this - entry represents the file itself. - -5) For a nonpartial file on a subtier server, this is an entry for the upstream - server or for a file reachable via the upstream server. For a partial file, - this is an entry for the reference file, or a file reachable via the - reference file. - -6) This is the entry that represents the file itself. It is present if if the - file functions as a partial view (and has been initialized as such), or if it - resides on a subtier node of a star topology server cluster (and has been - initialized as such). - -7) This is an entry created prior to history schema version 10. The exact type - of this entry is unknown. However, since the star topology feature has not - been publicly released yet, it is likely to represent either an immediate - regular client, an immediate partial view, or a client of an immediate - partial view. - - -Abstract history schema ------------------------ - - table client_files: - int ident_salt - int client_version - int rh_base_version (0 if recip_hist is null) (doubles as last server version integrated by client in -`client_version`) - // recip_hist_index = produced_server_version - recip_hist_base_version - 1 - table recip_hist: (nullable) - string changeset (nullable) - link proxy_file (references client_files) - int client_type - int last_seen (UNIX timestamp) - int locked_server_version - - // Server version on which the first entry in `sync_history` is based. This - // will be zero unless the history has been trimmed (note, history trimming is - // not yet implemented, so for now, it is always zero). - // - // `curr_server_version = history_base_version + sync_history.size()` - int history_base_version - - // Server version until which the history has been compacted. This can never - // decrease. - int compacted_until_version - - // Salt attached to `history_base_version`. This is required to be zero if - // `history_base_version` is zero. - int base_version_salt - - // History of server versions (one entry per server version). - // - // For entries whose changesets are of local origin, `origin_file` is - // zero. For other entries, `origin_file` points to the entry in - // `client_files` representing the file in which the initial untransformed - // changeset originated. - // - // For entries whose changesets are of local origin, `client_version` is - // always zero. For other entries, the a changeset was produced by integration - // of a changeset received from a remote peer (client or upstream server), and - // `client_version` is the version produced on that remote peer by the - // received changeset. - // - // `history_entry_index = produced_server_version - history_base_version - 1` - table sync_history: - int version_salt (random) (formerly known as server_session_ident) - link origin_file (references client_files) - int client_version - int timestamp - string changeset - - // Continuous transactions history (one entry per Realm version (snapshot number), trimmed) - // - // `ct_history_entry_index = realm_version - ct_history_base_version - 1` - table ct_history: - link history_entry (nullable) (references sync_history) (null for empty changesets of local origin) // FIXME: This -looks wrong! - - int history_byte_size - - // This object is only present after a successful invocation of - // ServerHistory::add_upstream_sync_status() - optional object upstream_status: - SaltedFileIdent client_file_ident - SaltedVersion progress_latest_server_version - DownloadCursor progress_download - UploadCursor progress_upload - - // This array is only present after a successful invocation of - // ServerHistory::initiate_as_partial_view() - optional array partial_sync: - // A file identifier allocated in the context of both the reference, and the - // partial Realm, and with the purpose of identifying changesets in the - // history of the reference Realm, that originate from, or via the partial - // Realm. - // - // At the same time, it is used to identify outgoing changesets in the - // history of the partial Realm that originate from, or via the reference - // Realm. - int partial_file_ident - int partial_file_ident_salt - int progress_server_version - int progress_reference_version - int progress_reference_version_salt - - - -History representation ----------------------- - - mixed_array_ref history: - 0 -> mixed_array_ref client_files: - 0 -> int_bptree_ref client_file_ident_salts: - file_ident -> ident_salt - 1 -> int_bptree_ref client_file_client_versions: - file_ident -> client_version - 2 -> int_bptree_ref client_file_rh_base_versions: - file_ident -> rh_base_version - 3 -> mixed_bptree_ref cf_recip_hist_refs: - file_ident -> nullable_binary_bptree_ref recip_hist: - recip_hist_index -> changeset - 4 -> int_bptree_ref cf_proxy_files: - file_ident -> proxy_file_ident - 5 -> int_bptree_ref cf_client_types: - file_ident -> client_type - 6 -> int_bptree_ref cf_last_seen: - file_indet -> unix_timestamp - 7 -> int_bptree_ref cf_locked_server_versions: - file_ident -> locked_server_version - 1 -> tagged_int history_base_version - 2 -> tagged_int base_version_salt - 3 -> mixed_array_ref sync_history: - 0 -> int_bptree_ref sh_version_salts: - history_entry_index -> server_version_salt - 1 -> int_bptree_ref sh_origin_files: - history_entry_index -> file_ident - 2 -> int_bptree_ref sh_client_versions: - history_entry_index -> sync_history.client_version - 3 -> int_bptree_ref sync_history_timestamps: - history_entry_index -> sync_history.timestamp - 4 -> binary_bptree_ref sh_changesets: - history_entry_index -> sync_history.changeset - 4 -> binary_bptree_ref ct_history - Core history_entry_index -> (changeset in Core's format) - 5 -> tagged_int history_byte_size - 6 -> ref to ObjectIDHistoryState - 7 -> int_array_ref upstream_status: - 0 -> int client_file_ident - 1 -> int client_file_ident_salt - 2 -> int progress_latest_server_version - 3 -> int progress_latest_server_version_salt - 4 -> int progress_download_server_version - 5 -> int progress_download_client_version - 6 -> int progress_upload_client_version - 7 -> int progress_upload_server_version - 8 -> int_array_ref partial_sync: - 0 -> int partial_file_ident - 1 -> int partial_file_ident_salt - 2 -> int progress_server_version - 3 -> int progress_reference_version - 4 -> int progress_reference_version_salt - 9 -> tagged_int compacted_until_version - 10 -> tagged_int last_compaction_at -*/ - - -namespace { - -// This is the hard-coded file identifier that represents changes of local -// origin in a file on the root node of a star topology server cluster, or a -// file on a server that is not part of a cluster. -constexpr ServerHistory::file_ident_type g_root_node_file_ident = 1; - - -} // unnamed namespace - - -void ServerHistory::get_status(VersionInfo& version_info, bool& has_upstream_sync_status, - file_ident_type& partial_file_ident, - version_type& partial_progress_reference_version) const -{ - TransactionRef rt = m_db->start_read(); // Throws - version_type realm_version = rt->get_version(); - const_cast(this)->set_group(rt.get()); - ensure_updated(realm_version); // Throws - version_info.realm_version = realm_version; - version_info.sync_version = get_salted_server_version(); - has_upstream_sync_status = (m_acc && m_acc->upstream_status.is_attached()); - bool is_initiated_as_partial_view = (m_acc && m_acc->partial_sync.is_attached()); - if (is_initiated_as_partial_view) { - partial_file_ident = file_ident_type(m_acc->partial_sync.get(s_ps_partial_file_ident_iip)); - REALM_ASSERT(partial_file_ident != 0); - partial_progress_reference_version = - version_type(m_acc->partial_sync.get(s_ps_progress_reference_version_iip)); - } - else { - partial_file_ident = 0; - partial_progress_reference_version = 0; - } -} - - -void ServerHistory::allocate_file_identifiers(FileIdentAllocSlots& slots, VersionInfo& version_info) -{ - TransactionRef tr = m_db->start_write(); // Throws - version_type realm_version = tr->get_version(); - ensure_updated(realm_version); // Throws - prepare_for_write(); // Throws - - if (REALM_UNLIKELY(m_acc->upstream_status.is_attached())) { - throw util::runtime_error("Cannot allocate new client file identifiers in a file " - "that is associated with an upstream server"); - } - - for (FileIdentAllocSlot& slot : slots) - slot.file_ident = allocate_file_ident(slot.proxy_file, slot.client_type); // Throws - - version_type new_realm_version = tr->commit(); // Throws - version_info.realm_version = new_realm_version; - version_info.sync_version = get_salted_server_version(); -} - - -bool ServerHistory::register_received_file_identifier(file_ident_type received_file_ident, - file_ident_type proxy_file_ident, ClientType client_type, - salt_type& file_ident_salt, VersionInfo& version_info) -{ - TransactionRef tr = m_db->start_write(); // Throws - version_type realm_version = tr->get_version(); - ensure_updated(realm_version); // Throws - prepare_for_write(); // Throws - - salt_type salt = 0; - bool success = try_register_file_ident(received_file_ident, proxy_file_ident, client_type, - salt); // Throws - if (REALM_UNLIKELY(!success)) - return false; - - version_type new_realm_version = tr->commit(); // Throws - file_ident_salt = salt; - version_info.realm_version = new_realm_version; - version_info.sync_version = get_salted_server_version(); - return true; -} - - -bool ServerHistory::integrate_client_changesets(const IntegratableChangesets& integratable_changesets, - VersionInfo& version_info, bool& backup_whole_realm, - IntegrationResult& result, util::Logger& logger) -{ - REALM_ASSERT(!integratable_changesets.empty()); - - // Determine the order in which to process client files. Client files with - // serialized transactions must be processed first. At most one of the - // available serialized transactions can succeed. - // - // Subordinately, the order in which to process client files is randomized - // to prevent integer ordering between client file identifiers from giving - // unfair priority to some client files. - std::vector& client_file_order = m_client_file_order_buffer; - client_file_order.clear(); - bool has_changesets = false; - for (const auto& pair : integratable_changesets) { - file_ident_type client_file_ident = pair.first; - client_file_order.push_back(client_file_ident); - const IntegratableChangesetList& list = pair.second; - if (list.has_changesets()) - has_changesets = true; - } - - result = {}; - for (;;) { - if (has_changesets) { - result.partial_clear(); - } - - bool anything_to_do = (integratable_changesets.size() > result.excluded_client_files.size()); - if (REALM_UNLIKELY(!anything_to_do)) - return false; - - file_ident_type current_client_file_ident = 0; - ExtendedIntegrationError current_error_potential = {}; - std::size_t num_changesets_to_dump = 0; - bool dump_changeset_info = false; - - try { - TransactionRef tr = m_db->start_write(); // Throws - version_type realm_version = tr->get_version_of_current_transaction().version; - ensure_updated(realm_version); // Throws - prepare_for_write(); // Throws - - bool dirty = false; - bool backup_whole_realm_2 = false; - for (file_ident_type client_file_ident : client_file_order) { - REALM_ASSERT(client_file_ident > 0); - REALM_ASSERT(client_file_ident != g_root_node_file_ident); - REALM_ASSERT(client_file_ident != m_local_file_ident); - bool excluded = - (result.excluded_client_files.find(client_file_ident) != result.excluded_client_files.end()); - if (REALM_UNLIKELY(excluded)) - continue; - current_client_file_ident = client_file_ident; - // Verify that the client file entry has not expired - current_error_potential = ExtendedIntegrationError::client_file_expired; - std::size_t client_file_index = std::size_t(client_file_ident); - std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(client_file_index); - bool expired = (last_seen_timestamp == 0); - if (REALM_UNLIKELY(expired)) - goto error; - const IntegratableChangesetList& list = integratable_changesets.find(client_file_ident)->second; - std::vector changesets; - current_error_potential = ExtendedIntegrationError::bad_origin_file_ident; - for (const IntegratableChangeset& ic : list.changesets) { - REALM_ASSERT(ic.client_file_ident == client_file_ident); - // Verify that the origin file identifier either is the - // client's file identifier, or a file identifier of a - // subordinate client for which the sending client acts as a - // proxy. - file_ident_type origin_file_ident = ic.origin_file_ident; - if (origin_file_ident != 0) { - static_assert(g_root_node_file_ident == 1, ""); - if (REALM_UNLIKELY(origin_file_ident <= g_root_node_file_ident)) - goto error; - if (REALM_UNLIKELY(std::uint_fast64_t(origin_file_ident) >= m_num_client_files)) - goto error; - std::size_t index = std::size_t(origin_file_ident); - file_ident_type proxy_file_ident = file_ident_type(m_acc->cf_proxy_files.get(index)); - if (REALM_UNLIKELY(proxy_file_ident != ic.client_file_ident)) - goto error; - } - RemoteChangeset changeset{ic}; - changesets.push_back(changeset); // Throws - result.integrated_changesets.push_back(&ic); // Throws - } - - auto integrate = [&](std::size_t begin, std::size_t end, UploadCursor upload_progress) { - const RemoteChangeset* changesets_2 = changesets.data() + begin; - std::size_t num_changesets = end - begin; - num_changesets_to_dump += num_changesets; - bool dirty_2 = integrate_remote_changesets( - client_file_ident, upload_progress, list.locked_server_version, changesets_2, num_changesets, - logger); // Throws - if (dirty_2) { - dirty = true; - bool backup_whole_realm_3 = - (begin == end || upload_progress.client_version != changesets[end - 1].remote_version || - list.locked_server_version != upload_progress.last_integrated_server_version); - if (backup_whole_realm_3) - backup_whole_realm_2 = true; - } - }; - - // Note: This value will be read if an exception is thrown - // below. Clang's static analyzer incorrectly reports it as - // a dead store. - current_error_potential = ExtendedIntegrationError::bad_changeset; - static_cast(current_error_potential); - - std::size_t num_changesets = changesets.size(); - std::size_t aug_num_changesets = num_changesets; - logger.debug("Integrating %1 changesets from client file %2", aug_num_changesets, client_file_ident); - - integrate(0, num_changesets, list.upload_progress); // Throws - } - - if (dirty) { - auto ta = util::make_temp_assign(m_is_local_changeset, false, true); - version_info.realm_version = tr->commit(); // Throws - version_info.sync_version = get_salted_server_version(); - if (backup_whole_realm_2) - backup_whole_realm = true; - return true; - } - return false; - } - catch (BadChangesetError& e) { - logger.error("Failed to parse, or apply changeset received from client: %1", - e.what()); // Throws - dump_changeset_info = true; - } - catch (TransformError& e) { - logger.error("Failed to transform changeset received from client: %1", - e.what()); // Throws - dump_changeset_info = true; - } - - if (dump_changeset_info) { - std::size_t changeset_ndx = 0; - std::size_t num_parts = num_changesets_to_dump; - for (std::size_t i = 0; i < num_parts; ++i) { - // Regular changeset - const IntegratableChangeset& ic = *result.integrated_changesets[changeset_ndx]; - std::string hex_dump = util::hex_dump(ic.changeset.data(), - ic.changeset.size()); // Throws - logger.error("Failed transaction (part %1/%2): Changeset " - "(client_file_ident=%3, origin_timestamp=%4, " - "origin_file_ident=%5, client_version=%6, " - "last_integrated_server_version=%7): %8", - (i + 1), num_parts, ic.client_file_ident, ic.origin_timestamp, ic.origin_file_ident, - ic.upload_cursor.client_version, ic.upload_cursor.last_integrated_server_version, - hex_dump); // Throws - ++changeset_ndx; - continue; - } - } - - error: - REALM_ASSERT(current_client_file_ident != 0); - result.excluded_client_files[current_client_file_ident] = current_error_potential; // Throws - } -} - - -auto ServerHistory::integrate_backup_idents_and_changeset( - version_type expected_realm_version, salt_type server_version_salt, - const FileIdentAllocSlots& file_ident_alloc_slots, - const std::vector& integratable_changesets, util::Logger& logger) -> IntegratedBackup -{ - IntegratedBackup result; - result.success = false; - - try { - TransactionRef tr = m_db->start_write(); // Throws - version_type realm_version = tr->get_version_of_current_transaction().version; - ensure_updated(realm_version); // Throws - prepare_for_write(); // Throws - - result.version_info.realm_version = realm_version; - - if (realm_version + 1 != expected_realm_version) - return result; - - // To ensure identity of a server Realm and its backup, it is necessary - // to set the server_version_salt of the backup Realm to the same value - // as that of the original Realm. - m_salt_for_new_server_versions = server_version_salt; - - for (const auto& slot : file_ident_alloc_slots) { - if (std::uint_fast64_t(slot.file_ident.ident) != m_num_client_files) - return result; - add_client_file(slot.file_ident.salt, slot.proxy_file, slot.client_type); // Throws - } - - std::map> changesets; - - for (const IntegratableChangeset& ic : integratable_changesets) - changesets[ic.client_file_ident].push_back(ic); - - for (auto& pair : changesets) { - file_ident_type client_file_ident = pair.first; - // FIXME: Backup should also get the proper upload progress and - // locked server version. This requires extending the backup - // protocol. - const auto& back = pair.second.back(); - UploadCursor upload_progress = {back.remote_version, back.last_integrated_local_version}; - version_type locked_server_version = upload_progress.last_integrated_server_version; - integrate_remote_changesets(client_file_ident, upload_progress, locked_server_version, pair.second.data(), - pair.second.size(), logger); // Throws - } - - auto ta = util::make_temp_assign(m_is_local_changeset, false, true); - result.version_info.realm_version = tr->commit(); // Throws - result.version_info.sync_version = get_salted_server_version(); - result.success = true; - } - catch (BadChangesetError& e) { - logger.error("Bad incremental backup", e.what()); // Throws - } - catch (TransformError& e) { - logger.error("Bad incremental backup", e.what()); // Throws - } - - return result; -} - - -auto ServerHistory::allocate_file_ident(file_ident_type proxy_file_ident, ClientType client_type) -> SaltedFileIdent -{ - REALM_ASSERT(!m_acc->upstream_status.is_attached()); - - std::size_t file_index = m_num_client_files; - salt_type salt = register_client_file_by_index(file_index, proxy_file_ident, client_type); // Throws - - if (uint64_t(file_index) > get_max_file_ident()) - throw util::overflow_error{"File identifier"}; - - file_ident_type ident = file_ident_type(file_index); - return {ident, salt}; -} - - -void ServerHistory::register_assigned_file_ident(file_ident_type file_ident) -{ - file_ident_type proxy_file_ident = 0; // No proxy - ClientType client_type = ClientType::self; - salt_type file_ident_salt; // Dummy - bool success = try_register_file_ident(file_ident, proxy_file_ident, client_type, - file_ident_salt); // Throws - REALM_ASSERT(success); -} - - -bool ServerHistory::try_register_file_ident(file_ident_type file_ident, file_ident_type proxy_file_ident, - ClientType client_type, salt_type& file_ident_salt) -{ - REALM_ASSERT(m_acc->upstream_status.is_attached()); - static_assert(g_root_node_file_ident == 1, ""); - if (REALM_UNLIKELY(file_ident < 2)) - return false; - std::size_t max = std::numeric_limits::max(); - if (REALM_UNLIKELY(std::uint_fast64_t(file_ident) > max)) - throw util::overflow_error{"Client file index"}; - std::size_t file_index = std::size_t(file_ident); - if (file_index < m_num_client_files) - return false; - file_ident_salt = register_client_file_by_index(file_index, proxy_file_ident, client_type); // Throws - return true; -} - - -auto ServerHistory::register_client_file_by_index(std::size_t file_index, file_ident_type proxy_file_ident, - ClientType client_type) -> salt_type -{ - REALM_ASSERT(file_index >= m_num_client_files); - REALM_ASSERT(proxy_file_ident == 0 || is_valid_proxy_file_ident(proxy_file_ident)); - bool generate_salt = is_direct_client(client_type); - salt_type salt = 0; - if (generate_salt) { - auto max_salt = 0x0'7FFF'FFFF'FFFF'FFFF; - std::mt19937_64& random = m_context.server_history_get_random(); - salt = std::uniform_int_distribution(1, max_salt)(random); - } - while (file_index > m_num_client_files) - add_client_file(0, 0, ClientType::upstream); // Throws - add_client_file(salt, proxy_file_ident, client_type); // Throws - return salt; -} - - -bool ServerHistory::ensure_upstream_file_ident(file_ident_type file_ident) -{ - REALM_ASSERT(m_acc->upstream_status.is_attached()); - - static_assert(g_root_node_file_ident == 1, ""); - if (REALM_UNLIKELY(file_ident < 2)) - return (file_ident == 1); - std::size_t max = std::numeric_limits::max(); - if (REALM_UNLIKELY(std::uint_fast64_t(file_ident) > max)) - throw util::overflow_error{"Client file index"}; - std::size_t file_index = std::size_t(file_ident); - if (REALM_LIKELY(file_index < m_num_client_files)) { - auto client_type = m_acc->cf_client_types.get(file_index); - if (REALM_UNLIKELY(client_type != int(ClientType::upstream))) - return false; - REALM_ASSERT(m_acc->cf_ident_salts.get(file_index) == 0); - REALM_ASSERT(m_acc->cf_proxy_files.get(file_index) == 0); - return true; - } - do - add_client_file(0, 0, ClientType::upstream); // Throws - while (file_index >= m_num_client_files); - return true; -} - - -void ServerHistory::add_client_file(salt_type file_ident_salt, file_ident_type proxy_file_ident, - ClientType client_type) -{ - switch (client_type) { - case ClientType::upstream: - case ClientType::self: - REALM_ASSERT(file_ident_salt == 0); - REALM_ASSERT(proxy_file_ident == 0); - break; - case ClientType::indirect: - REALM_ASSERT(file_ident_salt == 0); - REALM_ASSERT(proxy_file_ident != 0); - break; - case ClientType::regular: - case ClientType::subserver: - REALM_ASSERT(file_ident_salt != 0); - REALM_ASSERT(proxy_file_ident == 0); - break; - case ClientType::legacy: - REALM_ASSERT(false); - break; - } - std::int_fast64_t client_version = 0; - std::int_fast64_t recip_hist_base_version = 0; - ref_type recip_hist_ref = 0; - std::int_fast64_t last_seen_timestamp = 0; - std::int_fast64_t locked_server_version = 0; - if (is_direct_client(client_type)) { - last_seen_timestamp = 1; - } - m_acc->cf_ident_salts.insert(realm::npos, std::int_fast64_t(file_ident_salt)); // Throws - m_acc->cf_client_versions.insert(realm::npos, client_version); // Throws - m_acc->cf_rh_base_versions.insert(realm::npos, recip_hist_base_version); // Throws - m_acc->cf_recip_hist_refs.insert(realm::npos, recip_hist_ref); // Throws - m_acc->cf_proxy_files.insert(realm::npos, std::int_fast64_t(proxy_file_ident)); // Throws - m_acc->cf_client_types.insert(realm::npos, std::int_fast64_t(client_type)); // Throws - m_acc->cf_last_seen_timestamps.insert(realm::npos, last_seen_timestamp); // Throws - m_acc->cf_locked_server_versions.insert(realm::npos, locked_server_version); // Throws - std::size_t max_size = std::numeric_limits::max(); - if (m_num_client_files == max_size) - throw util::overflow_error{"Client file index"}; - ++m_num_client_files; -} - - -void ServerHistory::save_upstream_sync_progress(const SyncProgress& progress) -{ - Array& us = m_acc->upstream_status; - us.set(s_us_progress_download_server_version_iip, - std::int_fast64_t(progress.download.server_version)); // Throws - us.set(s_us_progress_download_client_version_iip, - std::int_fast64_t(progress.download.last_integrated_client_version)); // Throws - us.set(s_us_progress_latest_server_version_iip, - std::int_fast64_t(progress.latest_server_version.version)); // Throws - us.set(s_us_progress_latest_server_version_salt_iip, - std::int_fast64_t(progress.latest_server_version.salt)); // Throws - us.set(s_us_progress_upload_client_version_iip, - std::int_fast64_t(progress.upload.client_version)); // Throws - us.set(s_us_progress_upload_server_version_iip, - std::int_fast64_t(progress.upload.last_integrated_server_version)); // Throws -} - - -auto ServerHistory::do_bootstrap_client_session(SaltedFileIdent client_file_ident, DownloadCursor download_progress, - SaltedVersion server_version, ClientType client_type, - UploadCursor& upload_progress, version_type& locked_server_version, - Logger& logger) const noexcept -> BootstrapError -{ - REALM_ASSERT(is_direct_client(client_type)); - REALM_ASSERT(client_type != ClientType::legacy); - - // Validate `client_file_ident` - if (!m_acc) - return BootstrapError::bad_client_file_ident; - { - bool good = - (client_file_ident.ident >= 1 && util::int_less_than(client_file_ident.ident, m_num_client_files)); - if (!good) - return BootstrapError::bad_client_file_ident; - } - std::size_t client_file_index = std::size_t(client_file_ident.ident); - { - auto correct_salt = salt_type(m_acc->cf_ident_salts.get(client_file_index)); - bool good = (correct_salt != 0 && // Prevent (spoofed) match on special entries with no salt - client_file_ident.salt == correct_salt); - if (!good) - return BootstrapError::bad_client_file_ident_salt; - } - - // Besides being superfluous, it is also a protocol violation if a client - // asks to download from a point before the base of its reciprocal history. - auto recip_hist_base_version = version_type(m_acc->cf_rh_base_versions.get(client_file_index)); - if (download_progress.server_version < recip_hist_base_version) { - logger.debug("Bad download progress: %1 < %2", download_progress.server_version, recip_hist_base_version); - return BootstrapError::bad_download_server_version; - } - - // If the main history has been trimmed or compacted to a point beyond the - // beginning of the reciprocal history, then the client file entry has - // expired. - // - // NOTE: History trimming (removal of leading history entries) is currently - // never done on server-side files. - // - // NOTE: For an overview of the in-place history compaction mechanism, see - // `/doc/history_compaction.md` in the `realm-sync` Git repository. - std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(client_file_index); - bool expired_due_to_compaction = (last_seen_timestamp == 0); - if (REALM_UNLIKELY(expired_due_to_compaction)) { - logger.debug("Client expired because history has been compacted"); - return BootstrapError::client_file_expired; - } - - REALM_ASSERT_RELEASE(recip_hist_base_version >= m_history_base_version); - - // Validate `download_progress` - version_type current_server_version = get_server_version(); - if (download_progress.server_version > current_server_version) - return BootstrapError::bad_download_server_version; - auto last_integrated_client_version = version_type(m_acc->cf_client_versions.get(client_file_index)); - if (download_progress.last_integrated_client_version > last_integrated_client_version) - return BootstrapError::bad_download_client_version; - - // Validate `server_version` - { - bool good = (server_version.version >= download_progress.server_version && - server_version.version <= current_server_version); - if (!good) - return BootstrapError::bad_server_version; - } - { - salt_type correct_salt = get_server_version_salt(server_version.version); - bool good = (server_version.salt == correct_salt); - if (!good) - return BootstrapError::bad_server_version_salt; - } - - // Validate client type - { - auto client_type_2 = ClientType(m_acc->cf_client_types.get(client_file_index)); - bool good = (client_type_2 == ClientType::legacy || client_type == client_type_2); - if (!good) - return BootstrapError::bad_client_type; - } - - upload_progress.client_version = last_integrated_client_version; - upload_progress.last_integrated_server_version = recip_hist_base_version; - locked_server_version = version_type(m_acc->cf_locked_server_versions.get(client_file_index)); - return BootstrapError::no_error; -} - - -auto ServerHistory::bootstrap_client_session(SaltedFileIdent client_file_ident, DownloadCursor download_progress, - SaltedVersion server_version, ClientType client_type, - UploadCursor& upload_progress, version_type& locked_server_version, - Logger& logger) const -> BootstrapError -{ - TransactionRef tr = m_db->start_read(); // Throws - auto realm_version = tr->get_version(); - const_cast(this)->set_group(tr.get()); - ensure_updated(realm_version); // Throws - - BootstrapError error = do_bootstrap_client_session(client_file_ident, download_progress, server_version, - client_type, upload_progress, locked_server_version, logger); - return error; -} - - -bool ServerHistory::fetch_download_info(file_ident_type client_file_ident, DownloadCursor& download_progress, - version_type end_version, UploadCursor& upload_progress, - HistoryEntryHandler& handler, - std::uint_fast64_t& cumulative_byte_size_current, - std::uint_fast64_t& cumulative_byte_size_total, - std::size_t accum_byte_size_soft_limit) const -{ - REALM_ASSERT(client_file_ident != 0); - REALM_ASSERT(download_progress.server_version <= end_version); - - TransactionRef tr = m_db->start_read(); // Throws - version_type realm_version = tr->get_version(); - const_cast(this)->set_group(tr.get()); - ensure_updated(realm_version); // Throws - - REALM_ASSERT(download_progress.server_version >= m_history_base_version); - - std::size_t client_file_index = std::size_t(client_file_ident); - { - auto client_type = ClientType(m_acc->cf_client_types.get(client_file_index)); - REALM_ASSERT_RELEASE(is_direct_client(client_type)); - std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(client_file_index); - bool expired = (last_seen_timestamp == 0); - if (REALM_UNLIKELY(expired)) - return false; - } - - std::size_t accum_byte_size = 0; - DownloadCursor download_progress_2 = download_progress; - - std::vector changesets; - std::vector original_changeset_sizes; - - for (;;) { - version_type begin_version = download_progress_2.server_version; - HistoryEntry entry; - version_type version = find_history_entry(client_file_ident, begin_version, end_version, entry, - download_progress_2.last_integrated_client_version); - if (version == 0) { - // End of history reached - download_progress_2.server_version = end_version; - break; - } - - download_progress_2.server_version = version; - - entry.remote_version = download_progress_2.last_integrated_client_version; - - if (entry.origin_file_ident == 0) - entry.origin_file_ident = m_local_file_ident; - - handler.handle(download_progress_2.server_version, entry, entry.changeset.size()); // Throws - - accum_byte_size += entry.changeset.size(); - - if (accum_byte_size > accum_byte_size_soft_limit) - break; - } - - // Set cumulative byte sizes. - std::int_fast64_t cumulative_byte_size_current_2 = 0; - std::int_fast64_t cumulative_byte_size_total_2 = 0; - if (download_progress_2.server_version > m_history_base_version) { - std::size_t begin_ndx = to_size_t(download_progress_2.server_version - m_history_base_version) - 1; - cumulative_byte_size_current_2 = m_acc->sh_cumul_byte_sizes.get(begin_ndx); - REALM_ASSERT(cumulative_byte_size_current_2 >= 0); - } - if (m_history_size > 0) { - std::size_t end_ndx = m_history_size - 1; - cumulative_byte_size_total_2 = m_acc->sh_cumul_byte_sizes.get(end_ndx); - } - REALM_ASSERT(cumulative_byte_size_current_2 <= cumulative_byte_size_total_2); - - version_type upload_client_version = version_type(m_acc->cf_client_versions.get(client_file_index)); - version_type upload_server_version = version_type(m_acc->cf_rh_base_versions.get(client_file_index)); - - download_progress = download_progress_2; - cumulative_byte_size_current = std::uint_fast64_t(cumulative_byte_size_current_2); - cumulative_byte_size_total = std::uint_fast64_t(cumulative_byte_size_total_2); - upload_progress = UploadCursor{upload_client_version, upload_server_version}; - - return true; -} - - -void ServerHistory::add_upstream_sync_status() -{ - TransactionRef tr = m_db->start_write(); // Throws - version_type realm_version = tr->get_version(); - ensure_updated(realm_version); // Throws - prepare_for_write(); // Throws - - REALM_ASSERT(!m_acc->upstream_status.is_attached()); - REALM_ASSERT(m_local_file_ident == g_root_node_file_ident); - - // An upstream status cannot be added to a file from which new client file - // identifiers have already been allocated, since in a star topology server - // cluster, all file identifiers must be allocated by the root node. - static_assert(g_root_node_file_ident == 1, ""); - if (REALM_UNLIKELY(m_num_client_files > 2)) { - throw util::runtime_error("Realm file has registered client file identifiers, " - "so can no longer be associated with upstream server " - "(star topology server cluster)"); - } - - bool context_flag_no = false; - std::size_t size = s_upstream_status_size; - m_acc->upstream_status.create(Array::type_Normal, context_flag_no, size); // Throws - _impl::ShallowArrayDestroyGuard adg{&m_acc->upstream_status}; - m_acc->upstream_status.update_parent(); // Throws - adg.release(); // Ref ownership transferred to parent array - tr->commit(); // Throws -} - -std::vector ServerHistory::get_parsed_changesets(version_type begin, version_type end) const -{ - TransactionRef rt = m_db->start_read(); // Throws - version_type realm_version = rt->get_version(); - const_cast(this)->set_group(rt.get()); - ensure_updated(realm_version); - - REALM_ASSERT(begin > m_history_base_version); - if (end == version_type(-1)) { - end = m_history_base_version + m_history_size + 1; - } - REALM_ASSERT(begin <= end); - - std::vector changesets; - changesets.reserve(std::size_t(end - begin)); // Throws - for (version_type version = begin; version < end; ++version) { - std::size_t ndx = std::size_t(version - m_history_base_version - 1); - Changeset changeset; - - auto binary = ChunkedBinaryData{m_acc->sh_changesets, ndx}; - ChunkedBinaryInputStream stream{binary}; - parse_changeset(stream, changeset); // Throws - - // Add the attributes for the changeset. - changeset.last_integrated_remote_version = m_acc->sh_client_versions.get(ndx); - changeset.origin_file_ident = m_acc->sh_origin_files.get(ndx); - changeset.origin_timestamp = m_acc->sh_timestamps.get(ndx); - changeset.version = version; - changesets.emplace_back(std::move(changeset)); - } - return changesets; -} - - -class ServerHistory::ReciprocalHistory : private ArrayParent { -public: - ReciprocalHistory(BPlusTree& cf_recip_hist_refs, std::size_t remote_file_index, - version_type base_version) - : m_cf_recip_hist_refs{cf_recip_hist_refs} - , m_remote_file_index{remote_file_index} - , m_base_version{base_version} - { - if (ref_type ref = to_ref(cf_recip_hist_refs.get(remote_file_index))) { - init(ref); // Throws - m_size = m_changesets->size(); // Relatively expensive - } - } - - std::size_t remote_file_index() const noexcept - { - return m_remote_file_index; - } - - version_type base_version() const noexcept - { - return m_base_version; - } - - std::size_t size() const noexcept - { - return m_size; - } - - // Returns true iff the reciprocal history has been instantiated - explicit operator bool() const noexcept - { - return bool(m_changesets); - } - - void ensure_instantiated() - { - if (m_changesets) - return; - - // Instantiate the reciprocal history - Allocator& alloc = m_cf_recip_hist_refs.get_alloc(); - BinaryColumn recip_hist(alloc); - recip_hist.create(); - auto ref = recip_hist.get_ref(); - DeepArrayRefDestroyGuard adg{ref, alloc}; - m_cf_recip_hist_refs.set(m_remote_file_index, ref); // Throws - adg.release(); // Ref ownership transferred to parent array - init(ref); // Throws - } - - // The reciprocal history must have been instantiated (see - // ensure_instantiated()). - bool get(version_type server_version, ChunkedBinaryData& transform) const noexcept - { - REALM_ASSERT(m_changesets); - REALM_ASSERT(server_version > m_base_version); - - std::size_t i = std::size_t(server_version - m_base_version - 1); - if (i < m_size) { - ChunkedBinaryData transform_2{*m_changesets, i}; - if (!transform_2.is_null()) { - transform = transform_2; - return true; - } - } - return false; - } - - // The reciprocal history must have been instantiated (see - // ensure_instantiated()). - void set(version_type server_version, BinaryData transform) - { - REALM_ASSERT(m_changesets); - REALM_ASSERT(server_version > m_base_version); - std::size_t i = std::size_t(server_version - m_base_version - 1); - while (m_size <= i) { - m_changesets->add({}); // Throws - m_size++; - } - // FIXME: BinaryColumn::set() currently interprets BinaryData(0,0) as - // null. It should probably be changed such that BinaryData(0,0) is - // always interpreted as the empty string. For the purpose of setting - // null values, BinaryColumn::set() should accept values of type - // util::Optional(). - BinaryData transform_2 = (transform.is_null() ? BinaryData{"", 0} : transform); - m_changesets->set(i, transform_2); // Throws - } - - // Requires that new_base_version > base_version() - void trim(version_type new_base_version) - { - REALM_ASSERT(new_base_version > m_base_version); - std::size_t n = std::size_t(new_base_version - m_base_version); - if (n >= m_size) { - if (m_changesets) - m_changesets->clear(); // Throws - m_base_version = new_base_version; - m_size = 0; - return; - } - REALM_ASSERT(m_changesets); - while (n) { - m_changesets->erase(0); - --n; - } - m_base_version = new_base_version; - m_size -= n; - } - - void update_child_ref(std::size_t child_ndx, ref_type new_ref) override final - { - m_cf_recip_hist_refs.set(child_ndx, new_ref); // Throws - } - - ref_type get_child_ref(std::size_t child_ndx) const noexcept override final - { - return m_cf_recip_hist_refs.get(child_ndx); - } - -private: - BPlusTree& m_cf_recip_hist_refs; - const std::size_t m_remote_file_index; - version_type m_base_version; - std::size_t m_size = 0; - util::Optional m_changesets; - - void init(ref_type ref) - { - Allocator& alloc = m_cf_recip_hist_refs.get_alloc(); - m_changesets.emplace(alloc); // Throws - m_changesets->init_from_ref(ref); - m_changesets->set_parent(this, m_remote_file_index); - } -}; - - -class ServerHistory::TransformHistoryImpl : public TransformHistory { -public: - TransformHistoryImpl(file_ident_type remote_file_ident, ServerHistory& history, - ReciprocalHistory& recip_hist) noexcept - : m_remote_file_ident{remote_file_ident} - , m_history{history} - , m_recip_hist{recip_hist} - { - } - - version_type find_history_entry(version_type begin_version, version_type end_version, - HistoryEntry& entry) const noexcept override final - { - return m_history.find_history_entry(m_remote_file_ident, begin_version, end_version, entry); - } - - ChunkedBinaryData get_reciprocal_transform(version_type server_version, - bool& is_compressed) const noexcept override final - { - is_compressed = false; - ChunkedBinaryData transform; - if (m_recip_hist.get(server_version, transform)) - return transform; - HistoryEntry entry = m_history.get_history_entry(server_version); - return entry.changeset; - } - - void set_reciprocal_transform(version_type server_version, BinaryData transform) override final - { - m_recip_hist.set(server_version, transform); // Throws - } - -private: - const file_ident_type m_remote_file_ident; // Zero for server - ServerHistory& m_history; - ReciprocalHistory& m_recip_hist; -}; - - -bool ServerHistory::integrate_remote_changesets(file_ident_type remote_file_ident, UploadCursor upload_progress, - version_type locked_server_version, const RemoteChangeset* changesets, - std::size_t num_changesets, util::Logger& logger) -{ - std::size_t remote_file_index = std::size_t(remote_file_ident); - REALM_ASSERT(remote_file_index < m_num_client_files); - bool from_downstream = (remote_file_ident != 0); - if (from_downstream) { - auto client_type = ClientType(m_acc->cf_client_types.get(remote_file_index)); - REALM_ASSERT_RELEASE(is_direct_client(client_type)); - std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(remote_file_index); - bool expired = (last_seen_timestamp == 0); - REALM_ASSERT_RELEASE(!expired); - } - version_type orig_client_version = version_type(m_acc->cf_client_versions.get(remote_file_index)); - version_type recip_hist_base_version = version_type(m_acc->cf_rh_base_versions.get(remote_file_index)); - ReciprocalHistory recip_hist(m_acc->cf_recip_hist_refs, remote_file_index, - recip_hist_base_version); // Throws - - { - UploadCursor prev_upload_cursor = {orig_client_version, recip_hist_base_version}; - for (std::size_t i = 0; i < num_changesets; ++i) { - // Note: remote_file_ident may be different from - // changeset.origin_file_ident in a cluster setup. - REALM_ASSERT(changesets[i].origin_file_ident > 0); - UploadCursor upload_cursor = {changesets[i].remote_version, changesets[i].last_integrated_local_version}; - REALM_ASSERT(upload_cursor.client_version > prev_upload_cursor.client_version); - REALM_ASSERT(are_mutually_consistent(upload_cursor, prev_upload_cursor)); - prev_upload_cursor = upload_cursor; - } - } - - if (num_changesets > 0) { - recip_hist.ensure_instantiated(); // Throws - - // Parse the changesets - std::vector parsed_transformed_changesets; - parsed_transformed_changesets.resize(num_changesets); - for (std::size_t i = 0; i < num_changesets; ++i) - parse_remote_changeset(changesets[i], parsed_transformed_changesets[i]); // Throws - - // Transform the changesets - version_type current_server_version = get_server_version(); - Group& group = *m_group; - Transaction& transaction = dynamic_cast(group); - auto apply = [&](const Changeset* c) -> bool { - TempShortCircuitReplication tdr{*this}; // Short-circuit while integrating changes - InstructionApplier applier{transaction}; - applier.apply(*c); - reset(); // Reset the instruction encoder - return true; - }; - // Merge with causally unrelated changesets, and resolve the - // conflicts if there are any. - TransformHistoryImpl transform_hist{remote_file_ident, *this, recip_hist}; - Transformer transformer; - transformer.transform_remote_changesets(transform_hist, m_local_file_ident, current_server_version, - parsed_transformed_changesets, apply, logger); // Throws - - for (std::size_t i = 0; i < num_changesets; ++i) { - REALM_ASSERT(get_instruction_encoder().buffer().size() == 0); - const Changeset& changeset = parsed_transformed_changesets[i]; - - HistoryEntry entry; - entry.origin_timestamp = changeset.origin_timestamp; - entry.origin_file_ident = changeset.origin_file_ident; - entry.remote_version = changeset.version; - - ChangesetEncoder::Buffer changeset_buffer; - - encode_changeset(parsed_transformed_changesets[i], changeset_buffer); // Throws - entry.changeset = BinaryData{changeset_buffer.data(), changeset_buffer.size()}; - - add_sync_history_entry(entry); // Throws - } - } - - bool dirty = (num_changesets > 0); - - if (update_upload_progress(orig_client_version, recip_hist, upload_progress)) // Throws - dirty = true; - - if (from_downstream) { - version_type orig_version = version_type(m_acc->cf_locked_server_versions.get(remote_file_index)); - if (locked_server_version > orig_version) { - m_acc->cf_locked_server_versions.set(remote_file_index, - std::int_fast64_t(locked_server_version)); // Throws - dirty = true; - } - } - - if (from_downstream && dirty) { - m_acc->cf_last_seen_timestamps.set(remote_file_index, 1); - } - - return dirty; -} - - -bool ServerHistory::update_upload_progress(version_type orig_client_version, ReciprocalHistory& recip_hist, - UploadCursor upload_progress) -{ - UploadCursor orig_upload_progress = {orig_client_version, recip_hist.base_version()}; - REALM_ASSERT(upload_progress.client_version >= orig_upload_progress.client_version); - REALM_ASSERT(are_mutually_consistent(upload_progress, orig_upload_progress)); - std::size_t client_file_index = recip_hist.remote_file_index(); - bool update_client_version = (upload_progress.client_version > orig_upload_progress.client_version); - if (update_client_version) { - auto value_1 = std::int_fast64_t(upload_progress.client_version); - m_acc->cf_client_versions.set(client_file_index, value_1); // Throws - bool update_server_version = - (upload_progress.last_integrated_server_version > orig_upload_progress.last_integrated_server_version); - if (update_server_version) { - recip_hist.trim(upload_progress.last_integrated_server_version); // Throws - auto value_2 = std::int_fast64_t(upload_progress.last_integrated_server_version); - m_acc->cf_rh_base_versions.set(client_file_index, value_2); // Throws - } - return true; - } - return false; -} - - -// Overriding member in Replication -void ServerHistory::initialize(DB& sg) -{ - REALM_ASSERT(!m_db); - SyncReplication::initialize(sg); // Throws - m_db = &sg; -} - - -// Overriding member in Replication -auto ServerHistory::get_history_type() const noexcept -> HistoryType -{ - return hist_SyncServer; -} - - -// Overriding member in Replication -int ServerHistory::get_history_schema_version() const noexcept -{ - return get_server_history_schema_version(); -} - - -// Overriding member in Replication -bool ServerHistory::is_upgradable_history_schema(int stored_schema_version) const noexcept -{ - if (stored_schema_version >= 20) { - return true; - } - return false; -} - - -// Overriding member in Replication -void ServerHistory::upgrade_history_schema(int stored_schema_version) -{ - // upgrade_history_schema() is called only when there is a need to upgrade - // (`stored_schema_version < get_server_history_schema_version()`), and only - // when is_upgradable_history_schema() returned true (`stored_schema_version - // >= 1`). - REALM_ASSERT(stored_schema_version < get_server_history_schema_version()); - REALM_ASSERT(stored_schema_version >= 1); - int orig_schema_version = stored_schema_version; - int schema_version = orig_schema_version; - // NOTE: Future migration steps go here. - - REALM_ASSERT(schema_version == get_server_history_schema_version()); - - // Record migration event - record_current_schema_version(); // Throws -} - - -// Overriding member in Replication -_impl::History* ServerHistory::_get_history_write() -{ - return this; -} - -// Overriding member in Replication -std::unique_ptr<_impl::History> ServerHistory::_create_history_read() -{ - auto server_hist = std::make_unique(m_context); // Throws - server_hist->initialize(*m_db); // Throws - return std::unique_ptr<_impl::History>(server_hist.release()); -} - - -// Overriding member in Replication -auto ServerHistory::prepare_changeset(const char* data, std::size_t size, version_type realm_version) -> version_type -{ - ensure_updated(realm_version); - prepare_for_write(); // Throws - - bool nonempty_changeset_of_local_origin = (m_is_local_changeset && size != 0); - - if (nonempty_changeset_of_local_origin) { - auto& buffer = get_instruction_encoder().buffer(); - BinaryData changeset{buffer.data(), buffer.size()}; - HistoryEntry entry; - entry.origin_timestamp = sync::generate_changeset_timestamp(); - entry.origin_file_ident = 0; // Of local origin - entry.remote_version = 0; // Of local origin on server-side - entry.changeset = changeset; - - add_sync_history_entry(entry); // Throws - } - - // Add the standard ct changeset. - // This is done for changes of both local and remote origin. - BinaryData core_changeset{data, size}; - add_core_history_entry(core_changeset); // Thows - - return m_ct_base_version + m_ct_history_size; // New snapshot number -} - - -// Overriding member in _impl::History -void ServerHistory::update_from_parent(version_type realm_version) -{ - using gf = _impl::GroupFriend; - ref_type ref = gf::get_history_ref(*m_group); - update_from_ref_and_version(ref, realm_version); // Throws -} - - -// Overriding member in _impl::History -void ServerHistory::get_changesets(version_type begin_version, version_type end_version, - BinaryIterator* iterators) const noexcept -{ - REALM_ASSERT(begin_version <= end_version); - REALM_ASSERT(begin_version >= m_ct_base_version); - REALM_ASSERT(end_version <= m_ct_base_version + m_ct_history_size); - std::size_t n = to_size_t(end_version - begin_version); - REALM_ASSERT(n == 0 || m_acc); - std::size_t offset = to_size_t(begin_version - m_ct_base_version); - for (std::size_t i = 0; i < n; ++i) { - iterators[i] = BinaryIterator(&m_acc->ct_history, offset + i); - } -} - - -// Overriding member in _impl::History -void ServerHistory::set_oldest_bound_version(version_type realm_version) -{ - REALM_ASSERT(realm_version >= m_version_of_oldest_bound_snapshot); - if (realm_version > m_version_of_oldest_bound_snapshot) { - m_version_of_oldest_bound_snapshot = realm_version; - trim_cont_transact_history(); // Throws - } -} - - -// Overriding member in _impl::History -void ServerHistory::verify() const -{ -#ifdef REALM_DEBUG - // The size of the continuous transactions history can only be zero when the - // Realm is in the initial empty state where top-ref is null. - version_type initial_realm_version = 1; - REALM_ASSERT(m_ct_history_size != 0 || m_ct_base_version == initial_realm_version); - - if (!m_acc) { - REALM_ASSERT(m_local_file_ident == g_root_node_file_ident); - REALM_ASSERT(m_num_client_files == 0); - REALM_ASSERT(m_history_size == 0); - REALM_ASSERT(m_server_version_salt == 0); - REALM_ASSERT(m_history_base_version == 0); - REALM_ASSERT(m_ct_history_size == 0); - return; - } - - m_acc->root.verify(); - m_acc->client_files.verify(); - m_acc->sync_history.verify(); - if (m_acc->upstream_status.is_attached()) - m_acc->upstream_status.verify(); - if (m_acc->partial_sync.is_attached()) - m_acc->partial_sync.verify(); - m_acc->cf_ident_salts.verify(); - m_acc->cf_client_versions.verify(); - m_acc->cf_rh_base_versions.verify(); - m_acc->cf_recip_hist_refs.verify(); - m_acc->cf_proxy_files.verify(); - m_acc->cf_client_types.verify(); - m_acc->cf_last_seen_timestamps.verify(); - m_acc->cf_locked_server_versions.verify(); - m_acc->sh_version_salts.verify(); - m_acc->sh_origin_files.verify(); - m_acc->sh_client_versions.verify(); - m_acc->sh_timestamps.verify(); - m_acc->sh_changesets.verify(); - m_acc->sh_cumul_byte_sizes.verify(); - m_acc->ct_history.verify(); - - REALM_ASSERT(m_history_base_version == m_acc->root.get_as_ref_or_tagged(s_history_base_version_iip).get_as_int()); - salt_type base_version_salt = m_acc->root.get_as_ref_or_tagged(s_base_version_salt_iip).get_as_int(); - REALM_ASSERT((m_history_base_version == 0) == (base_version_salt == 0)); - - REALM_ASSERT(m_acc->cf_ident_salts.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_client_versions.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_rh_base_versions.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_recip_hist_refs.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_proxy_files.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_client_types.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_last_seen_timestamps.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_locked_server_versions.size() == m_num_client_files); - - REALM_ASSERT(m_acc->sh_version_salts.size() == m_history_size); - REALM_ASSERT(m_acc->sh_origin_files.size() == m_history_size); - REALM_ASSERT(m_acc->sh_client_versions.size() == m_history_size); - REALM_ASSERT(m_acc->sh_timestamps.size() == m_history_size); - REALM_ASSERT(m_acc->sh_changesets.size() == m_history_size); - REALM_ASSERT(m_acc->sh_cumul_byte_sizes.size() == m_history_size); - - salt_type server_version_salt = - (m_history_size == 0 ? base_version_salt : salt_type(m_acc->sh_version_salts.get(m_history_size - 1))); - REALM_ASSERT(m_server_version_salt == server_version_salt); - - REALM_ASSERT(m_local_file_ident > 0 && std::uint_fast64_t(m_local_file_ident) < m_num_client_files); - - // Check history entries - std::int_fast64_t accum_byte_size = 0; - struct ClientFile { - version_type last_integrated_client_version; - }; - std::unordered_map client_files; - for (std::size_t i = 0; i < m_history_size; ++i) { - auto salt = m_acc->sh_version_salts.get(i); - REALM_ASSERT(salt > 0 && salt <= 0x0'7FFF'FFFF'FFFF'FFFF); - file_ident_type origin_file_ident = 0; - REALM_ASSERT(!util::int_cast_with_overflow_detect(m_acc->sh_origin_files.get(i), origin_file_ident)); - REALM_ASSERT(origin_file_ident != m_local_file_ident); - std::size_t origin_file_index = 0; - REALM_ASSERT(!util::int_cast_with_overflow_detect(origin_file_ident, origin_file_index)); - REALM_ASSERT(origin_file_index < m_num_client_files); - version_type client_version = 0; - REALM_ASSERT(!util::int_cast_with_overflow_detect(m_acc->sh_client_versions.get(i), client_version)); - bool of_local_origin = (origin_file_ident == 0); - if (of_local_origin) { - REALM_ASSERT(client_version == 0); - } - else { - file_ident_type client_file_ident = 0; - bool from_reference_file = (origin_file_ident == m_local_file_ident); - if (!from_reference_file) { - auto client_type = m_acc->cf_client_types.get(origin_file_index); - bool good_client_type = false; - switch (ClientType(client_type)) { - case ClientType::upstream: - good_client_type = true; - break; - case ClientType::indirect: { - auto proxy_file = m_acc->cf_proxy_files.get(origin_file_index); - REALM_ASSERT(!util::int_cast_with_overflow_detect(proxy_file, client_file_ident)); - good_client_type = true; - break; - } - case ClientType::self: - break; - case ClientType::legacy: - case ClientType::regular: - case ClientType::subserver: - client_file_ident = origin_file_ident; - good_client_type = true; - break; - } - REALM_ASSERT(good_client_type); - } - ClientFile& client_file = client_files[client_file_ident]; - if (from_reference_file) { - REALM_ASSERT(client_version >= client_file.last_integrated_client_version); - } - else { - REALM_ASSERT(client_version > client_file.last_integrated_client_version); - } - client_file.last_integrated_client_version = client_version; - } - - std::size_t changeset_size = ChunkedBinaryData(m_acc->sh_changesets, i).size(); - accum_byte_size += changeset_size; - REALM_ASSERT(m_acc->sh_cumul_byte_sizes.get(i) == accum_byte_size); - } - - // Check client file entries - version_type current_server_version = m_history_base_version + m_history_size; - REALM_ASSERT(m_num_client_files >= 2); - bool found_self = false; - for (std::size_t i = 0; i < m_num_client_files; ++i) { - file_ident_type client_file_ident = file_ident_type(i); - auto j = client_files.find(client_file_ident); - ClientFile* client_file = (j == client_files.end() ? nullptr : &j->second); - version_type last_integrated_client_version = 0; - if (client_file) - last_integrated_client_version = client_file->last_integrated_client_version; - auto ident_salt = m_acc->cf_ident_salts.get(i); - auto client_version = m_acc->cf_client_versions.get(i); - auto rh_base_version = m_acc->cf_rh_base_versions.get(i); - auto recip_hist_ref = m_acc->cf_recip_hist_refs.get(i); - auto proxy_file = m_acc->cf_proxy_files.get(i); - auto client_type = m_acc->cf_client_types.get(i); - auto last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(i); - auto locked_server_version = m_acc->cf_locked_server_versions.get(i); - version_type client_version_2 = 0; - REALM_ASSERT(!util::int_cast_with_overflow_detect(client_version, client_version_2)); - file_ident_type proxy_file_2 = 0; - REALM_ASSERT(!util::int_cast_with_overflow_detect(proxy_file, proxy_file_2)); - version_type locked_server_version_2 = 0; - REALM_ASSERT(!util::int_cast_with_overflow_detect(locked_server_version, locked_server_version_2)); - if (client_file_ident == 0) { - // Special entry - REALM_ASSERT(ident_salt == 0); - REALM_ASSERT(proxy_file_2 == 0); - REALM_ASSERT(client_type == 0); - REALM_ASSERT(last_seen_timestamp == 0); - REALM_ASSERT(locked_server_version_2 == 0); - // Upstream server - REALM_ASSERT(client_version_2 >= last_integrated_client_version); - } - else if (client_file_ident == g_root_node_file_ident) { - // Root node's entry - REALM_ASSERT(ident_salt == 0); - REALM_ASSERT(client_version_2 == 0); - REALM_ASSERT(rh_base_version == 0); - REALM_ASSERT(recip_hist_ref == 0); - REALM_ASSERT(proxy_file_2 == 0); - REALM_ASSERT(client_type == 0); - REALM_ASSERT(last_seen_timestamp == 0); - REALM_ASSERT(locked_server_version_2 == 0); - REALM_ASSERT(!client_file); - if (m_local_file_ident == g_root_node_file_ident) - found_self = true; - } - else if (client_file_ident == m_local_file_ident) { - // Entry representing the Realm file itself - REALM_ASSERT(ident_salt == 0); - REALM_ASSERT(client_version_2 == 0); - REALM_ASSERT(rh_base_version == 0); - REALM_ASSERT(recip_hist_ref == 0); - REALM_ASSERT(proxy_file_2 == 0); - REALM_ASSERT(client_type == int(ClientType::self)); - REALM_ASSERT(last_seen_timestamp == 0); - REALM_ASSERT(locked_server_version_2 == 0); - REALM_ASSERT(!client_file); - found_self = true; - } - else if (ident_salt == 0) { - if (proxy_file_2 == 0) { - // This entry represents a file reachable via the upstream - // server. - REALM_ASSERT(client_version_2 == 0); - REALM_ASSERT(rh_base_version == 0); - REALM_ASSERT(recip_hist_ref == 0); - REALM_ASSERT(client_type == int(ClientType::upstream)); - REALM_ASSERT(last_seen_timestamp == 0); - REALM_ASSERT(locked_server_version_2 == 0); - REALM_ASSERT(!client_file); - } - else { - // This entry represents a client of a direct client, such as - // client of a partial view, or a client of a subserver. - REALM_ASSERT(client_version_2 == 0); - REALM_ASSERT(rh_base_version == 0); - REALM_ASSERT(recip_hist_ref == 0); - REALM_ASSERT(client_type == int(ClientType::indirect)); - REALM_ASSERT(last_seen_timestamp == 0); - REALM_ASSERT(locked_server_version_2 == 0); - REALM_ASSERT(is_valid_proxy_file_ident(proxy_file_2)); - REALM_ASSERT(!client_file); - } - } - else { - // This entry represents a direct client, which can be a regular - // client, a subserver, or a partial view. - bool expired = (last_seen_timestamp == 0); - REALM_ASSERT(ident_salt > 0 && ident_salt <= 0x0'7FFF'FFFF'FFFF'FFFF); - REALM_ASSERT(client_version_2 >= last_integrated_client_version); - REALM_ASSERT(!expired || (recip_hist_ref == 0)); - REALM_ASSERT(proxy_file_2 == 0); - REALM_ASSERT(is_direct_client(ClientType(client_type))); - REALM_ASSERT(locked_server_version_2 <= current_server_version); - } - } - REALM_ASSERT(found_self); - - REALM_ASSERT(m_ct_history_size >= 1); // See comment above - REALM_ASSERT(m_acc->ct_history.size() == m_ct_history_size); -#endif // REALM_DEBUG -} - -void ServerHistory::discard_accessors() const noexcept -{ - m_acc = util::none; -} - - -class ServerHistory::DiscardAccessorsGuard { -public: - DiscardAccessorsGuard(const ServerHistory& sh) noexcept - : m_server_history{&sh} - { - } - ~DiscardAccessorsGuard() noexcept - { - if (REALM_UNLIKELY(m_server_history)) - m_server_history->discard_accessors(); - } - void release() noexcept - { - m_server_history = nullptr; - } - -private: - const ServerHistory* m_server_history; -}; - - -// Overriding member in _impl::History -void ServerHistory::update_from_ref_and_version(ref_type ref, version_type realm_version) -{ - if (ref == 0) { - // No history schema yet - m_local_file_ident = g_root_node_file_ident; - m_num_client_files = 0; - m_history_base_version = 0; - m_history_size = 0; - m_server_version_salt = 0; - m_ct_base_version = realm_version; - m_ct_history_size = 0; - discard_accessors(); - return; - } - if (REALM_LIKELY(m_acc)) { - m_acc->init_from_ref(ref); // Throws - } - else { - Allocator& alloc = _impl::GroupFriend::get_alloc(*m_group); - m_acc.emplace(alloc); - DiscardAccessorsGuard dag{*this}; - m_acc->init_from_ref(ref); - _impl::GroupFriend::set_history_parent(*m_group, m_acc->root); - - if (m_acc->upstream_status.is_attached()) { - REALM_ASSERT(m_acc->upstream_status.size() == s_upstream_status_size); - } - if (m_acc->partial_sync.is_attached()) { - REALM_ASSERT(m_acc->partial_sync.size() == s_partial_sync_size); - } - dag.release(); - } - - if (m_acc->upstream_status.is_attached()) { - file_ident_type file_ident = m_group->get_sync_file_id(); - m_local_file_ident = (file_ident == 0 ? g_root_node_file_ident : file_ident); - } - else { - m_local_file_ident = g_root_node_file_ident; - } - - m_num_client_files = m_acc->cf_ident_salts.size(); - REALM_ASSERT(m_acc->cf_client_versions.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_rh_base_versions.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_recip_hist_refs.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_proxy_files.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_client_types.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_last_seen_timestamps.size() == m_num_client_files); - REALM_ASSERT(m_acc->cf_locked_server_versions.size() == m_num_client_files); - - m_history_base_version = version_type(m_acc->root.get_as_ref_or_tagged(s_history_base_version_iip).get_as_int()); - m_history_size = m_acc->sh_changesets.size(); - REALM_ASSERT(m_acc->sh_version_salts.size() == m_history_size); - REALM_ASSERT(m_acc->sh_origin_files.size() == m_history_size); - REALM_ASSERT(m_acc->sh_client_versions.size() == m_history_size); - REALM_ASSERT(m_acc->sh_timestamps.size() == m_history_size); - REALM_ASSERT(m_acc->sh_cumul_byte_sizes.size() == m_history_size); - - m_server_version_salt = - (m_history_size > 0 ? salt_type(m_acc->sh_version_salts.get(m_history_size - 1)) - : salt_type(m_acc->root.get_as_ref_or_tagged(s_base_version_salt_iip).get_as_int())); - - m_ct_history_size = m_acc->ct_history.size(); - m_ct_base_version = realm_version - m_ct_history_size; -} - - -void ServerHistory::Accessors::init_from_ref(ref_type ref) -{ - root.init_from_ref(ref); - client_files.init_from_parent(); - sync_history.init_from_parent(); - - { - ref_type ref_2 = upstream_status.get_ref_from_parent(); - if (ref_2 != 0) { - upstream_status.init_from_ref(ref_2); - } - else { - upstream_status.detach(); - } - } - - cf_ident_salts.init_from_parent(); // Throws - cf_client_versions.init_from_parent(); // Throws - cf_rh_base_versions.init_from_parent(); // Throws - cf_recip_hist_refs.init_from_parent(); // Throws - cf_proxy_files.init_from_parent(); // Throws - cf_client_types.init_from_parent(); // Throws - cf_last_seen_timestamps.init_from_parent(); // Throws - cf_locked_server_versions.init_from_parent(); // Throws - sh_version_salts.init_from_parent(); // Throws - sh_origin_files.init_from_parent(); // Throws - sh_client_versions.init_from_parent(); // Throws - sh_timestamps.init_from_parent(); // Throws - sh_changesets.init_from_parent(); // Throws - sh_cumul_byte_sizes.init_from_parent(); // Throws - ct_history.init_from_parent(); // Throws - - // Note: If anything throws above, then accessors will be left in an - // undefined state. However, all IntegerBpTree accessors will still have - // a root array, and all optional BinaryColumn accessors will still - // exist, so it will be safe to call update_from_ref() again. -} - - -void ServerHistory::create_empty_history() -{ - using gf = _impl::GroupFriend; - - REALM_ASSERT(m_local_file_ident == g_root_node_file_ident); - REALM_ASSERT(m_num_client_files == 0); - REALM_ASSERT(m_history_base_version == 0); - REALM_ASSERT(m_history_size == 0); - REALM_ASSERT(m_server_version_salt == 0); - REALM_ASSERT(m_ct_history_size == 0); - REALM_ASSERT(!m_acc); - Allocator& alloc = m_db->get_alloc(); - m_acc.emplace(alloc); - DiscardAccessorsGuard dag{*this}; - gf::prepare_history_parent(*m_group, m_acc->root, Replication::hist_SyncServer, - get_server_history_schema_version(), m_local_file_ident); // Throws - m_acc->create(); // Throws - dag.release(); - - // Add the special client file entry (index = 0), and the root servers entry - // (index = 1). - static_assert(g_root_node_file_ident == 1, ""); - REALM_ASSERT(m_num_client_files == 0); - for (int i = 0; i < 2; ++i) { - m_acc->cf_ident_salts.insert(realm::npos, 0); // Throws - m_acc->cf_client_versions.insert(realm::npos, 0); // Throws - m_acc->cf_rh_base_versions.insert(realm::npos, 0); // Throws - m_acc->cf_recip_hist_refs.insert(realm::npos, 0); // Throws - m_acc->cf_proxy_files.insert(realm::npos, 0); // Throws - m_acc->cf_client_types.insert(realm::npos, 0); // Throws - m_acc->cf_last_seen_timestamps.insert(realm::npos, 0); // Throws - m_acc->cf_locked_server_versions.insert(realm::npos, 0); // Throws - ++m_num_client_files; - } -} - -void ServerHistory::Accessors::create() -{ - // Note: `Array::create()` does *NOT* call `Node::update_parent()`, while - // `BPlusTree::create()` *DOES* update its parent in an exception-safe - // way. This means that we need destruction guards for arrays, but not - // BPlusTrees/BinaryColumns. - - // Note: The arrays `upstream_status` and `partial_sync` are created - // on-demand instead of here. - - bool context_flag_no = false; - root.create(Array::type_HasRefs, context_flag_no, s_root_size); // Throws - _impl::DeepArrayDestroyGuard destroy_guard(&root); - - client_files.create(Array::type_HasRefs, context_flag_no, s_client_files_size); // Throws - client_files.update_parent(); // Throws - - sync_history.create(Array::type_HasRefs, context_flag_no, s_sync_history_size); // Throws - sync_history.update_parent(); // Throws - - schema_versions.create(Array::type_HasRefs, context_flag_no, s_schema_versions_size); // Throws - schema_versions.update_parent(); - Allocator& alloc = schema_versions.get_alloc(); - for (int i = 0; i < s_schema_versions_size; i++) { - MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag_no, alloc); - ref_type ref = mem.get_ref(); - schema_versions.set_as_ref(i, ref); - } - - cf_ident_salts.create(); // Throws - cf_client_versions.create(); // Throws - cf_rh_base_versions.create(); // Throws - cf_recip_hist_refs.create(); // Throws - cf_proxy_files.create(); // Throws - cf_client_types.create(); // Throws - cf_last_seen_timestamps.create(); // Throws - cf_locked_server_versions.create(); // Throws - - sh_version_salts.create(); // Throws - sh_origin_files.create(); // Throws - sh_client_versions.create(); // Throws - sh_timestamps.create(); // Throws - sh_changesets.create(); // Throws - sh_cumul_byte_sizes.create(); // Throws - - ct_history.create(); // Throws - - destroy_guard.release(); - root.update_parent(); // Throws -} - - -auto ServerHistory::get_server_version_salt(version_type server_version) const noexcept -> salt_type -{ - REALM_ASSERT(server_version >= m_history_base_version); - if (server_version == m_history_base_version) - return salt_type(m_acc->root.get(s_base_version_salt_iip)); - std::size_t history_entry_index = to_size_t(server_version - m_history_base_version) - 1; - REALM_ASSERT(history_entry_index < m_history_size); - return salt_type(m_acc->sh_version_salts.get(history_entry_index)); -} - - -bool ServerHistory::is_valid_proxy_file_ident(file_ident_type file_ident) const noexcept -{ - static_assert(g_root_node_file_ident == 1, ""); - REALM_ASSERT(file_ident >= 2); - REALM_ASSERT(std::uint_fast64_t(file_ident) < m_num_client_files); - std::size_t i = std::size_t(file_ident); - auto client_type = m_acc->cf_client_types.get(i); - return is_direct_client(ClientType(client_type)); -} - - -void ServerHistory::add_core_history_entry(BinaryData changeset) -{ - REALM_ASSERT(m_acc->ct_history.size() == m_ct_history_size); - - if (changeset.is_null()) - changeset = BinaryData("", 0); - - m_acc->ct_history.add(changeset); // Throws - ++m_ct_history_size; -} - - -void ServerHistory::add_sync_history_entry(const HistoryEntry& entry) -{ - REALM_ASSERT(m_acc->sh_version_salts.size() == m_history_size); - REALM_ASSERT(m_acc->sh_origin_files.size() == m_history_size); - REALM_ASSERT(m_acc->sh_client_versions.size() == m_history_size); - REALM_ASSERT(m_acc->sh_timestamps.size() == m_history_size); - REALM_ASSERT(m_acc->sh_changesets.size() == m_history_size); - REALM_ASSERT(m_acc->sh_cumul_byte_sizes.size() == m_history_size); - - std::int_fast64_t client_file = std::int_fast64_t(entry.origin_file_ident); - std::int_fast64_t client_version = std::int_fast64_t(entry.remote_version); - std::int_fast64_t timestamp = std::int_fast64_t(entry.origin_timestamp); - - // FIXME: BinaryColumn::set() currently interprets BinaryData(0,0) as - // null. It should probably be changed such that BinaryData(0,0) is - // always interpreted as the empty string. For the purpose of setting - // null values, BinaryColumn::set() should accept values of type - // Optional(). - BinaryData changeset("", 0); - if (!entry.changeset.is_null()) - changeset = entry.changeset.get_first_chunk(); - - m_acc->sh_version_salts.insert(realm::npos, m_salt_for_new_server_versions); // Throws - m_acc->sh_origin_files.insert(realm::npos, client_file); // Throws - m_acc->sh_client_versions.insert(realm::npos, client_version); // Throws - m_acc->sh_timestamps.insert(realm::npos, timestamp); // Throws - m_acc->sh_changesets.add(changeset); // Throws - - // Update the cumulative byte size. - std::int_fast64_t previous_history_byte_size = - (m_history_size == 0 ? 0 : m_acc->sh_cumul_byte_sizes.get(m_history_size - 1)); - std::int_fast64_t history_byte_size = previous_history_byte_size + changeset.size(); - m_acc->sh_cumul_byte_sizes.insert(realm::npos, history_byte_size); - - ++m_history_size; - m_server_version_salt = m_salt_for_new_server_versions; -} - - -void ServerHistory::trim_cont_transact_history() -{ - REALM_ASSERT(m_acc->ct_history.size() == m_ct_history_size); - - // `m_version_of_oldest_bound_snapshot` is not updated by transactions - // occuring through other DB objects than the one associated with - // this history object. For that reason, it can sometimes happen that it - // precedes the beginning of the history, even though it seems - // nonsensical. It would happen if the history was already trimmed via one - // of the other DB objects. In such a case, no trimming can be done - // yet. - if (m_version_of_oldest_bound_snapshot > m_ct_base_version) { - std::size_t num_entries_to_erase = std::size_t(m_version_of_oldest_bound_snapshot - m_ct_base_version); - // The new changeset is always added before - // set_oldest_bound_version() is called. Therefore, the trimming - // operation can never leave the history empty. - REALM_ASSERT(num_entries_to_erase < m_ct_history_size); - for (std::size_t i = 0; i < num_entries_to_erase; ++i) { - std::size_t j = num_entries_to_erase - i - 1; - m_acc->ct_history.erase(j); - } - m_ct_base_version += num_entries_to_erase; - m_ct_history_size -= num_entries_to_erase; - } -} - - -ChunkedBinaryData ServerHistory::get_changeset(version_type server_version) const noexcept -{ - REALM_ASSERT(server_version > m_history_base_version && server_version <= get_server_version()); - std::size_t history_entry_ndx = to_size_t(server_version - m_history_base_version) - 1; - return ChunkedBinaryData(m_acc->sh_changesets, history_entry_ndx); -} - - -// Skips history entries with empty changesets, and history entries produced by -// integration of changes received from the specified remote file. -// -// Pass zero for `remote_file_ident` if the remote file is on the upstream -// server, or the reference file. -// -// Returns zero if no history entry was found. Otherwise it returns the version -// produced by the changeset of the located history entry. -auto ServerHistory::find_history_entry(file_ident_type remote_file_ident, version_type begin_version, - version_type end_version, HistoryEntry& entry, - version_type& last_integrated_remote_version) const noexcept -> version_type -{ - REALM_ASSERT(remote_file_ident != g_root_node_file_ident); - REALM_ASSERT(begin_version >= m_history_base_version); - REALM_ASSERT(begin_version <= end_version); - auto server_version = begin_version; - while (server_version < end_version) { - ++server_version; - // FIXME: Find a way to avoid dynamically allocating a buffer for, and - // copying the changeset for all the skipped history entries. - HistoryEntry entry_2 = get_history_entry(server_version); - bool received_from_client = received_from(entry_2, remote_file_ident); - if (received_from_client) { - last_integrated_remote_version = entry_2.remote_version; - continue; - } - if (entry_2.changeset.size() == 0) - continue; // Empty - // These changes were not received from the specified client, and the - // changeset was not empty. - entry = entry_2; - return server_version; - } - return 0; -} - - -auto ServerHistory::get_history_entry(version_type server_version) const noexcept -> HistoryEntry -{ - REALM_ASSERT(server_version > m_history_base_version && server_version <= get_server_version()); - std::size_t history_entry_ndx = to_size_t(server_version - m_history_base_version) - 1; - auto origin_file = m_acc->sh_origin_files.get(history_entry_ndx); - auto client_version = m_acc->sh_client_versions.get(history_entry_ndx); - auto timestamp = m_acc->sh_timestamps.get(history_entry_ndx); - ChunkedBinaryData chunked_changeset(m_acc->sh_changesets, history_entry_ndx); - HistoryEntry entry; - entry.origin_file_ident = file_ident_type(origin_file); - entry.remote_version = version_type(client_version); - entry.origin_timestamp = timestamp_type(timestamp); - entry.changeset = chunked_changeset; - return entry; -} - - -// Returns true if, and only if the specified history entry was produced by -// integratrion of a changeset that was received from the specified remote -// file. Use `remote_file_ident = 0` to specify the upstream server when on a -// subtier node of a star topology server cluster, or to specify the reference -// file when in a partial view. -bool ServerHistory::received_from(const HistoryEntry& entry, file_ident_type remote_file_ident) const noexcept -{ - file_ident_type origin_file_ident = entry.origin_file_ident; - std::size_t origin_file_index = std::size_t(origin_file_ident); - bool from_upstream_server = (remote_file_ident == 0); - if (!from_upstream_server) { - std::size_t remote_file_index = std::size_t(remote_file_ident); - REALM_ASSERT(is_direct_client(ClientType(m_acc->cf_client_types.get(remote_file_index)))); - if (origin_file_ident == remote_file_ident) - return true; - file_ident_type proxy_file = file_ident_type(m_acc->cf_proxy_files.get(origin_file_index)); - return (proxy_file == remote_file_ident); - } - bool of_local_origin = (origin_file_ident == 0); - if (of_local_origin) - return false; - ClientType client_type = ClientType(m_acc->cf_client_types.get(origin_file_index)); - return (client_type == ClientType::upstream); -} - - -auto ServerHistory::get_history_contents() const -> HistoryContents -{ - HistoryContents hc; - - TransactionRef tr = m_db->start_read(); // Throws - version_type realm_version = tr->get_version(); - const_cast(this)->set_group(tr.get()); - ensure_updated(realm_version); // Throws - - util::AppendBuffer buffer; - hc.client_files = {}; - for (std::size_t i = 0; i < m_num_client_files; ++i) { - HistoryContents::ClientFile cf; - cf.ident_salt = m_acc->cf_ident_salts.get(i); - cf.client_version = m_acc->cf_client_versions.get(i); - cf.rh_base_version = m_acc->cf_rh_base_versions.get(i); - cf.proxy_file = m_acc->cf_proxy_files.get(i); - cf.client_type = m_acc->cf_client_types.get(i); - cf.locked_server_version = m_acc->cf_locked_server_versions.get(i); - cf.reciprocal_history = {}; - version_type recip_hist_base_version = version_type(cf.rh_base_version); - ReciprocalHistory recip_hist(m_acc->cf_recip_hist_refs, i, recip_hist_base_version); // Throws - std::size_t recip_hist_size = recip_hist.size(); - for (std::size_t j = 0; j < recip_hist_size; ++j) { - version_type version = recip_hist_base_version + i + 1; - ChunkedBinaryData transform; - if (recip_hist.get(version, transform)) { - transform.copy_to(buffer); - cf.reciprocal_history.push_back(std::string{buffer.data(), buffer.size()}); - } - else { - cf.reciprocal_history.push_back(util::none); - } - } - hc.client_files.push_back(cf); - } - - hc.history_base_version = m_acc->root.get_as_ref_or_tagged(s_history_base_version_iip).get_as_int(); - hc.base_version_salt = m_acc->root.get_as_ref_or_tagged(s_base_version_salt_iip).get_as_int(); - - hc.sync_history = {}; - for (size_t i = 0; i < m_history_size; ++i) { - HistoryContents::HistoryEntry he; - he.version_salt = m_acc->sh_version_salts.get(i); - he.client_file_ident = m_acc->sh_origin_files.get(i); - he.client_version = m_acc->sh_client_versions.get(i); - he.timestamp = m_acc->sh_timestamps.get(i); - he.cumul_byte_size = m_acc->sh_cumul_byte_sizes.get(i); - ChunkedBinaryData chunked_changeset(m_acc->sh_changesets, i); - chunked_changeset.copy_to(buffer); - he.changeset = std::string(buffer.data(), buffer.size()); - hc.sync_history.push_back(he); - } - - hc.servers_client_file_ident = m_local_file_ident; - - return hc; -} - - -void ServerHistory::fixup_state_and_changesets_for_assigned_file_ident(Transaction& group, file_ident_type file_ident) -{ - // Must be in write transaction! - - REALM_ASSERT(file_ident != 0); - REALM_ASSERT(file_ident != g_root_node_file_ident); - REALM_ASSERT(m_acc->upstream_status.is_attached()); - REALM_ASSERT(m_local_file_ident == g_root_node_file_ident); - using Instruction = realm::sync::Instruction; - - auto promote_global_key = [&](GlobalKey& oid) { - REALM_ASSERT(oid.hi() == 0); // client_file_ident == 0 - oid = GlobalKey{uint64_t(file_ident), oid.lo()}; - }; - - auto promote_primary_key = [&](Instruction::PrimaryKey& pk) { - mpark::visit(overload{[&](GlobalKey& key) { - promote_global_key(key); - }, - [](auto&&) {}}, - pk); - }; - - auto get_table_for_class = [&](StringData class_name) -> ConstTableRef { - Group::TableNameBuffer buffer; - return group.get_table(Group::class_name_to_table_name(class_name, buffer)); - }; - - // Fix up changesets in history. We know that all of these are of our own - // creation. - for (std::size_t i = 0; i < m_acc->sh_changesets.size(); ++i) { - ChunkedBinaryData changeset{m_acc->sh_changesets, i}; - ChunkedBinaryInputStream in{changeset}; - Changeset log; - parse_changeset(in, log); - - auto last_class_name = sync::InternString::npos; - ConstTableRef selected_table; - for (auto instr : log) { - if (!instr) - continue; - - if (auto obj_instr = instr->get_if()) { - // Cache the TableRef - if (obj_instr->table != last_class_name) { - StringData class_name = log.get_string(obj_instr->table); - last_class_name = obj_instr->table; - selected_table = get_table_for_class(class_name); - } - - // Fix up instructions using GlobalKey to identify objects. - promote_primary_key(obj_instr->object); - - // Fix up the payload for Set and ArrayInsert. - Instruction::Payload* payload = nullptr; - if (auto set_instr = instr->get_if()) { - payload = &set_instr->value; - } - else if (auto list_insert_instr = instr->get_if()) { - payload = &list_insert_instr->value; - } - - if (payload && payload->type == Instruction::Payload::Type::Link) { - promote_primary_key(payload->data.link.target); - } - } - } - - ChangesetEncoder::Buffer modified; - encode_changeset(log, modified); - BinaryData result = BinaryData{modified.data(), modified.size()}; - m_acc->sh_changesets.set(i, result); - } -} - -void ServerHistory::record_current_schema_version() -{ - using gf = _impl::GroupFriend; - Allocator& alloc = gf::get_alloc(*m_group); - auto ref = gf::get_history_ref(*m_group); - REALM_ASSERT(ref != 0); - Array root{alloc}; - gf::set_history_parent(*m_group, root); - root.init_from_ref(ref); - Array schema_versions{alloc}; - schema_versions.set_parent(&root, s_schema_versions_iip); - schema_versions.init_from_parent(); - version_type snapshot_version = m_db->get_version_of_latest_snapshot(); - record_current_schema_version(schema_versions, snapshot_version); // Throws -} - - -void ServerHistory::record_current_schema_version(Array& schema_versions, version_type snapshot_version) -{ - static_assert(s_schema_versions_size == 4, ""); - REALM_ASSERT(schema_versions.size() == s_schema_versions_size); - - Allocator& alloc = schema_versions.get_alloc(); - { - Array sv_schema_versions{alloc}; - sv_schema_versions.set_parent(&schema_versions, s_sv_schema_versions_iip); - sv_schema_versions.init_from_parent(); - int schema_version = get_server_history_schema_version(); - sv_schema_versions.add(schema_version); // Throws - } - { - Array sv_library_versions{alloc}; - sv_library_versions.set_parent(&schema_versions, s_sv_library_versions_iip); - sv_library_versions.init_from_parent(); - const char* library_version = REALM_VERSION_STRING; - std::size_t size = std::strlen(library_version); - Array value{alloc}; - bool context_flag = false; - value.create(Array::type_Normal, context_flag, size); // Throws - _impl::ShallowArrayDestroyGuard adg{&value}; - using uchar = unsigned char; - for (std::size_t i = 0; i < size; ++i) - value.set(i, std::int_fast64_t(uchar(library_version[i]))); // Throws - sv_library_versions.add(std::int_fast64_t(value.get_ref())); // Throws - adg.release(); // Ownership transferred to parent array - } - { - Array sv_snapshot_versions{alloc}; - sv_snapshot_versions.set_parent(&schema_versions, s_sv_snapshot_versions_iip); - sv_snapshot_versions.init_from_parent(); - sv_snapshot_versions.add(std::int_fast64_t(snapshot_version)); // Throws - } - { - Array sv_timestamps{alloc}; - sv_timestamps.set_parent(&schema_versions, s_sv_timestamps_iip); - sv_timestamps.init_from_parent(); - std::time_t timestamp = std::time(nullptr); - sv_timestamps.add(std::int_fast64_t(timestamp)); // Throws - } -} - - -std::ostream& _impl::operator<<(std::ostream& out, const ServerHistory::HistoryContents& hc) -{ - out << "client files:\n"; - for (std::size_t i = 0; i < hc.client_files.size(); ++i) { - out << "\n"; - out << " client_file_ident = " << i << "\n"; - out << " ident_salt = " << hc.client_files[i].ident_salt << "\n"; - out << " client_version = " << hc.client_files[i].client_version << "\n"; - out << " rh_base_version = " << hc.client_files[i].rh_base_version << "\n"; - out << " proxy_file = " << hc.client_files[i].proxy_file << "\n"; - out << " client_type = " << hc.client_files[i].client_type << "\n"; - out << " locked_server_version = " << hc.client_files[i].locked_server_version << "\n"; - out << " reciprocal history:\n"; - for (const util::Optional& transform : hc.client_files[i].reciprocal_history) { - if (transform) { - out << " " << util::hex_dump((*transform).data(), (*transform).size()) << "\n"; - } - else { - out << " NULL\n"; - } - } - out << "\n"; - } - out << "\n"; - - out << "history_base_version = " << hc.history_base_version << "\n"; - out << "base_version_salt = " << hc.base_version_salt << "\n"; - out << "\n"; - - out << "history entries:\n"; - for (std::size_t i = 0; i < hc.sync_history.size(); ++i) { - out << "\n"; - out << " version_salt = " << hc.sync_history[i].version_salt << "\n"; - out << " client_file_ident = " << hc.sync_history[i].client_file_ident << "\n"; - out << " client_version = " << hc.sync_history[i].client_version << "\n"; - out << " timestamp = " << hc.sync_history[i].timestamp << "\n"; - out << " cumul_byte_size = " << hc.sync_history[i].cumul_byte_size << "\n"; - const std::string& changeset = hc.sync_history[i].changeset; - out << " changeset = " << util::hex_dump(changeset.data(), changeset.size()) << "\n"; - out << "\n"; - } - out << "\n"; - - out << "servers_client_file_ident = " << hc.servers_client_file_ident << "\n"; - - return out; -} - -bool _impl::operator==(const ServerHistory::HistoryContents& hc_1, const ServerHistory::HistoryContents& hc_2) -{ - if (hc_1.client_files.size() != hc_2.client_files.size()) - return false; - - for (std::size_t i = 0; i < hc_1.client_files.size(); ++i) { - ServerHistory::HistoryContents::ClientFile cf_1 = hc_1.client_files[i]; - ServerHistory::HistoryContents::ClientFile cf_2 = hc_2.client_files[i]; - - bool partially_equal = - (cf_1.ident_salt == cf_2.ident_salt && cf_1.client_version == cf_2.client_version && - cf_1.rh_base_version == cf_2.rh_base_version && cf_1.proxy_file == cf_2.proxy_file && - cf_1.client_type == cf_2.client_type && cf_1.locked_server_version == cf_2.locked_server_version && - cf_1.reciprocal_history.size() == cf_2.reciprocal_history.size()); - if (!partially_equal) - return false; - - for (std::size_t j = 0; j < cf_1.reciprocal_history.size(); ++j) { - if (cf_1.reciprocal_history[j] != cf_2.reciprocal_history[j]) - return false; - } - } - - bool same_base_version = - (hc_1.history_base_version == hc_2.history_base_version && hc_1.base_version_salt == hc_2.base_version_salt); - if (!same_base_version) - return false; - - if (hc_1.sync_history.size() != hc_2.sync_history.size()) - return false; - - for (std::size_t i = 0; i < hc_1.sync_history.size(); ++i) { - ServerHistory::HistoryContents::HistoryEntry sh_1 = hc_1.sync_history[i]; - ServerHistory::HistoryContents::HistoryEntry sh_2 = hc_2.sync_history[i]; - bool equal = (sh_1.version_salt == sh_2.version_salt && sh_1.client_file_ident == sh_2.client_file_ident && - sh_1.client_version == sh_2.client_version && sh_1.timestamp == sh_2.timestamp && - sh_1.cumul_byte_size == sh_2.cumul_byte_size); - if (!equal) - return false; - } - - if (hc_1.servers_client_file_ident != hc_2.servers_client_file_ident) - return false; - - return true; -} diff --git a/src/realm/sync/noinst/server/server_history.hpp b/src/realm/sync/noinst/server/server_history.hpp deleted file mode 100644 index b8372c94d51..00000000000 --- a/src/realm/sync/noinst/server/server_history.hpp +++ /dev/null @@ -1,884 +0,0 @@ - -#ifndef REALM_NOINST_SERVER_HISTORY_HPP -#define REALM_NOINST_SERVER_HISTORY_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace realm { -namespace sync { -struct Changeset; -} - -namespace _impl { - -// As new schema versions come into existence, describe them here. -// -// 0 Initial version. -// -// 1 Added support for stable IDs. -// -// 2 Added support for partial sync (`s_psp_server_version_ndx_in_parent`, -// `s_psp_master_version_ndx_in_parent`). -// -// 3 Added write capability to partial sync (introduction of optional subarray -// `ServerHistory::Arrays::partial_sync`). -// -// 4 Added a new first entry to `client_files` table. This special entry -// represents the "invalid" client file identifier with value zero. The -// references to client file entries from history entries are now stored as -// client file identifiers. Before, they were stored as the client file -// identifier minus one. Added `proxy_file` column to `client_files` -// table. Added upstream client functionality (introduction of optional -// subarray `ServerHistory::Arrays::upstream_status`). -// -// 5 Added support for history log compaction. The top-level fields -// `last_compacted_at` and `compacted_until_version` were added, and the -// `client_files` table gained a `last_seen` timestamp entry. -// -// 6 Misplaced `ct_history_entries` was moved out of table `history_entries` -// and into history's root array. -// -// Changed the format for downloadable_bytes which is used in the download -// progress system. The history entry array is enlarged with a new column -// containing cumulative byte sizes of changesets. upload_byte_size is made -// obsolete. history_byte_size is made obsolete. -// -// 7 Convert full-state partial views to reduced-state partial views. -// -// 8 Added new column `locked_server_versions` to the `client_files` table. -// -// Added a `schema_versions` table for the purpose of recording the creation -// of, and the migrations of the history compartment from one schema version -// to the next. -// -// 9 When `last_seen_at` is zero for a particular entry in the "client files" -// table, it now means that that entry has been expired. -// -// 10 Added new column `client_types` to the `client_files` table. -// -// The entry in `client_files` table representing the file itself no longer -// has a nonzero `ident_salt`. It was useless anyway. -// -// In a reference file, new entries in `client_files` created to represent -// clients of partial views will no longer have a nonzero -// `ident_salt`. Additionally, they will now have a nonzero `proxy_file` -// specifying the identifier of the partial view. Preexisting entries will -// not be modified, but will be marked as "legacy" entries in the -// `client_types` column. -// -// Only entries corresponding to direct clients (including partial views and -// legacy entries) have nonzero values in the `last_seen_timestamp` -// column. Previously, indirect clients and the self entry also had nonzero -// `last_seen_timestamp`. -// -// The special entry an index zero no longer has a nonzero -// `locked_server_version`. It was useless anyway. -// -// 11..19 Reserved -// -// 20 ObjectIDHistoryState enhanced with m_table_map - -constexpr int get_server_history_schema_version() noexcept -{ - return 20; -} - - -class ServerHistory : public sync::SyncReplication, - public _impl::History, - public std::enable_shared_from_this { -public: - // clang-format off - using file_ident_type = sync::file_ident_type; - using version_type = sync::version_type; - using salt_type = sync::salt_type; - using timestamp_type = sync::timestamp_type; - using SaltedFileIdent = sync::SaltedFileIdent; - using SaltedVersion = sync::SaltedVersion; - using DownloadCursor = sync::DownloadCursor; - using UploadCursor = sync::UploadCursor; - using SyncProgress = sync::SyncProgress; - using HistoryEntry = sync::HistoryEntry; - using RemoteChangeset = sync::RemoteChangeset; - // clang-format on - - enum class BootstrapError { - no_error = 0, - client_file_expired, - bad_client_file_ident, - bad_client_file_ident_salt, - bad_download_server_version, - bad_download_client_version, - bad_server_version, - bad_server_version_salt, - bad_client_type - }; - - struct HistoryEntryHandler { - virtual void handle(version_type server_version, const HistoryEntry&, std::size_t original_size) = 0; - virtual ~HistoryEntryHandler() {} - }; - - // See table at top of `server_history.cpp`. - // - // CAUTION: The values of these are fixed by the history schema. - enum class ClientType { - // clang-format off - upstream = 0, // Reachable via upstream server - self = 6, // The file itself - indirect = 1, // Client of subserver - legacy = 5, // Precise type is unknown - regular = 2, // Direct regular client - subserver = 4, // Direct subserver - // clang-format on - }; - - struct FileIdentAllocSlot { - file_ident_type proxy_file; - ClientType client_type; - SaltedFileIdent file_ident; - }; - - using FileIdentAllocSlots = std::vector; - - enum class ExtendedIntegrationError { client_file_expired, bad_origin_file_ident, bad_changeset }; - - struct IntegratableChangeset { - file_ident_type client_file_ident; // Identifier of sending client's file - timestamp_type origin_timestamp; - file_ident_type origin_file_ident; // Zero if otherwise equal to client_file_ident - UploadCursor upload_cursor; - std::string changeset; - - IntegratableChangeset(file_ident_type client_file_ident, timestamp_type origin_timestamp, - file_ident_type origin_file_ident, UploadCursor upload_cursor, - BinaryData changeset) noexcept; - - operator RemoteChangeset() const noexcept - { - RemoteChangeset rc; - rc.remote_version = upload_cursor.client_version; - rc.last_integrated_local_version = upload_cursor.last_integrated_server_version; - rc.data = BinaryData{changeset.data(), changeset.size()}; - rc.origin_timestamp = origin_timestamp; - rc.origin_file_ident = (origin_file_ident != 0 ? origin_file_ident : client_file_ident); - return rc; - } - }; - - struct IntegratableChangesetList { - UploadCursor upload_progress = {0, 0}; - version_type locked_server_version = 0; - std::vector changesets; - - bool has_changesets() const noexcept - { - return !changesets.empty(); - } - }; - - /// Key is identifier of client file from which the changes were - /// received. That client file is not necessarily the client file from which - /// the changes originated (star topology). - using IntegratableChangesets = std::map; - - struct IntegrationResult { - std::map excluded_client_files; - - std::vector integrated_changesets; - - void partial_clear() noexcept - { - integrated_changesets.clear(); - } - }; - - struct IntegratedBackup { - bool success; - sync::VersionInfo version_info; - }; - - class IntegrationReporter; - class Context; - - static constexpr bool is_direct_client(ClientType) noexcept; - - ServerHistory(Context&); - - /// Get the current Realm version and server version. - /// - /// If this file has been initiated as a partial view, \a partial_file_ident - /// is set to the file identifier allocated in the reference file for this - /// partial view, and \a partial_progress_reference_version is set to the - /// last sync version of the reference file that has been integrated into - /// the partial view. Otherwise both are set to zero. - void get_status(sync::VersionInfo&, bool& has_upstream_status, file_ident_type& partial_file_ident, - version_type& partial_progress_reference_version) const; - - /// Validate the specified client file identifier, download progress, and - /// server version as received in an IDENT message. If they are valid, fetch - /// the upload progress representing the last integrated changeset from the - /// specified client file. - /// - /// The validation step in this function will fail if `server_version` - /// refers to an earlier server version than `download_progress`, and it - /// will fail if the server history has been trimmed, and - /// `download_progress` refers to an earlier server version than the base - /// version of the history. - /// - /// On entry, the download progress (`download_progress`) should be as - /// specified by the client in the IDENT message. If - /// `download_progress.server_version` is zero, and the history base version - /// is not zero, and the history was not trimmed, then - /// `download_progress.server_version` will be set to the base version of - /// the history before being checked as described above. In all other cases, - /// `download_progress` will be checked as specified. - /// - /// \param client_type Must be one for which is_direct_client() returns - /// true, and must not be ClientType::legacy. If the actual type stored in - /// the `client_files` entry for the specified client file is not - /// ClientType::legacy, this function generates - /// BootstrapError::bad_client_type if the stored type differs from the - /// specified type. - /// - /// \param upload_progress Will be set to the position in the client-side - /// history corresponding to the last client version that has been - /// integrated on the server side. - BootstrapError bootstrap_client_session(SaltedFileIdent client_file_ident, DownloadCursor download_progress, - SaltedVersion server_version, ClientType client_type, - UploadCursor& upload_progress, version_type& locked_server_version, - util::Logger&) const; - - /// Allocate new file identifiers. - /// - /// This function must not be used with files that are either associated - /// with an upstream server, or initialized as a partial view. It throws - /// std::exception if used with any such file. - /// - /// This function is guaranteed to never introduce a new synchronization - /// version (sync::VersionInfo::sync_version). - void allocate_file_identifiers(FileIdentAllocSlots&, sync::VersionInfo&); - - /// Register a file identifier in the local file, that has been allocated by - /// an upstream server, or in case of partial sync, one that has been - /// allocated in the context of the reference file. - /// - /// In any case, the specified identifier must be valid (greater than 0), - /// and greater than any previously registered file identifier, including - /// any origin file identifier of an integrated changeset that was - /// downloaded from the upstream server, and including the implicit - /// registration of the root server's own file identifier (with value 1). If - /// it is not, this function shall return false. - /// - /// If the identifier was requested on behalf of a direct client (regular - /// client, subserver, or partial view), \a proxy_file_ident must be zero, - /// and \a client_type must be set to indicate the type of the direct - /// client. - /// - /// If the identifier was requested on behalf of a client of a direct - /// client, including on behalf of a client of a subserver, and on behalf of - /// a client of a partial view of this file, \a proxy_file_ident must - /// specify the direct client, and \a client_type must be set to - /// ClientType::indirect. - /// - /// When, and only when true is returned, \a file_ident_salt is set to the - /// salt that was assigned to the registered identifier in the context of - /// this file, and \a version_info is set to reflect the produced Realm - /// snapshot. - /// - /// This function must be called only on histories with upstream sync - /// status, or files that are initialized as partial views. - bool register_received_file_identifier(file_ident_type received_file_ident, file_ident_type proxy_file_ident, - ClientType client_type, salt_type& file_ident_salt, - sync::VersionInfo& version_info); - - /// FIXME: Fully document this function. - /// - /// \param version_info Will be set when this function returns true. Will be - /// left unmodified when this function returns false. - /// - /// \param backup_whole_realm Will be set to true when a change was made - /// that necessitates a full Realm backup (nonincremental). Otherwise it is - /// left unmodified. - /// - /// \param result Updatet to reflect the result of the integration - /// process. This happens regardless of whether the function returns true or - /// false. - /// - /// \return True when, and only when a new Realm version (snapshot) was - /// created. - bool integrate_client_changesets(const IntegratableChangesets&, sync::VersionInfo& version_info, - bool& backup_whole_realm, IntegrationResult& result, util::Logger&); - - /// EXPLAIN BACKUP incremental - auto integrate_backup_idents_and_changeset(version_type expected_realm_version, salt_type server_version_salt, - const FileIdentAllocSlots&, const std::vector&, - util::Logger&) -> IntegratedBackup; - - /// \brief Scan through the history for changesets to be downloaded. - /// - /// This function scans the history for changesets to be downloaded, i.e., - /// for changesets that are not empty, and were not produced by integration - /// of changesets recieved from the specified client file. The scan begins - /// at the position specified by the initial value of \a - /// download_progress.server_version, and ends no later than at the position - /// specified by \a end_version. - /// - /// The implementation is allowed to end the scan before \a end_version, - /// such as to limit the combined size of returned changesets. However, if - /// the specified range contains any changesets that are supposed to be - /// downloaded, this function must return at least one. - /// - /// Upon return, \a download_progress will have been updated to point to the - /// position from which the next scan should resume. This must be a position - /// after the last returned changeset, and before any remaining changesets - /// that are supposed to be downloaded, although never a position that - /// succeeds \a end_version. - /// - /// In each history entry passed to the specified handler, - /// sync::HistoryEntry::remote_version will have been replaced with the last - /// client version integrated prior to that entry, and for changesets of - /// local origin, `sync::HistoryEntry::origin_file_ident` will have been - /// replaced by the actual identifier for the local file. - /// - /// FIXME: Describe requirements on argument validity. - /// - /// \param upload_progress Set to refer to the last client version - /// integrated into the history. - /// - /// \param cumulative_byte_size_current is the cumulative byte size of all - /// changesets up to the end of the changesets fetched in this call. - /// - /// \param cumulative_byte_size_total is the cumulative byte size of - /// the entire history. - /// - /// \param accum_byte_size_soft_limit denotes a soft limit on the total size - /// of the uncompacted changesets. When the total size of the changesets - /// exceeds this limit, no more changesets will be added. - /// - /// \return False if the client file entry of the specified client file has - /// expired. Otherwise true. - bool fetch_download_info(file_ident_type client_file_ident, DownloadCursor& download_progress, - version_type end_version, UploadCursor& upload_progress, HistoryEntryHandler&, - std::uint_fast64_t& cumulative_byte_size_current, - std::uint_fast64_t& cumulative_byte_size_total, - std::size_t accum_byte_size_soft_limit = 0x20000) const; - - /// The application must call this function before using the history as an - /// upstream client history. - /// - /// This function must be called at most once for each Realm file. Use - /// get_status() to determine whether it has been called already. - /// - /// This function throws std::runtime_error if the history is nonempty or if - /// new client file identifiers have already been allocated from this file. - void add_upstream_sync_status(); - - /// Perform a transaction on the shared group associated with this - /// history. If the handler returns true, the transaction will be comitted, - /// and the version info will be set accordingly. If the handler returns - /// false, the transaction will be rolled back, and the version info will be - /// left unmodified. - /// - /// \return True if, and only if the handler retured true. - template - bool transact(H handler, sync::VersionInfo&); - - std::vector get_parsed_changesets(version_type begin, version_type end) const; - - // History inspection for debugging purposes and for testing the - // backup. - struct HistoryContents { - - struct ClientFile { - std::uint_fast64_t ident_salt; - std::uint_fast64_t client_version; - std::uint_fast64_t rh_base_version; - std::int_fast64_t proxy_file; - std::int_fast64_t client_type; - std::uint_fast64_t locked_server_version; - std::vector> reciprocal_history; - }; - - std::vector client_files; - - std::uint_fast64_t history_base_version; - std::uint_fast64_t base_version_salt; - - struct HistoryEntry { - std::uint_fast64_t version_salt; - std::uint_fast64_t client_file_ident; - std::uint_fast64_t client_version; - std::uint_fast64_t timestamp; - std::uint_fast64_t cumul_byte_size; - std::string changeset; - }; - - std::vector sync_history; - - std::uint_fast64_t servers_client_file_ident; - }; - - // The contents of the entire Realm is returned in a HistoryContents object. - // This function is used for testing the backup and for debugging. - HistoryContents get_history_contents() const; - - // FIXME: This function was not designed to be public. At least document the - // special conditions under which it can be called! - SaltedVersion get_salted_server_version() const noexcept; - - // Overriding member functions in Replication - void initialize(DB&) override; - HistoryType get_history_type() const noexcept override; - int get_history_schema_version() const noexcept override; - bool is_upgradable_history_schema(int) const noexcept override; - void upgrade_history_schema(int) override; - _impl::History* _get_history_write() override; - std::unique_ptr<_impl::History> _create_history_read() override; - - // Overriding member functions in Replication - version_type prepare_changeset(const char*, std::size_t, version_type) override; - - // Overriding member functions in _impl::History - void update_from_ref_and_version(ref_type ref, version_type) override; - void update_from_parent(version_type) override; - - void set_group(Group* group, bool updated = false) override - { - _impl::History::set_group(group, updated); - if (REALM_LIKELY(m_acc)) { - _impl::GroupFriend::set_history_parent(*m_group, m_acc->root); - } - - m_local_file_ident = group->get_sync_file_id(); - } - - // Overriding member functions in _impl::History - void get_changesets(version_type, version_type, BinaryIterator*) const noexcept override; - void set_oldest_bound_version(version_type) override; - void verify() const override; - -private: - // FIXME: Avoid use of optional type `std::int64_t` - using IntegerBpTree = BPlusTree; - - class ReciprocalHistory; - class TransformHistoryImpl; - class DiscardAccessorsGuard; - - // clang-format off - - // Sizes of fixed-size arrays - static constexpr int s_root_size = 11; - static constexpr int s_client_files_size = 8; - static constexpr int s_sync_history_size = 6; - static constexpr int s_upstream_status_size = 8; - static constexpr int s_partial_sync_size = 5; - static constexpr int s_schema_versions_size = 4; - - // Slots in root array of history compartment - static constexpr int s_client_files_iip = 0; // table ref - static constexpr int s_history_base_version_iip = 1; // version - static constexpr int s_base_version_salt_iip = 2; // salt - static constexpr int s_sync_history_iip = 3; // table ref - static constexpr int s_ct_history_iip = 4; // column ref - static constexpr int s_object_id_history_state_iip = 5; // ref - static constexpr int s_upstream_status_iip = 6; // optional array ref - static constexpr int s_partial_sync_iip = 7; // optional array ref - static constexpr int s_compacted_until_version_iip = 8; // version - static constexpr int s_last_compaction_timestamp_iip = 9; // UNIX timestamp (in seconds) - static constexpr int s_schema_versions_iip = 10; // ref - - // Slots in root array of `client_files` table - static constexpr int s_cf_ident_salts_iip = 0; // column ref - static constexpr int s_cf_client_versions_iip = 1; // column ref - static constexpr int s_cf_rh_base_versions_iip = 2; // column ref - static constexpr int s_cf_recip_hist_refs_iip = 3; // column ref - static constexpr int s_cf_proxy_files_iip = 4; // column ref - static constexpr int s_cf_client_types_iip = 5; // column ref - static constexpr int s_cf_last_seen_timestamps_iip = 6; // column ref (UNIX timestamps in seconds) - static constexpr int s_cf_locked_server_versions_iip = 7; // column ref - - // Slots in root array of `sync_history` table - static constexpr int s_sh_version_salts_iip = 0; // column ref - static constexpr int s_sh_origin_files_iip = 1; // column ref - static constexpr int s_sh_client_versions_iip = 2; // column ref - static constexpr int s_sh_timestamps_iip = 3; // column ref - static constexpr int s_sh_changesets_iip = 4; // column ref - static constexpr int s_sh_cumul_byte_sizes_iip = 5; // column_ref - - // Slots in `upstream_status` array - static constexpr int s_us_client_file_ident_iip = 0; // file ident - static constexpr int s_us_client_file_ident_salt_iip = 1; // salt - static constexpr int s_us_progress_latest_server_version_iip = 2; // version - static constexpr int s_us_progress_latest_server_version_salt_iip = 3; // salt - static constexpr int s_us_progress_download_server_version_iip = 4; // version - static constexpr int s_us_progress_download_client_version_iip = 5; // version - static constexpr int s_us_progress_upload_client_version_iip = 6; // version - static constexpr int s_us_progress_upload_server_version_iip = 7; // version - - // Slots in `partial_sync` array - static constexpr int s_ps_partial_file_ident_iip = 0; // file ident - static constexpr int s_ps_partial_file_ident_salt_iip = 1; // salt - static constexpr int s_ps_progress_partial_version_iip = 2; // version - static constexpr int s_ps_progress_reference_version_iip = 3; // version - static constexpr int s_ps_progress_reference_version_salt_iip = 4; // salt - - // Slots in root array of `schema_versions` table - static constexpr int s_sv_schema_versions_iip = 0; // integer - static constexpr int s_sv_library_versions_iip = 1; // ref - static constexpr int s_sv_snapshot_versions_iip = 2; // integer (version_type) - static constexpr int s_sv_timestamps_iip = 3; // integer (seconds since epoch) - - // clang-format on - - struct Accessors { - Array root; - Array client_files; // List of columns - Array sync_history; // List of columns - Array upstream_status; // Optional - Array partial_sync; // Optional - Array schema_versions; - - // Columns of Accessors::client_files - BPlusTree cf_ident_salts; - BPlusTree cf_client_versions; - BPlusTree cf_rh_base_versions; - BPlusTree cf_recip_hist_refs; - BPlusTree cf_proxy_files; - BPlusTree cf_client_types; - BPlusTree cf_last_seen_timestamps; - BPlusTree cf_locked_server_versions; - - // Columns of Accessors::sync_history - BPlusTree sh_version_salts; - BPlusTree sh_origin_files; - BPlusTree sh_client_versions; - BPlusTree sh_timestamps; - BinaryColumn sh_changesets; - BPlusTree sh_cumul_byte_sizes; - - // Continuous transactions history - BinaryColumn ct_history; - - Accessors(Allocator&) noexcept; - - void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept; - void init_from_ref(ref_type ref); - void create(); - - private: - void init_children(); - }; - - Context& m_context; - - // Salt to attach to new server versions (history entries) produced on - // behalf of this history object. The salt is allowed to differ between - // every server version, but for the purpose of compressibility (on the - // wire), it is best to use the same when we can. What matters, is that if - // the server state regresses (restore of backup), and a new server version - // is generated with the same numerical value as one that existed before the - // regression, then the two will have different salts attached to them (with - // a high probability). - salt_type m_salt_for_new_server_versions; - - DB* m_db = nullptr; - - version_type m_version_of_oldest_bound_snapshot = 0; - - // The identifier of the local Realm file. If this file is used on a subtier - // node of a star topology server cluster, the identifier is allocated in - // the context of a different Realm file. - // - // In a file on a subtier node of a star topology server cluster, that is - // not used as a partila view, it will be 1 until a file identifier is - // allocated. - // - // In a file that is not used as a partial view, and is not on a subtier - // node of a star topology server cluster, it is always equal to 1. - // - // It is never zero. - mutable file_ident_type m_local_file_ident; - - // Current number of client file entries (Array::client_files). A cache of - // `m_cf_ident_salts.size()`. - mutable std::size_t m_num_client_files; - - // Server version produced by the changeset associated with the last entry - // in the discarded prefix of the history (Array::sync_history), or zero if - // no entries were ever discarded. - mutable version_type m_history_base_version; - - // Current number of entries in the history (Array::sync_history). A cache - // of `m_sh_changesets->size()`. - mutable std::size_t m_history_size; - - // Salt associated with current server version (get_server_version()). - mutable salt_type m_server_version_salt; - - /// Realm version (snapshot number) on which the changeset associated with - /// the first entry in the continuous transactions history is based, or if - /// that history is empty, the version associated with the currently bound - /// snapshot. In general, the version associated with currently bound - /// snapshot is equal to `m_ct_base_version + m_ct_history_size`, but after - /// add_core_history_entry() is called, the snapshot version is equal to - /// `m_ct_base_version + m_ct_history_size - 1`. - mutable version_type m_ct_base_version; - - // Current number of entries in the continuous transaction history. A cache - // of `m_ct_history.size()`. - mutable std::size_t m_ct_history_size; - - // The construction of the array accessors need to be delayed, because the - // allocator (Allocator) is not known at the time of construction of the - // ServerHistory object. - mutable util::Optional m_acc; - - bool m_is_local_changeset = true; - - std::vector m_client_file_order_buffer; - - void discard_accessors() const noexcept; - void prepare_for_write(); - void create_empty_history(); - version_type get_server_version() const noexcept; - salt_type get_server_version_salt(version_type server_version) const noexcept; - bool is_valid_proxy_file_ident(file_ident_type) const noexcept; - void add_core_history_entry(BinaryData); - void add_sync_history_entry(const HistoryEntry&); - void trim_cont_transact_history(); - ChunkedBinaryData get_changeset(version_type server_version) const noexcept; - version_type find_history_entry(file_ident_type remote_file_ident, version_type begin_version, - version_type end_version, HistoryEntry&) const noexcept; - version_type find_history_entry(file_ident_type remote_file_ident, version_type begin_version, - version_type end_version, HistoryEntry&, - version_type& last_integrated_remote_version) const noexcept; - HistoryEntry get_history_entry(version_type server_version) const noexcept; - bool received_from(const HistoryEntry&, file_ident_type remote_file_ident) const noexcept; - - SaltedFileIdent allocate_file_ident(file_ident_type proxy_file_ident, ClientType); - void register_assigned_file_ident(file_ident_type file_ident); - bool try_register_file_ident(file_ident_type file_ident, file_ident_type proxy_file_ident, ClientType, - salt_type& file_ident_salt); - salt_type register_client_file_by_index(std::size_t file_index, file_ident_type proxy_file_ident, ClientType); - bool ensure_upstream_file_ident(file_ident_type file_ident); - void add_client_file(salt_type file_ident_salt, file_ident_type proxy_file_ident, ClientType); - void save_upstream_sync_progress(const SyncProgress&); - - BootstrapError do_bootstrap_client_session(SaltedFileIdent client_file_ident, DownloadCursor download_progress, - SaltedVersion server_version, ClientType client_type, - UploadCursor& upload_progress, version_type& locked_server_version, - util::Logger& logger) const noexcept; - - /// Behaviour is undefined if `last_integrated_local_version` of any - /// changeset is less than the value passed during any previous successful - /// invocation of this function. The application can use - /// validate_client_identity() to obtain the minimum value (as - /// upload_progress.last_integrated_local_version). - /// - /// Behaviour is undefined if `last_integrated_local_version` of any - /// changeset is greater than the current server version (as returned by - /// get_server_version()). - /// - /// \param remote_file_ident The identifier of the remote file from which - /// the changes were received, or zero if the changes were received from the - /// upstream server. It is an error to specify a client file whose entry in - /// the "client files" table is expired. - /// - /// \return True if, and only if changes were made to the Realm file (state - /// or history compartment). - /// - /// \throw sync::BadChangesetError If parsing of a changeset fails, or if - /// application of the parsed changeset fails due to a problem with the - /// changeset. - /// - /// \throw sync::TransformError If operational transformation of the - /// changeset fails due to a problem with the changeset. - /// - /// FIXME: Bad changesets should not cause exceptions to be thrown. Use - /// std::error_code instead. - bool integrate_remote_changesets(file_ident_type remote_file_ident, UploadCursor upload_progress, - version_type locked_server_version, const RemoteChangeset* changesets, - std::size_t num_changesets, util::Logger&); - - bool update_upload_progress(version_type orig_client_version, ReciprocalHistory& recip_hist, - UploadCursor upload_progress); - - void fixup_state_and_changesets_for_assigned_file_ident(Transaction&, file_ident_type); - - void record_current_schema_version(); - static void record_current_schema_version(Array& schema_versions, version_type snapshot_version); -}; - - -class ServerHistory::Context { -public: - virtual std::mt19937_64& server_history_get_random() noexcept = 0; - -protected: - Context() noexcept = default; -}; - - -std::ostream& operator<<(std::ostream& os, const ServerHistory::HistoryContents& hc); -bool operator==(const ServerHistory::HistoryContents&, const ServerHistory::HistoryContents&); - - -// Implementation - -constexpr bool ServerHistory::is_direct_client(ClientType client_type) noexcept -{ - switch (client_type) { - case ClientType::legacy: - case ClientType::regular: - case ClientType::subserver: - return true; - case ClientType::upstream: - case ClientType::indirect: - case ClientType::self: - break; - } - return false; -} - -inline ServerHistory::IntegratableChangeset::IntegratableChangeset(file_ident_type cfi, timestamp_type ot, - file_ident_type ofi, UploadCursor uc, - BinaryData c) noexcept - : client_file_ident{cfi} - , origin_timestamp{ot} - , origin_file_ident{ofi} - , upload_cursor(uc) - , changeset(c.data(), c.size()) -{ -} - -inline ServerHistory::ServerHistory(Context& context) - : m_context{context} -{ - // The synchronization protocol specification requires that server version - // salts are nonzero positive integers that fit in 63 bits. - std::mt19937_64& random = context.server_history_get_random(); - m_salt_for_new_server_versions = - std::uniform_int_distribution(1, 0x0'7FFF'FFFF'FFFF'FFFF)(random); -} - -template -bool ServerHistory::transact(H handler, sync::VersionInfo& version_info) -{ - auto wt = m_db->start_write(); // Throws - if (handler(*wt)) { // Throws - version_info.realm_version = wt->commit(); // Throws - version_info.sync_version = get_salted_server_version(); - return true; - } - return false; -} - -inline ServerHistory::Accessors::Accessors(Allocator& alloc) noexcept - : root{alloc} - , client_files{alloc} - , sync_history{alloc} - , upstream_status{alloc} - , partial_sync{alloc} - , schema_versions{alloc} - , cf_ident_salts{alloc} - , cf_client_versions{alloc} - , cf_rh_base_versions{alloc} - , cf_recip_hist_refs{alloc} - , cf_proxy_files{alloc} - , cf_client_types{alloc} - , cf_last_seen_timestamps{alloc} - , cf_locked_server_versions{alloc} - , sh_version_salts{alloc} - , sh_origin_files{alloc} - , sh_client_versions{alloc} - , sh_timestamps{alloc} - , sh_changesets{alloc} - , sh_cumul_byte_sizes{alloc} - , ct_history{alloc} -{ - client_files.set_parent(&root, s_client_files_iip); - sync_history.set_parent(&root, s_sync_history_iip); - upstream_status.set_parent(&root, s_upstream_status_iip); - partial_sync.set_parent(&root, s_partial_sync_iip); - schema_versions.set_parent(&root, s_schema_versions_iip); - - cf_ident_salts.set_parent(&client_files, s_cf_ident_salts_iip); - cf_client_versions.set_parent(&client_files, s_cf_client_versions_iip); - cf_rh_base_versions.set_parent(&client_files, s_cf_rh_base_versions_iip); - cf_recip_hist_refs.set_parent(&client_files, s_cf_recip_hist_refs_iip); - cf_proxy_files.set_parent(&client_files, s_cf_proxy_files_iip); - cf_client_types.set_parent(&client_files, s_cf_client_types_iip); - cf_last_seen_timestamps.set_parent(&client_files, s_cf_last_seen_timestamps_iip); - cf_locked_server_versions.set_parent(&client_files, s_cf_locked_server_versions_iip); - - sh_version_salts.set_parent(&sync_history, s_sh_version_salts_iip); - sh_origin_files.set_parent(&sync_history, s_sh_origin_files_iip); - sh_client_versions.set_parent(&sync_history, s_sh_client_versions_iip); - sh_timestamps.set_parent(&sync_history, s_sh_timestamps_iip); - sh_changesets.set_parent(&sync_history, s_sh_changesets_iip); - sh_cumul_byte_sizes.set_parent(&sync_history, s_sh_cumul_byte_sizes_iip); - - ct_history.set_parent(&root, s_ct_history_iip); -} - -inline void ServerHistory::Accessors::set_parent(ArrayParent* parent, size_t index) noexcept -{ - root.set_parent(parent, index); -} - -inline void ServerHistory::prepare_for_write() -{ - if (!m_acc) - create_empty_history(); // Throws - - REALM_ASSERT(m_acc->sh_changesets.is_attached()); - REALM_ASSERT(m_acc->root.size() == s_root_size); -} - -// Note: This function can be safely called during or after a transaction. -inline auto ServerHistory::get_server_version() const noexcept -> version_type -{ - return m_history_base_version + m_history_size; -} - -// Note: This function can be safely called during or after a transaction. -inline auto ServerHistory::get_salted_server_version() const noexcept -> SaltedVersion -{ - version_type version = get_server_version(); - return SaltedVersion{version, m_server_version_salt}; -} - -inline auto ServerHistory::find_history_entry(file_ident_type remote_file_ident, version_type begin_version, - version_type end_version, HistoryEntry& entry) const noexcept - -> version_type -{ - version_type last_integrated_remote_version; // Dummy - return find_history_entry(remote_file_ident, begin_version, end_version, entry, last_integrated_remote_version); -} - -} // namespace _impl -} // namespace realm - -#endif // REALM_NOINST_SERVER_HISTORY_HPP diff --git a/src/realm/sync/noinst/server/server_impl_base.hpp b/src/realm/sync/noinst/server/server_impl_base.hpp deleted file mode 100644 index 76e4b38fe46..00000000000 --- a/src/realm/sync/noinst/server/server_impl_base.hpp +++ /dev/null @@ -1,28 +0,0 @@ - -#ifndef REALM_NOINST_SERVER_IMPL_BASE_HPP -#define REALM_NOINST_SERVER_IMPL_BASE_HPP - -#include - -namespace realm { -namespace _impl { - -class ServerImplBase { -public: - static constexpr int get_oldest_supported_protocol_version() noexcept; -}; - -constexpr int ServerImplBase::get_oldest_supported_protocol_version() noexcept -{ - // See sync::get_current_protocol_version() for information about the - // individual protocol versions. - return 2; -} - -static_assert(ServerImplBase::get_oldest_supported_protocol_version() >= 1, ""); -static_assert(ServerImplBase::get_oldest_supported_protocol_version() <= sync::get_current_protocol_version(), ""); - -} // namespace _impl -} // namespace realm - -#endif // REALM_NOINST_SERVER_IMPL_BASE_HPP diff --git a/src/realm/sync/noinst/sync_metadata_schema.cpp b/src/realm/sync/noinst/sync_metadata_schema.cpp deleted file mode 100644 index 90b101041bb..00000000000 --- a/src/realm/sync/noinst/sync_metadata_schema.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/************************************************************************* - * - * Copyright 2022 Realm, Inc. - * - * 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 - -#include -#include -#include -#include - -namespace realm::sync { -namespace { - -constexpr static std::string_view c_flx_metadata_table("flx_metadata"); -constexpr static std::string_view c_sync_internal_schemas_table("sync_internal_schemas"); -constexpr static std::string_view c_meta_schema_version_field("schema_version"); -constexpr static std::string_view c_meta_schema_schema_group_field("schema_group_name"); - -} // namespace - -void create_sync_metadata_schema(Group& g, std::vector* tables) -{ - util::FlatMap found_tables; - for (auto& table : *tables) { - if (g.has_table(table.name)) { - throw RuntimeError( - ErrorCodes::RuntimeError, - util::format("table %1 already existed when creating internal tables for sync", table.name)); - } - TableRef table_ref; - if (table.is_embedded) { - table_ref = g.add_table(table.name, Table::Type::Embedded); - } - else if (table.pk_info) { - table_ref = g.add_table_with_primary_key(table.name, table.pk_info->data_type, table.pk_info->name, - table.pk_info->is_optional); - *table.pk_info->key_out = table_ref->get_primary_key_column(); - } - else { - table_ref = g.add_table(table.name); - } - - found_tables.insert({table.name, table_ref}); - *table.key_out = table_ref->get_key(); - } - - for (auto& table : *tables) { - auto& table_ref = found_tables.at(table.name); - for (auto& column : table.columns) { - if (column.data_type == type_Link) { - auto target_table_it = found_tables.find(column.target_table); - if (target_table_it == found_tables.end()) { - throw LogicError(ErrorCodes::InvalidArgument, - util::format("cannot link to non-existant table %1 from internal sync table %2", - column.target_table, table.name)); - } - if (column.is_list) { - *column.key_out = table_ref->add_column_list(*target_table_it->second, column.name); - } - else { - *column.key_out = table_ref->add_column(*target_table_it->second, column.name); - } - } - else { - *column.key_out = table_ref->add_column(column.data_type, column.name, column.is_optional); - } - } - } -} - -void load_sync_metadata_schema(const Group& g, std::vector* tables) -{ - if (auto status = try_load_sync_metadata_schema(g, tables); !status.is_ok()) { - throw Exception(std::move(status)); - } -} - -Status try_load_sync_metadata_schema(const Group& g, std::vector* tables) -{ - for (auto& table : *tables) { - auto table_ref = g.get_table(table.name); - if (!table_ref) { - return Status(ErrorCodes::RuntimeError, - util::format("could not find internal sync table %1", table.name)); - } - - *table.key_out = table_ref->get_key(); - if (table.pk_info) { - auto pk_col = table_ref->get_primary_key_column(); - if (auto pk_name = table_ref->get_column_name(pk_col); pk_name != table.pk_info->name) { - return Status( - ErrorCodes::RuntimeError, - util::format( - "primary key name of sync internal table %1 does not match (stored: %2, defined: %3)", - table.name, pk_name, table.pk_info->name)); - } - if (auto pk_type = table_ref->get_column_type(pk_col); pk_type != table.pk_info->data_type) { - return Status( - ErrorCodes::RuntimeError, - util::format( - "primary key type of sync internal table %1 does not match (stored: %2, defined: %3)", - table.name, pk_type, table.pk_info->data_type)); - } - if (auto is_nullable = table_ref->is_nullable(pk_col); is_nullable != table.pk_info->is_optional) { - return Status( - ErrorCodes::RuntimeError, - util::format( - "primary key nullabilty of sync internal table %1 does not match (stored: %2, defined: %3)", - table.name, is_nullable, table.pk_info->is_optional)); - } - *table.pk_info->key_out = pk_col; - } - else if (table.is_embedded && !table_ref->is_embedded()) { - return Status(ErrorCodes::RuntimeError, - util::format("internal sync table %1 should be embedded, but is not", table.name)); - } - - if (table.columns.size() + size_t(table.pk_info ? 1 : 0) != table_ref->get_column_count()) { - return Status( - ErrorCodes::RuntimeError, - util::format("sync internal table %1 has a different number of columns than its schema", table.name)); - } - - for (auto& col : table.columns) { - auto col_key = table_ref->get_column_key(col.name); - if (!col_key) { - return Status(ErrorCodes::RuntimeError, - util::format("column %1 is missing in sync internal table %2", col.name, table.name)); - } - - auto found_col_type = table_ref->get_column_type(col_key); - if (found_col_type != col.data_type) { - return Status( - ErrorCodes::RuntimeError, - util::format("column %1 in sync internal table %2 is the wrong type", col.name, table.name)); - } - - if (col.is_optional != table_ref->is_nullable(col_key)) { - return Status( - ErrorCodes::RuntimeError, - util::format("column %1 in sync internal table %2 has different nullabilty than in its schema", - col.name, table.name)); - } - - if (col.data_type == type_Link) { - if (table_ref->get_link_target(col_key)->get_name() != col.target_table) { - return Status(ErrorCodes::RuntimeError, - util::format("column %1 in sync internal table %2 links to the wrong table %3", - col.name, table.name, - table_ref->get_link_target(col_key)->get_name())); - } - } - *col.key_out = col_key; - } - } - return Status::OK(); -} - -SyncMetadataSchemaVersionsReader::SyncMetadataSchemaVersionsReader(const TransactionRef& tr) -{ - std::vector unified_schema_version_table_def{ - {&m_table, - c_sync_internal_schemas_table, - {&m_schema_group_field, c_meta_schema_schema_group_field, type_String}, - {{&m_version_field, c_meta_schema_version_field, type_Int}}}}; - - // Any type of transaction is allowed, including frozen and write, as long as it supports reading - REALM_ASSERT_EX(tr->get_transact_stage() != DB::transact_Ready, tr->get_transact_stage()); - // If the legacy_meta_table exists, then this table hasn't been converted and - // the metadata schema versions information has not been upgraded/not accurate - if (tr->has_table(c_flx_metadata_table)) { - return; - } - - if (tr->has_table(c_sync_internal_schemas_table)) { - // Load m_table with the table/schema information - load_sync_metadata_schema(*tr, &unified_schema_version_table_def); - } -} - -std::optional SyncMetadataSchemaVersionsReader::get_legacy_version(const TransactionRef& tr) -{ - if (!tr->has_table(c_flx_metadata_table)) { - return std::nullopt; - } - - TableKey legacy_table_key; - ColKey legacy_version_key; - std::vector legacy_table_def{ - {&legacy_table_key, c_flx_metadata_table, {{&legacy_version_key, c_meta_schema_version_field, type_Int}}}}; - - // Convert the legacy table to the regular schema versions table if it exists - load_sync_metadata_schema(*tr, &legacy_table_def); - - if (auto legacy_meta_table = tr->get_table(legacy_table_key); - legacy_meta_table && legacy_meta_table->size() > 0) { - auto legacy_obj = legacy_meta_table->get_object(0); - return legacy_obj.get(legacy_version_key); - } - - return std::nullopt; -} - -std::optional SyncMetadataSchemaVersionsReader::get_version_for(const TransactionRef& tr, - std::string_view schema_group_name) -{ - if (!m_table) { - // The legacy version only applies to the subscription store, don't query otherwise - if (schema_group_name == internal_schema_groups::c_flx_subscription_store) { - if (auto legacy_version = get_legacy_version(tr)) { - return legacy_version; - } - } - return util::none; - } - - auto schema_versions = tr->get_table(m_table); - auto obj_key = schema_versions->find_primary_key(Mixed{StringData(schema_group_name)}); - if (!obj_key) { - return util::none; - } - auto metadata_obj = schema_versions->get_object(obj_key); - if (!metadata_obj) { - return util::none; - } - - return metadata_obj.get(m_version_field); -} - -SyncMetadataSchemaVersions::SyncMetadataSchemaVersions(const TransactionRef& tr) - : SyncMetadataSchemaVersionsReader(tr) -{ - std::vector unified_schema_version_table_def{ - {&m_table, - c_sync_internal_schemas_table, - {&m_schema_group_field, c_meta_schema_schema_group_field, type_String}, - {{&m_version_field, c_meta_schema_version_field, type_Int}}}}; - - DB::TransactStage orig = tr->get_transact_stage(); - bool modified = false; - - // Read and write transactions are allowed, but not frozen - REALM_ASSERT_EX((orig == DB::transact_Reading || orig == DB::transact_Writing), orig); - // If the versions table exists, then m_table would have been initialized by the reader constructor - // If the versions table doesn't exist, then initialize it now - if (!m_table) { - // table should have already been initialized or needs to be created, - // but re-initialize in case it isn't (e.g. both unified and legacy tables exist in DB) - if (REALM_UNLIKELY(tr->has_table(c_sync_internal_schemas_table))) { - load_sync_metadata_schema(*tr, &unified_schema_version_table_def); - } - else { - // Only write the versions table if it doesn't exist - if (tr->get_transact_stage() != DB::transact_Writing) { - tr->promote_to_write(); - } - create_sync_metadata_schema(*tr, &unified_schema_version_table_def); - modified = true; - } - } - - if (auto legacy_version = get_legacy_version(tr)) { - // Migrate from just having a subscription store metadata table to having multiple table groups with multiple - // versions. - if (tr->get_transact_stage() != DB::transact_Writing) { - tr->promote_to_write(); - } - // Only the flx subscription store can potentially have the legacy metadata table - set_version_for(tr, internal_schema_groups::c_flx_subscription_store, *legacy_version); - tr->remove_table(c_flx_metadata_table); - modified = true; - } - if (!modified) - return; // nothing to commit - - // Commit and revert to the original transact stage - if (orig == DB::transact_Reading) - tr->commit_and_continue_as_read(); - else - tr->commit_and_continue_writing(); -} - -void SyncMetadataSchemaVersions::set_version_for(const TransactionRef& tr, std::string_view schema_group_name, - int64_t version) -{ - REALM_ASSERT(m_table); - - auto schema_versions = tr->get_table(m_table); - auto metadata_obj = schema_versions->create_object_with_primary_key(Mixed{StringData(schema_group_name)}); - metadata_obj.set(m_version_field, version); -} - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/sync_metadata_schema.hpp b/src/realm/sync/noinst/sync_metadata_schema.hpp deleted file mode 100644 index 35f7d547231..00000000000 --- a/src/realm/sync/noinst/sync_metadata_schema.hpp +++ /dev/null @@ -1,154 +0,0 @@ -/************************************************************************* - * - * Copyright 2022 Realm, Inc. - * - * 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 - -namespace realm { -class Group; -class Status; -class Transaction; -using TransactionRef = std::shared_ptr; -} // namespace realm - -namespace realm::sync { -namespace internal_schema_groups { -constexpr static std::string_view c_flx_subscription_store("flx_subscription_store"); -constexpr static std::string_view c_pending_bootstraps("pending_bootstraps"); -constexpr static std::string_view c_flx_migration_store("flx_migration_store"); -constexpr static std::string_view c_pending_reset_store("pending_reset_store"); -} // namespace internal_schema_groups - -/* - * The types in this file represent the schema for tables used internally by the sync client. They are similar - * to the Schema/ObjectSchema/Property types in object store, but are lighter weight and have no dependencies - * outside of core. - * - * The two functions for interacting with them are: - * create_sync_metadata_schema - which takes a write transaction and a group of tables, and creates them. - * load_sync_metadata_schema - validates and loads a group of tables. - * - * The SyncMetadataTable/SyncMetadataColumn classes each have an out parameter `key_out` that is the core key - * type for the table/column that will get updated when it's created/loaded/validated. - * - * If validation fails, load_sync_metadata_schema will throw a std::runtime_exception. - */ - -struct SyncMetadataColumn { - SyncMetadataColumn(ColKey* out, std::string_view name, DataType data_type, bool is_optional = false) - : key_out(out) - , name(name) - , data_type(data_type) - , is_optional(is_optional) - , is_list(false) - { - } - - SyncMetadataColumn(ColKey* out, std::string_view name, std::string_view target_table, bool is_list) - : key_out(out) - , name(name) - , data_type(type_Link) - , is_optional(is_list ? false : true) - , is_list(is_list) - , target_table(target_table) - { - } - - ColKey* key_out; - std::string_view name; - DataType data_type; - bool is_optional; - bool is_list; - std::string_view target_table; -}; - -struct SyncMetadataTable { - TableKey* key_out; - std::string_view name; - bool is_embedded; - util::Optional pk_info; - std::vector columns; - - struct IsEmbeddedTag {}; - SyncMetadataTable(TableKey* out, std::string_view name, IsEmbeddedTag, - std::initializer_list columns) - : key_out(out) - , name(name) - , is_embedded(true) - , pk_info(util::none) - , columns(std::move(columns)) - { - } - - SyncMetadataTable(TableKey* out, std::string_view name, SyncMetadataColumn pk_info, - std::initializer_list columns) - : key_out(out) - , name(name) - , is_embedded(false) - , pk_info(std::move(pk_info)) - , columns(std::move(columns)) - { - } - - SyncMetadataTable(TableKey* out, std::string_view name, std::initializer_list columns) - : key_out(out) - , name(name) - , is_embedded(false) - , pk_info(util::none) - , columns(std::move(columns)) - { - } -}; - - -void create_sync_metadata_schema(Group& g, std::vector* tables); -void load_sync_metadata_schema(const Group& g, std::vector* tables); -Status try_load_sync_metadata_schema(const Group& g, std::vector* tables); - -class SyncMetadataSchemaVersionsReader { -public: - explicit SyncMetadataSchemaVersionsReader(const TransactionRef& ref); - - std::optional get_version_for(const TransactionRef& tr, std::string_view schema_group_name); - - std::optional get_legacy_version(const TransactionRef& tr); - -protected: - TableKey m_table; - ColKey m_version_field; - ColKey m_schema_group_field; -}; - - -// SyncMetadataSchemas manages the schema version numbers for different groups of internal tables used -// within sync. -class SyncMetadataSchemaVersions : public SyncMetadataSchemaVersionsReader { -public: - explicit SyncMetadataSchemaVersions(const TransactionRef& ref); - - void set_version_for(const TransactionRef& tr, std::string_view schema_group_name, int64_t version); -}; - -} // namespace realm::sync diff --git a/src/realm/sync/noinst/sync_schema_migration.cpp b/src/realm/sync/noinst/sync_schema_migration.cpp deleted file mode 100644 index 42e73898a72..00000000000 --- a/src/realm/sync/noinst/sync_schema_migration.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/************************************************************************* - * - * Copyright 2023 Realm, Inc. - * - * 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 -#include -#include - -#include - -using namespace realm; -using namespace _impl; - -namespace realm::_impl::sync_schema_migration { - -// A table without a "class_" prefix will not generate sync instructions. -constexpr static std::string_view s_meta_schema_migration_table_name("schema_migration_metadata"); -constexpr static std::string_view s_pk_col_name("id"); -constexpr static std::string_view s_version_column_name("version"); -constexpr static std::string_view s_timestamp_col_name("event_time"); -constexpr static std::string_view s_previous_schema_version_col_name("previous_schema_version"); -constexpr int64_t metadata_version = 1; - -std::optional has_pending_migration(const Transaction& rt) -{ - ConstTableRef table = rt.get_table(s_meta_schema_migration_table_name); - if (!table || table->size() == 0) { - return none; - } - ColKey timestamp_col = table->get_column_key(s_timestamp_col_name); - ColKey version_col = table->get_column_key(s_version_column_name); - ColKey previous_schema_version_col = table->get_column_key(s_previous_schema_version_col_name); - REALM_ASSERT(timestamp_col); - REALM_ASSERT(version_col); - REALM_ASSERT(previous_schema_version_col); - - Obj first = *table->begin(); - REALM_ASSERT(first); - auto version = first.get(version_col); - auto time = first.get(timestamp_col); - if (version != metadata_version) { - throw SyncSchemaMigrationFailed( - util::format("Unsupported sync schema migration metadata version: %1 vs %2, from %3", version, - metadata_version, time)); - } - return first.get(previous_schema_version_col); -} - -void track_sync_schema_migration(Transaction& wt, uint64_t previous_schema_version) -{ - TableRef table = wt.get_table(s_meta_schema_migration_table_name); - ColKey version_col, timestamp_col, previous_schema_version_col; - if (!table) { - table = wt.add_table_with_primary_key(s_meta_schema_migration_table_name, type_ObjectId, s_pk_col_name); - REALM_ASSERT(table); - version_col = table->add_column(type_Int, s_version_column_name); - timestamp_col = table->add_column(type_Timestamp, s_timestamp_col_name); - previous_schema_version_col = table->add_column(type_Int, s_previous_schema_version_col_name); - } - else { - version_col = table->get_column_key(s_version_column_name); - timestamp_col = table->get_column_key(s_timestamp_col_name); - previous_schema_version_col = table->get_column_key(s_previous_schema_version_col_name); - } - REALM_ASSERT(version_col); - REALM_ASSERT(timestamp_col); - REALM_ASSERT(previous_schema_version_col); - - // A migration object may exist if the migration was started in a previous session. - if (table->is_empty()) { - table->create_object_with_primary_key(ObjectId::gen(), - {{version_col, metadata_version}, - {timestamp_col, Timestamp(std::chrono::system_clock::now())}, - {previous_schema_version_col, int64_t(previous_schema_version)}}); - } - else { - auto first = *table->begin(); - auto version = first.get(version_col); - auto time = first.get(timestamp_col); - if (version != metadata_version) { - throw SyncSchemaMigrationFailed( - util::format("Unsupported sync schema migration metadata version: %1 vs %2, from %3", version, - metadata_version, time)); - } - uint64_t schema_version = first.get(previous_schema_version_col); - if (schema_version != previous_schema_version) { - throw SyncSchemaMigrationFailed( - util::format("Cannot continue sync schema migration with different previous schema version (existing " - "previous_schema_version=%1, new previous_schema_version=%2)", - schema_version, previous_schema_version)); - } - } -} - -void perform_schema_migration(DB& db) -{ - // Everything is performed in one single write transaction. - auto tr = db.start_write(); - - // Disable sync replication. - auto& repl = dynamic_cast(*db.get_replication()); - sync::TempShortCircuitReplication sync_history_guard(repl); - repl.set_write_validator_factory(nullptr); - - // Delete all columns before deleting tables to avoid complications with links - for (auto tk : tr->get_table_keys()) { - tr->get_table(tk)->remove_columns(); - } - for (auto tk : tr->get_table_keys()) { - tr->remove_table(tk); - } - - // Clear sync history, reset the file ident and the server version in the download and upload progress. - - auto& history = repl.get_history(); - sync::SaltedFileIdent reset_file_ident{0, 0}; - sync::SaltedVersion reset_server_version{0, 0}; - std::vector<_impl::client_reset::RecoveredChange> changes{}; - history.set_history_adjustments(*db.get_logger(), tr->get_version(), reset_file_ident, reset_server_version, - changes); - - tr->commit(); -} - -} // namespace realm::_impl::sync_schema_migration diff --git a/src/realm/sync/noinst/sync_schema_migration.hpp b/src/realm/sync/noinst/sync_schema_migration.hpp deleted file mode 100644 index bed44dbed17..00000000000 --- a/src/realm/sync/noinst/sync_schema_migration.hpp +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************************************* - * - * Copyright 2023 Realm, Inc. - * - * 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 - -namespace realm { -namespace _impl::sync_schema_migration { - -std::optional has_pending_migration(const Transaction& rt); - -void track_sync_schema_migration(Transaction& wt, uint64_t previous_schema_version); - -void perform_schema_migration(DB& db); - -} // namespace _impl::sync_schema_migration -} // namespace realm \ No newline at end of file diff --git a/src/realm/sync/object_id.cpp b/src/realm/sync/object_id.cpp deleted file mode 100644 index c934276b0a4..00000000000 --- a/src/realm/sync/object_id.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include // std::isxdigit -#include // strtoull - -using namespace realm; -using namespace realm::sync; - -std::ostream& realm::sync::operator<<(std::ostream& os, format_pk fmt) -{ - const auto& key = fmt.pk; - auto formatter = util::overload{ - [&](mpark::monostate) { - os << "NULL"; - }, - [&](int64_t x) { - os << "Int(" << x << ")"; - }, - [&](StringData x) { - os << "\"" << x << "\""; - }, - [&](GlobalKey x) { - os << "GlobalKey{" << x << "}"; - }, - [&](ObjectId x) { - os << "ObjectId{" << x << "}"; - }, - [&](UUID x) { - os << "UUID{" << x << "}"; - }, - }; - mpark::visit(formatter, key); - return os; -} - -void ObjectIDSet::insert(StringData table, const PrimaryKey& object_id) -{ - m_objects[table].insert(object_id); -} - -void ObjectIDSet::erase(StringData table, const PrimaryKey& object_id) -{ - auto search = m_objects.find(table); - if (search != m_objects.end()) { - auto& single_table_ids = search->second; - single_table_ids.erase(object_id); - if (single_table_ids.empty()) - m_objects.erase(table); - } -} - -bool ObjectIDSet::contains(StringData table, const PrimaryKey& object_id) const noexcept -{ - auto search = m_objects.find(table); - if (search != m_objects.end()) { - const auto& single_table_ids = search->second; - return single_table_ids.find(object_id) != single_table_ids.end(); - } - return false; -} - -void FieldSet::insert(StringData table, StringData column, const PrimaryKey& object_id) -{ - m_fields[table][column].insert(object_id); -} - -void FieldSet::erase(StringData table, StringData column, const PrimaryKey& object_id) -{ - auto search_1 = m_fields.find(table); - if (search_1 != m_fields.end()) { - auto& single_table_fields = search_1->second; - auto search_2 = single_table_fields.find(column); - if (search_2 != single_table_fields.end()) { - auto& single_field_ids = search_2->second; - single_field_ids.erase(object_id); - if (single_field_ids.empty()) { - single_table_fields.erase(column); - if (single_table_fields.empty()) { - m_fields.erase(table); - } - } - } - } -} - -bool FieldSet::contains(StringData table, const PrimaryKey& object_id) const noexcept -{ - auto search_1 = m_fields.find(table); - if (search_1 == m_fields.end()) - return false; - - const auto& single_table_fields = search_1->second; - for (const auto& kv : single_table_fields) { - const auto& single_field_ids = kv.second; - if (single_field_ids.find(object_id) != single_field_ids.end()) - return true; - } - return false; -} - -bool FieldSet::contains(StringData table, StringData column, const PrimaryKey& object_id) const noexcept -{ - auto search_1 = m_fields.find(table); - if (search_1 == m_fields.end()) - return false; - - const auto& single_table_fields = search_1->second; - auto search_2 = single_table_fields.find(column); - if (search_2 == single_table_fields.end()) - return false; - - const auto& single_field_ids = search_2->second; - return single_field_ids.find(object_id) != single_field_ids.end(); -} diff --git a/src/realm/sync/object_id.hpp b/src/realm/sync/object_id.hpp deleted file mode 100644 index 8f1a1fae2db..00000000000 --- a/src/realm/sync/object_id.hpp +++ /dev/null @@ -1,128 +0,0 @@ -/************************************************************************* - * - * Copyright 2017 Realm Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef REALM_SYNC_OBJECT_ID_HPP -#define REALM_SYNC_OBJECT_ID_HPP - -#include // std::hash -#include -#include // operator<< -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace realm { - -class Group; - -namespace sync { - -// Any unambiguous object identifier. Monostate represents NULL (can't use realm::None or std::nullptr_t because they -// do not implement operator<). -using PrimaryKey = mpark::variant; - -/// FIXME: Since PrimaryKey is a typedef to an `std` type, ADL for operator<< -/// doesn't work properly. This struct exists solely for passing PrimaryKey -/// instances to various output streams. -struct format_pk { - const PrimaryKey& pk; - explicit format_pk(const PrimaryKey& pk) - : pk(pk) - { - } -}; -std::ostream& operator<<(std::ostream& os, format_pk); - - -// ObjectIDSet is a set of (table name, object id) -class ObjectIDSet { -public: - void insert(StringData table, const PrimaryKey& object_id); - void erase(StringData table, const PrimaryKey& object_id); - bool contains(StringData table, const PrimaryKey& object_id) const noexcept; - bool empty() const noexcept; - - // A map from table name to a set of object ids. - std::map> m_objects; -}; - -// FieldSet is a set of fields in tables. A field is defined by a -// table name, a column in the table and an object id for the row. -class FieldSet { -public: - void insert(StringData table, StringData column, const PrimaryKey& object_id); - void erase(StringData table, StringData column, const PrimaryKey& object_id); - bool contains(StringData table, const PrimaryKey& object_id) const noexcept; - bool contains(StringData table, StringData column, const PrimaryKey& object_id) const noexcept; - bool empty() const noexcept; - - // A map from table name to a map from column name to a set of - // object ids. - std::map>> m_fields; -}; - -struct GlobalID { - StringData table_name; - PrimaryKey object_id; - - bool operator==(const GlobalID& other) const; - bool operator!=(const GlobalID& other) const; - bool operator<(const GlobalID& other) const; -}; - -/// Implementation: - - -inline bool GlobalID::operator==(const GlobalID& other) const -{ - return object_id == other.object_id && table_name == other.table_name; -} - -inline bool GlobalID::operator!=(const GlobalID& other) const -{ - return !(*this == other); -} - -inline bool GlobalID::operator<(const GlobalID& other) const -{ - if (table_name == other.table_name) - return object_id < other.object_id; - return table_name < other.table_name; -} - -inline bool ObjectIDSet::empty() const noexcept -{ - return m_objects.empty(); -} - -inline bool FieldSet::empty() const noexcept -{ - return m_fields.empty(); -} - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_OBJECT_ID_HPP diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp deleted file mode 100644 index 525604f7853..00000000000 --- a/src/realm/sync/protocol.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include - - -namespace realm::sync { - -const char* get_protocol_error_message(int error_code) noexcept -{ - // FIXME: These human-readable messages are phrased from the perspective of the client, but they may occur on the - // server side as well. - - switch (ProtocolError(error_code)) { - case ProtocolError::connection_closed: - return "Connection closed (no error)"; - case ProtocolError::other_error: - return "Other connection level error"; - case ProtocolError::unknown_message: - return "Unknown type of input message"; - case ProtocolError::bad_syntax: - return "Bad syntax in input message head"; - case ProtocolError::limits_exceeded: - return "Limits exceeded in input message"; - case ProtocolError::wrong_protocol_version: - return "Wrong protocol version (CLIENT)"; - case ProtocolError::bad_session_ident: - return "The server has forgotten about this session (Bad session identifier in input message). " - "Restart the client to resume synchronization"; - case ProtocolError::reuse_of_session_ident: - return "An existing synchronization session exists with this session identifier (Overlapping reuse of " - "session identifier (BIND))."; - case ProtocolError::bound_in_other_session: - return "An existing synchronization session exists for this client-side file (Client file bound in other " - "session (IDENT))"; - case ProtocolError::bad_message_order: - return "Bad input message order"; - case ProtocolError::bad_decompression: - return "The server sent an invalid DOWNLOAD message (Bad decompression of message)"; - case ProtocolError::bad_changeset_header_syntax: - return "The server sent an invalid DOWNLOAD message (Bad changeset header syntax)"; - case ProtocolError::bad_changeset_size: - return "The server sent an invalid DOWNLOAD message (Bad changeset size)"; - case ProtocolError::switch_to_flx_sync: - return "Wrong wire protocol, switch to the flexible sync wire protocol"; - case ProtocolError::switch_to_pbs: - return "Wrong wire protocol, switch to the partition-based sync wire protocol"; - - case ProtocolError::session_closed: - return "Session closed (no error)"; - case ProtocolError::other_session_error: - return "Other session level error"; - case ProtocolError::token_expired: - return "Access token expired"; - case ProtocolError::bad_authentication: - return "Bad user authentication (BIND)"; - case ProtocolError::illegal_realm_path: - return "Illegal Realm path (BIND)"; - case ProtocolError::no_such_realm: - return "No such Realm (BIND)"; - case ProtocolError::permission_denied: - return "Permission denied (BIND)"; - case ProtocolError::bad_server_file_ident: - return "The server sent an obsolete error code (Bad server file identifier (IDENT))"; - case ProtocolError::bad_client_file_ident: - return "The server has forgotten about this client-side file (Bad client file identifier (IDENT)). " - "Please wipe the file on the client to resume synchronization"; - case ProtocolError::bad_server_version: - return "The client is ahead of the server (Bad server version (IDENT, UPLOAD)). Please wipe the file on " - "the client to resume synchronization"; - case ProtocolError::bad_client_version: - return "The server claimed to have received changesets from this client that the client has not produced " - "yet (Bad client version (IDENT, UPLOAD)). Please wipe the file on the client to resume " - "synchronization"; - case ProtocolError::diverging_histories: - return "The client and server disagree about the history (Diverging histories (IDENT)). Please wipe the " - "file on the client to resume synchronization"; - case ProtocolError::bad_changeset: - return "The server sent a changeset that could not be integrated (Bad changeset (UPLOAD, ERROR)). This " - "is likely due to corruption of the client-side file. Please restore the file on the client by " - "wiping it and resuming synchronization"; - case ProtocolError::partial_sync_disabled: - return "Query-based sync is disabled"; - case ProtocolError::unsupported_session_feature: - return "Unsupported session-level feature"; - case ProtocolError::bad_origin_file_ident: - return "The server sent an obsolete error code (Bad origin file identifier (UPLOAD))"; - case ProtocolError::bad_client_file: - return "Synchronization no longer possible for client-side file. Please wipe the file on the client to " - "resume synchronization"; - case ProtocolError::server_file_deleted: - return "Server file was deleted while a session was bound to it"; - case ProtocolError::client_file_blacklisted: - return "Client file has been blacklisted (IDENT)"; - case ProtocolError::user_blacklisted: - return "User has been blacklisted (BIND)"; - case ProtocolError::transact_before_upload: - return "The server sent an obsolete error code (Serialized transaction before upload completion)"; - case ProtocolError::client_file_expired: - return "Client file has expired due to log compaction. Please wipe the file on the client to resume " - "synchronization"; - case ProtocolError::user_mismatch: - return "User mismatch for client file identifier (IDENT)"; - case ProtocolError::too_many_sessions: - return "Too many sessions in connection (BIND)"; - case ProtocolError::invalid_schema_change: - return "Invalid schema change (UPLOAD)"; - case ProtocolError::bad_query: - return "Client query is invalid/malformed (IDENT, QUERY)"; - case ProtocolError::object_already_exists: - return "Client tried to create an object that already exists outside their view (UPLOAD)"; - case ProtocolError::server_permissions_changed: - return "Server permissions for this file ident have changed since the last time it was used (IDENT)"; - case ProtocolError::initial_sync_not_completed: - return "Client tried to open a session before initial sync is complete (BIND)"; - case ProtocolError::write_not_allowed: - return "Client attempted a write that is disallowed by permissions, or modifies an object outside the " - "current query - requires client reset"; - case ProtocolError::compensating_write: - return "Client attempted a write that is disallowed by permissions, or modifies an object outside the " - "current query, and the server undid the change"; - case ProtocolError::migrate_to_flx: - return "Server migrated to flexible sync - migrating client to use flexible sync"; - case ProtocolError::bad_progress: - return "Bad progress information (DOWNLOAD)"; - case ProtocolError::revert_to_pbs: - return "Server rolled back after flexible sync migration - reverting client to partition based " - "sync"; - case ProtocolError::bad_schema_version: - return "Client tried to open a session with an invalid schema version (BIND)"; - case ProtocolError::schema_version_changed: - return "Client opened a session with a new valid schema version - migrating client to use new schema " - "version (BIND)"; - } - return nullptr; -} - -std::ostream& operator<<(std::ostream& os, ProtocolError error) -{ - if (auto str = get_protocol_error_message(static_cast(error))) { - return os << str; - } - return os << "Unknown protocol error " << static_cast(error); -} - -Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) -{ - auto translated_error_code = [&] { - switch (error_code) { - case ProtocolError::connection_closed: - return ErrorCodes::ConnectionClosed; - case ProtocolError::other_error: - return ErrorCodes::RuntimeError; - case ProtocolError::unknown_message: - [[fallthrough]]; - case ProtocolError::bad_syntax: - [[fallthrough]]; - case ProtocolError::wrong_protocol_version: - [[fallthrough]]; - case ProtocolError::bad_session_ident: - [[fallthrough]]; - case ProtocolError::reuse_of_session_ident: - [[fallthrough]]; - case ProtocolError::bound_in_other_session: - [[fallthrough]]; - case ProtocolError::bad_changeset_header_syntax: - [[fallthrough]]; - case ProtocolError::bad_changeset_size: - [[fallthrough]]; - case ProtocolError::bad_message_order: - return ErrorCodes::SyncProtocolInvariantFailed; - case ProtocolError::bad_decompression: - return ErrorCodes::RuntimeError; - case ProtocolError::switch_to_flx_sync: - [[fallthrough]]; - case ProtocolError::switch_to_pbs: - return ErrorCodes::WrongSyncType; - - case ProtocolError::session_closed: - return ErrorCodes::ConnectionClosed; - case ProtocolError::other_session_error: - return ErrorCodes::RuntimeError; - case ProtocolError::illegal_realm_path: - return ErrorCodes::BadSyncPartitionValue; - case ProtocolError::permission_denied: - return ErrorCodes::SyncPermissionDenied; - case ProtocolError::bad_client_file_ident: - [[fallthrough]]; - case ProtocolError::bad_server_version: - [[fallthrough]]; - case ProtocolError::bad_client_version: - [[fallthrough]]; - case ProtocolError::diverging_histories: - [[fallthrough]]; - case ProtocolError::client_file_expired: - [[fallthrough]]; - case ProtocolError::bad_client_file: - return ErrorCodes::SyncClientResetRequired; - case ProtocolError::bad_changeset: - return ErrorCodes::BadChangeset; - case ProtocolError::bad_origin_file_ident: - return ErrorCodes::SyncProtocolInvariantFailed; - case ProtocolError::user_mismatch: - return ErrorCodes::SyncUserMismatch; - case ProtocolError::invalid_schema_change: - return ErrorCodes::InvalidSchemaChange; - case ProtocolError::bad_query: - return ErrorCodes::InvalidSubscriptionQuery; - case ProtocolError::object_already_exists: - return ErrorCodes::ObjectAlreadyExists; - case ProtocolError::server_permissions_changed: - return ErrorCodes::SyncServerPermissionsChanged; - case ProtocolError::initial_sync_not_completed: - return ErrorCodes::ConnectionClosed; - case ProtocolError::write_not_allowed: - return ErrorCodes::SyncWriteNotAllowed; - case ProtocolError::compensating_write: - return ErrorCodes::SyncCompensatingWrite; - case ProtocolError::bad_progress: - return ErrorCodes::SyncProtocolInvariantFailed; - case ProtocolError::migrate_to_flx: - [[fallthrough]]; - case ProtocolError::revert_to_pbs: - return ErrorCodes::WrongSyncType; - case ProtocolError::bad_schema_version: - [[fallthrough]]; - case ProtocolError::schema_version_changed: - return ErrorCodes::SyncSchemaMigrationError; - - case ProtocolError::limits_exceeded: - [[fallthrough]]; - case ProtocolError::token_expired: - [[fallthrough]]; - case ProtocolError::bad_authentication: - [[fallthrough]]; - case ProtocolError::no_such_realm: - [[fallthrough]]; - case ProtocolError::bad_server_file_ident: - [[fallthrough]]; - case ProtocolError::partial_sync_disabled: - [[fallthrough]]; - case ProtocolError::unsupported_session_feature: - [[fallthrough]]; - case ProtocolError::too_many_sessions: - [[fallthrough]]; - case ProtocolError::server_file_deleted: - [[fallthrough]]; - case ProtocolError::client_file_blacklisted: - [[fallthrough]]; - case ProtocolError::user_blacklisted: - [[fallthrough]]; - case ProtocolError::transact_before_upload: - REALM_UNREACHABLE(); - } - return ErrorCodes::UnknownError; - }(); - - if (translated_error_code == ErrorCodes::UnknownError) { - return {ErrorCodes::UnknownError, - util::format("Unknown sync protocol error code %1: %2", static_cast(error_code), msg)}; - } - return {translated_error_code, msg}; -} - -} // namespace realm::sync diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp deleted file mode 100644 index eab452bdf2f..00000000000 --- a/src/realm/sync/protocol.hpp +++ /dev/null @@ -1,527 +0,0 @@ -#ifndef REALM_SYNC_PROTOCOL_HPP -#define REALM_SYNC_PROTOCOL_HPP - -#include -#include - -#include -#include -#include -#include - - -// NOTE: The protocol specification is in `/doc/protocol.md` - - -namespace realm { -namespace sync { - -// Protocol versions: -// -// 1 Initial version, matching io.realm.sync-30, but not including query-based -// sync, serialized transactions, and state realms (async open). -// -// 2 Restored erase-always-wins OT behavior. -// -// 3 Support for Mixed, TypeLinks, Set, and Dictionary columns. -// -// 4 Error messaging format accepts a flexible JSON field in JSON_ERROR. -// JSONErrorMessage.IsClientReset controls recovery mode. -// -// 5 Introduces compensating write errors. -// -// 6 Support for asymmetric tables. -// -// 7 Client takes the 'action' specified in the JSON_ERROR messages received -// from server. Client sends JSON_ERROR messages to the server. -// -// 8 Websocket http errors are now sent as websocket close codes -// FLX sync BIND message can include JSON data in place of server path string -// Updated format for Sec-Websocket-Protocol strings -// -// 9 Support for PBS->FLX client migration -// Client reset updated to not provide the local schema when creating frozen -// realms - this informs the server to not send the schema before sending the -// migrate to FLX server action -// -// 10 Update BIND message to send information to the server about the reason a -// synchronization session is used for; add support for server log messages -// -// 11 Support for FLX schema migrations -// Update BIND message to send information to the server about the schema -// version a synchronized realm is opened with -// Update JSON_ERROR message to read the previous schema version sent by -// the server -// -// 12 Support for estimated progress in DOWNLOAD message for FLX -// Server replaces 'downloadable_bytes' (which was always zero prior this version) -// with an estimated progress value (double from 0.0 to 1.0) for flx sessions -// -// 13 Support for syncing collections (lists and dictionaries) in Mixed columns and -// collections of Mixed -// -// 14 Support for server initiated bootstraps, including bootstraps for role/ -// permissions changes instead of performing a client reset when changed. -// -// XX Changes: -// - TBD -// -constexpr int get_current_protocol_version() noexcept -{ - // Also update the "flx: verify websocket protocol number and prefixes" test - // in flx_sync.cpp when updating this value - return 14; -} - -constexpr std::string_view get_pbs_websocket_protocol_prefix() noexcept -{ - return "com.mongodb.realm-sync#"; -} - -constexpr std::string_view get_flx_websocket_protocol_prefix() noexcept -{ - return "com.mongodb.realm-query-sync#"; -} - -enum class SyncServerMode { PBS, FLX }; - -/// Supported protocol envelopes: -/// -/// Alternative (*) -/// Name Envelope URL scheme Default port default port -/// ------------------------------------------------------------------------ -/// realm WebSocket realm: 7800 80 -/// realms WebSocket + SSL realms: 7801 443 -/// ws WebSocket ws: 80 -/// wss WebSocket + SSL wss: 443 -/// -/// *) When Client::Config::enable_default_port_hack is true -/// -enum class ProtocolEnvelope { realm, realms, ws, wss }; - -inline bool is_ssl(ProtocolEnvelope protocol) noexcept -{ - switch (protocol) { - case ProtocolEnvelope::realm: - case ProtocolEnvelope::ws: - break; - case ProtocolEnvelope::realms: - case ProtocolEnvelope::wss: - return true; - } - return false; -} - -inline std::string_view to_string(ProtocolEnvelope protocol) noexcept -{ - switch (protocol) { - case ProtocolEnvelope::realm: - return "realm://"; - case ProtocolEnvelope::realms: - return "realms://"; - case ProtocolEnvelope::ws: - return "ws://"; - case ProtocolEnvelope::wss: - return "wss://"; - } - return ""; -} - - -// These integer types are selected so that they accomodate the requirements of -// the protocol specification (`/doc/protocol.md`). -// -// clang-format off -using file_ident_type = std::uint_fast64_t; -using version_type = Replication::version_type; -using salt_type = std::int_fast64_t; -using timestamp_type = std::uint_fast64_t; -using session_ident_type = std::uint_fast64_t; -using request_ident_type = std::uint_fast64_t; -using milliseconds_type = std::int_fast64_t; -// clang-format on - -constexpr file_ident_type get_max_file_ident() -{ - return 0x0'7FFF'FFFF'FFFF'FFFF; -} - - -struct SaltedFileIdent { - file_ident_type ident; - /// History divergence and identity spoofing protection. - salt_type salt; -}; - -struct SaltedVersion { - version_type version; - /// History divergence protection. - salt_type salt; -}; - - -/// \brief A client's reference to a position in the server-side history. -/// -/// A download cursor refers to a position in the server-side history. If -/// `server_version` is zero, the position is at the beginning of the history, -/// otherwise the position is after the entry whose changeset produced that -/// version. In general, positions are to be understood as places between two -/// adjacent history entries. -/// -/// `last_integrated_client_version` is the version produced on the client by -/// the last changeset that was sent to the server and integrated into the -/// server-side Realm state at the time indicated by the history position -/// specified by `server_version`, or zero if no changesets from the client were -/// integrated by the server at that point in time. -struct DownloadCursor { - version_type server_version; - version_type last_integrated_client_version; -}; - -enum class DownloadBatchState { - MoreToCome = 0, - LastInBatch = 1, - SteadyState = 2, -}; - -/// Download progress information comes from the server either in the form of -/// remaining bytes to download (for PBS) or an estimate of how much has been -/// downloaded ranging from 0.0 to 1.0 (for FLX). Both of these need to be -/// stored in an integer array in the client history, which for the download -/// progress we do by converting to a fixed point integer. We always know from -/// context how to interpret this value, so there is no need to store a tag -/// indicating which it is. -class DownloadableProgress { -public: - DownloadableProgress() = default; - DownloadableProgress(double p) - : m_value(uint64_t(p * 10000)) - { - REALM_ASSERT(p >= 0 && p <= 1); - } - DownloadableProgress(uint64_t p) - : m_value(p) - { - } - DownloadableProgress(int p) - : m_value(uint64_t(p)) - { - REALM_ASSERT(p >= 0); - } - - /// Read the stored value as a FLX progress estimate - double as_estimate() const noexcept - { - return double(m_value) / 10000.0; - } - /// Read the stored value as a number of bytes waiting to be downloaded - uint64_t as_bytes() const noexcept - { - return m_value; - } - -private: - uint64_t m_value; -}; -static_assert(std::is_trivial_v); - -/// Checks that `dc.last_integrated_client_version` is zero if -/// `dc.server_version` is zero. -bool is_consistent(DownloadCursor dc) noexcept; - -/// Checks that `a.last_integrated_client_version` and -/// `b.last_integrated_client_version` are equal, if `a.server_version` and -/// `b.server_version` are equal. Otherwise checks that -/// `a.last_integrated_client_version` is less than, or equal to -/// `b.last_integrated_client_version`, if `a.server_version` is less than -/// `b.server_version`. Otherwise checks that `a.last_integrated_client_version` -/// is greater than, or equal to `b.last_integrated_client_version`. -bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept; - - -/// \brief The server's reference to a position in the client-side history. -/// -/// An upload cursor refers to a position in the client-side history. If -/// `client_version` is zero, the position is at the beginning of the history, -/// otherwise the position is after the entry whose changeset produced that -/// version. In general, positions are to be understood as places between two -/// adjacent history entries. -/// -/// `last_integrated_server_version` is the version produced on the server by -/// the last changeset that was sent to the client and integrated into the -/// client-side Realm state at the time indicated by the history position -/// specified by `client_version`, or zero if no changesets from the server were -/// integrated by the client at that point in time. -struct UploadCursor { - version_type client_version; - version_type last_integrated_server_version; -}; - -/// Checks that `uc.last_integrated_server_version` is zero if -/// `uc.client_version` is zero. -bool is_consistent(UploadCursor uc) noexcept; - -/// Checks that `a.last_integrated_server_version` and -/// `b.last_integrated_server_version` are equal, if `a.client_version` and -/// `b.client_version` are equal. Otherwise checks that -/// `a.last_integrated_server_version` is less than, or equal to -/// `b.last_integrated_server_version`, if `a.client_version` is less than -/// `b.client_version`. Otherwise checks that `a.last_integrated_server_version` -/// is greater than, or equal to `b.last_integrated_server_version`. -bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept; - - -/// A client's record of the current point of progress of the synchronization -/// process. The client must store this persistently in the local Realm file. -struct SyncProgress { - /// The last server version that the client has heard about. - SaltedVersion latest_server_version = {0, 0}; - - /// The last server version integrated, or about to be integrated by the - /// client. - DownloadCursor download = {0, 0}; - - /// The last client version integrated by the server. - UploadCursor upload = {0, 0}; -}; - -struct CompensatingWriteErrorInfo { - std::string object_name; - OwnedMixed primary_key; - std::string reason; -}; - -struct ResumptionDelayInfo { - // This is the maximum delay between trying to resume a session/connection. - std::chrono::milliseconds max_resumption_delay_interval = std::chrono::minutes{5}; - // The initial delay between trying to resume a session/connection. - std::chrono::milliseconds resumption_delay_interval = std::chrono::seconds{1}; - // After each failure of the same type, the last delay will be multiplied by this value - // until it is greater-than-or-equal to the max_resumption_delay_interval. - int resumption_delay_backoff_multiplier = 2; - // When calculating a new delay interval, a random value betwen zero and the result off - // dividing the current delay value by the delay_jitter_divisor will be subtracted from the - // delay interval. The default is to subtract up to 25% of the current delay interval. - // - // This is to reduce the likelyhood of a connection storm if the server goes down and - // all clients attempt to reconnect at once. - int delay_jitter_divisor = 4; -}; - -class IsFatalTag {}; -using IsFatal = util::TaggedBool; - -struct ProtocolErrorInfo { - enum class Action { - NoAction, - ProtocolViolation, - ApplicationBug, - Warning, - Transient, - DeleteRealm, - ClientReset, - ClientResetNoRecovery, - MigrateToFLX, - RevertToPBS, - // The RefreshUser/RefreshLocation/LogOutUser actions are currently generated internally when the - // sync websocket is closed with specific error codes. - RefreshUser, - RefreshLocation, - LogOutUser, - MigrateSchema, - }; - - ProtocolErrorInfo() = default; - ProtocolErrorInfo(int error_code, const std::string& msg, IsFatal is_fatal) - : raw_error_code(error_code) - , message(msg) - , is_fatal(is_fatal) - , client_reset_recovery_is_disabled(false) - , should_client_reset(util::none) - , server_requests_action(Action::NoAction) - { - } - int raw_error_code = 0; - std::string message; - IsFatal is_fatal = IsFatal{true}; - bool client_reset_recovery_is_disabled = false; - std::optional should_client_reset; - std::optional log_url; - std::optional compensating_write_server_version; - version_type compensating_write_rejected_client_version = 0; - std::vector compensating_writes; - std::optional resumption_delay_interval; - Action server_requests_action; - std::optional migration_query_string; - std::optional previous_schema_version; -}; - - -/// \brief Protocol errors discovered by the server, and reported to the client -/// by way of ERROR messages. -/// -/// These errors will be reported to the client-side application via the error -/// handlers of the affected sessions. -/// -/// ATTENTION: Please remember to update is_session_level_error() when -/// adding/removing error codes. -enum class ProtocolError { - // clang-format off - - // Connection level and protocol errors - connection_closed = RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED, // Connection closed (no error) - other_error = RLM_SYNC_ERR_CONNECTION_OTHER_ERROR, // Other connection level error - unknown_message = RLM_SYNC_ERR_CONNECTION_UNKNOWN_MESSAGE, // Unknown type of input message - bad_syntax = RLM_SYNC_ERR_CONNECTION_BAD_SYNTAX, // Bad syntax in input message head - limits_exceeded = RLM_SYNC_ERR_CONNECTION_LIMITS_EXCEEDED, // Limits exceeded in input message - wrong_protocol_version = RLM_SYNC_ERR_CONNECTION_WRONG_PROTOCOL_VERSION, // Wrong protocol version (CLIENT) (obsolete) - bad_session_ident = RLM_SYNC_ERR_CONNECTION_BAD_SESSION_IDENT, // Bad session identifier in input message - reuse_of_session_ident = RLM_SYNC_ERR_CONNECTION_REUSE_OF_SESSION_IDENT, // Overlapping reuse of session identifier (BIND) - bound_in_other_session = RLM_SYNC_ERR_CONNECTION_BOUND_IN_OTHER_SESSION, // Client file bound in other session (IDENT) - bad_message_order = RLM_SYNC_ERR_CONNECTION_BAD_MESSAGE_ORDER, // Bad input message order - bad_decompression = RLM_SYNC_ERR_CONNECTION_BAD_DECOMPRESSION, // Error in decompression (UPLOAD) - bad_changeset_header_syntax = RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_HEADER_SYNTAX, // Bad syntax in a changeset header (UPLOAD) - bad_changeset_size = RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_SIZE, // Bad size specified in changeset header (UPLOAD) - switch_to_flx_sync = RLM_SYNC_ERR_CONNECTION_SWITCH_TO_FLX_SYNC, // Connected with wrong wire protocol - should switch to FLX sync - switch_to_pbs = RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS, // Connected with wrong wire protocol - should switch to PBS - - // Session level errors - session_closed = RLM_SYNC_ERR_SESSION_SESSION_CLOSED, // Session closed (no error) - other_session_error = RLM_SYNC_ERR_SESSION_OTHER_SESSION_ERROR, // Other session level error - token_expired = RLM_SYNC_ERR_SESSION_TOKEN_EXPIRED, // Access token expired - bad_authentication = RLM_SYNC_ERR_SESSION_BAD_AUTHENTICATION, // Bad user authentication (BIND) - illegal_realm_path = RLM_SYNC_ERR_SESSION_ILLEGAL_REALM_PATH, // Illegal Realm path (BIND) - no_such_realm = RLM_SYNC_ERR_SESSION_NO_SUCH_REALM, // No such Realm (BIND) - permission_denied = RLM_SYNC_ERR_SESSION_PERMISSION_DENIED, // Permission denied (BIND) - bad_server_file_ident = RLM_SYNC_ERR_SESSION_BAD_SERVER_FILE_IDENT, // Bad server file identifier (IDENT) (obsolete!) - bad_client_file_ident = RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE_IDENT, // Bad client file identifier (IDENT) - bad_server_version = RLM_SYNC_ERR_SESSION_BAD_SERVER_VERSION, // Bad server version (IDENT, UPLOAD, TRANSACT) - bad_client_version = RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION, // Bad client version (IDENT, UPLOAD) - diverging_histories = RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES, // Diverging histories (IDENT) - bad_changeset = RLM_SYNC_ERR_SESSION_BAD_CHANGESET, // Bad changeset (UPLOAD, ERROR) - partial_sync_disabled = RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED, // Partial sync disabled (BIND) - unsupported_session_feature = RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE, // Unsupported session-level feature - bad_origin_file_ident = RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT, // Bad origin file identifier (UPLOAD) - bad_client_file = RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE, // Synchronization no longer possible for client-side file - server_file_deleted = RLM_SYNC_ERR_SESSION_SERVER_FILE_DELETED, // Server file was deleted while session was bound to it - client_file_blacklisted = RLM_SYNC_ERR_SESSION_CLIENT_FILE_BLACKLISTED, // Client file has been blacklisted (IDENT) - user_blacklisted = RLM_SYNC_ERR_SESSION_USER_BLACKLISTED, // User has been blacklisted (BIND) - transact_before_upload = RLM_SYNC_ERR_SESSION_TRANSACT_BEFORE_UPLOAD, // Serialized transaction before upload completion - client_file_expired = RLM_SYNC_ERR_SESSION_CLIENT_FILE_EXPIRED, // Client file has expired - user_mismatch = RLM_SYNC_ERR_SESSION_USER_MISMATCH, // User mismatch for client file identifier (IDENT) - too_many_sessions = RLM_SYNC_ERR_SESSION_TOO_MANY_SESSIONS, // Too many sessions in connection (BIND) - invalid_schema_change = RLM_SYNC_ERR_SESSION_INVALID_SCHEMA_CHANGE, // Invalid schema change (UPLOAD) - bad_query = RLM_SYNC_ERR_SESSION_BAD_QUERY, // Client query is invalid/malformed (IDENT, QUERY) - object_already_exists = RLM_SYNC_ERR_SESSION_OBJECT_ALREADY_EXISTS, // Client tried to create an object that already exists outside their view (UPLOAD) - server_permissions_changed = RLM_SYNC_ERR_SESSION_SERVER_PERMISSIONS_CHANGED, // Server permissions for this file ident have changed since the last time it was used (IDENT) - initial_sync_not_completed = RLM_SYNC_ERR_SESSION_INITIAL_SYNC_NOT_COMPLETED, // Client tried to open a session before initial sync is complete (BIND) - write_not_allowed = RLM_SYNC_ERR_SESSION_WRITE_NOT_ALLOWED, // Client attempted a write that is disallowed by permissions, or modifies an - // object outside the current query - requires client reset (UPLOAD) - compensating_write = RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE, // Client attempted a write that is disallowed by permissions, or modifies an - // object outside the current query, and the server undid the modification - // (UPLOAD) - migrate_to_flx = RLM_SYNC_ERR_SESSION_MIGRATE_TO_FLX, // Server migrated from PBS to FLX - migrate client to FLX (BIND) - bad_progress = RLM_SYNC_ERR_SESSION_BAD_PROGRESS, // Bad progress information (ERROR) - revert_to_pbs = RLM_SYNC_ERR_SESSION_REVERT_TO_PBS, // Server rolled back to PBS after FLX migration - revert FLX client migration (BIND) - bad_schema_version = RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION, // Client tried to open a session with an invalid schema version (BIND) - schema_version_changed = RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED, // Client opened a session with a new valid schema version - migrate client to use new schema version (BIND) - - // clang-format on -}; - -Status protocol_error_to_status(ProtocolError raw_error_code, std::string_view msg); - -constexpr bool is_session_level_error(ProtocolError); - -/// Returns null if the specified protocol error code is not defined by -/// ProtocolError. -const char* get_protocol_error_message(int error_code) noexcept; -std::ostream& operator<<(std::ostream&, ProtocolError protocol_error); - -// Implementation - -inline bool is_consistent(DownloadCursor dc) noexcept -{ - return (dc.server_version != 0 || dc.last_integrated_client_version == 0); -} - -inline bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept -{ - if (a.server_version < b.server_version) - return (a.last_integrated_client_version <= b.last_integrated_client_version); - if (a.server_version > b.server_version) - return (a.last_integrated_client_version >= b.last_integrated_client_version); - return (a.last_integrated_client_version == b.last_integrated_client_version); -} - -inline bool is_consistent(UploadCursor uc) noexcept -{ - return (uc.client_version != 0 || uc.last_integrated_server_version == 0); -} - -inline bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept -{ - if (a.client_version < b.client_version) - return (a.last_integrated_server_version <= b.last_integrated_server_version); - if (a.client_version > b.client_version) - return (a.last_integrated_server_version >= b.last_integrated_server_version); - return (a.last_integrated_server_version == b.last_integrated_server_version); -} - -constexpr bool is_session_level_error(ProtocolError error) -{ - return int(error) >= 200 && int(error) <= 299; -} - -inline std::ostream& operator<<(std::ostream& o, ProtocolErrorInfo::Action action) -{ - switch (action) { - case ProtocolErrorInfo::Action::NoAction: - return o << "NoAction"; - case ProtocolErrorInfo::Action::ProtocolViolation: - return o << "ProtocolViolation"; - case ProtocolErrorInfo::Action::ApplicationBug: - return o << "ApplicationBug"; - case ProtocolErrorInfo::Action::Warning: - return o << "Warning"; - case ProtocolErrorInfo::Action::Transient: - return o << "Transient"; - case ProtocolErrorInfo::Action::DeleteRealm: - return o << "DeleteRealm"; - case ProtocolErrorInfo::Action::ClientReset: - return o << "ClientReset"; - case ProtocolErrorInfo::Action::ClientResetNoRecovery: - return o << "ClientResetNoRecovery"; - case ProtocolErrorInfo::Action::MigrateToFLX: - return o << "MigrateToFLX"; - case ProtocolErrorInfo::Action::RevertToPBS: - return o << "RevertToPBS"; - case ProtocolErrorInfo::Action::RefreshUser: - return o << "RefreshUser"; - case ProtocolErrorInfo::Action::RefreshLocation: - return o << "RefreshLocation"; - case ProtocolErrorInfo::Action::LogOutUser: - return o << "LogOutUser"; - case ProtocolErrorInfo::Action::MigrateSchema: - return o << "MigrateSchema"; - } - return o << "Invalid error action: " << int64_t(action); -} - -inline std::ostream& operator<<(std::ostream& o, DownloadBatchState batch_state) -{ - switch (batch_state) { - case DownloadBatchState::MoreToCome: - return o << "MoreToCome"; - case DownloadBatchState::LastInBatch: - return o << "LastInBatch"; - case DownloadBatchState::SteadyState: - return o << "SteadyState"; - } - return o << "Invalid batch state: " << int(batch_state); -} - -} // namespace sync -} // namespace realm - -#endif // REALM_SYNC_PROTOCOL_HPP diff --git a/src/realm/sync/socket_provider.hpp b/src/realm/sync/socket_provider.hpp deleted file mode 100644 index ee51754c7b4..00000000000 --- a/src/realm/sync/socket_provider.hpp +++ /dev/null @@ -1,256 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// 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 -#include - -namespace realm::sync { -namespace websocket { -enum class WebSocketError; -} - -struct WebSocketEndpoint; -struct WebSocketInterface; -struct WebSocketObserver; - -/// Sync Socket Provider interface that provides the event loop and WebSocket -/// factory used by the SyncClient. -/// -/// All callback and event operations in the SyncClient must be completed in -/// the order in which they were issued (via post() or timer) to the event -/// loop and cannot be run in parallel. It is up to the custom event loop -/// implementation to determine if these are run on the same thread or a -/// thread pool as long as it is guaranteed that the callback handler -/// functions are processed in order and not run concurrently. -/// -/// The implementation of a SyncSocketProvider must support the following -/// operations that post handler functions (via by the Sync client) onto the -/// event loop: -/// * Post a handler function directly onto the event loop -/// * Post a handler function when the specified timer duration expires -/// -/// The event loop is not required to be a single thread as long as the -/// following requirements are satisfied: -/// * handler functions are called in the order they were posted to the -/// event loop queue, and -/// * a handler function runs to completion before the next handler function -/// is called. -/// -/// The SyncSocketProvider also provides a WebSocket interface for -/// connecting to the server via a WebSocket connection. -class SyncSocketProvider { -public: - /// Function handler typedef - using FunctionHandler = util::UniqueFunction; - - /// The Timer object used to track a timer that was started on the event - /// loop. - /// - /// This object provides a cancel() mechanism to cancel the timer. The - /// handler function for this timer must be called with a Status of - /// ErrorCodes::OperationAborted error code if the timer is canceled. - /// - /// Custom event loop implementations will need to create a subclass of - /// Timer that provides access to the underlying implementation to cancel - /// the timer. - struct Timer { - /// Cancels the timer and destroys the timer instance. - virtual ~Timer() = default; - /// Cancel the timer immediately. Does nothing if the timer has - /// already expired or been cancelled. - virtual void cancel() = 0; - }; - - /// Other class typedefs - using SyncTimer = std::unique_ptr; - - /// The event loop implementation must ensure the event loop is stopped and - /// flushed when the object is destroyed. If the event loop is processed by - /// a thread, the thread must be joined as part of this operation. - virtual ~SyncSocketProvider() = default; - - /// Create a new websocket pointed to the server indicated by endpoint and - /// connect to the server. Any events that occur during the execution of the - /// websocket will call directly to the handlers provided by the observer. - /// The WebSocketObserver guarantees that the WebSocket object will be - /// closed/destroyed before the observer is terminated/destroyed. - virtual std::unique_ptr connect(std::unique_ptr observer, - WebSocketEndpoint&& endpoint) = 0; - - /// Submit a handler function to be executed by the event loop (thread). - /// - /// Register the specified handler function to be queued on the event loop - /// for immediate asynchronous execution. The specified handler will be - /// executed by an expression on the form `handler()`. If the the handler - /// object is movable, it will never be copied. Otherwise, it will be - /// copied as necessary. - /// - /// This function is thread-safe and can be called by any thread. It can - /// also be called from other post() handler function. - /// - /// The handler will never be called as part of the execution of post(). If - /// post() is called on a thread separate from the event loop, the handler - /// may be called before post() returns. - /// - /// Handler functions added through post() must be executed in the order - /// they are added. More precisely, if post() is called twice to add two - /// handlers, A and B, and the execution of post(A) ends before the - /// beginning of the execution of post(B), then A is guaranteed to execute - /// before B. - /// - /// @param handler The handler function to be queued on the event loop. - virtual void post(FunctionHandler&& handler) = 0; - - /// Create and register a new timer whose handler function will be posted - /// to the event loop when the provided delay expires. - /// - /// This is a one shot timer and the Timer class returned becomes invalid - /// once the timer has expired. A new timer will need to be created to wait - /// again. - /// - /// @param delay The duration to wait in ms before the timer expires. - /// @param handler The handler function to be called on the event loop - /// when the timer expires. - /// - /// @return A pointer to the Timer object that can be used to cancel the - /// timer. The timer will also be canceled if the Timer object returned is - /// destroyed. - virtual SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) = 0; - - /// Temporary functions added to support the default socket provider until - /// it is fully integrated. Will be removed in future PRs. - virtual void stop(bool = false) {} -}; - -/// Struct that defines the endpoint to create a new websocket connection. -/// Many of these values come from the SyncClientConfig passed to SyncManager when -/// it was created. -struct WebSocketEndpoint { - using port_type = sync::port_type; - - std::string address; // Host address - port_type port; // Host port number - std::string path; // Includes access token in query. - std::vector protocols; // Array of one or more websocket protocols - bool is_ssl; // true if SSL should be used - - /// DEPRECATED - These will be removed in a future release - /// These fields are deprecated and should not be used by custom socket provider implementations - std::map headers; // Only includes "custom" headers. - bool verify_servers_ssl_certificate; - util::Optional ssl_trust_certificate_path; - std::function ssl_verify_callback; - util::Optional proxy; -}; - - -/// The WebSocket base class that is used by the SyncClient to send data over the -/// WebSocket connection with the server. This is the class that is returned by -/// SyncSocketProvider::connect() when a connection to an endpoint is requested. -/// If an error occurs while establishing the connection, the error is presented -/// to the WebSocketObserver provided when the WebSocket was created. -struct WebSocketInterface { - /// The destructor must close the websocket connection when the WebSocket object - /// is destroyed - virtual ~WebSocketInterface() = default; - - - /// For implementations that support it, return the app services request ID header - /// value, i.e. the "X-Appservices-Request-Id" header value. - /// - /// TODO: This will go away with RCORE-1380 since it's not strictly available in the - /// websocket spec. If HTTP headers aren't available, the default implementation of - /// returning an empty string is okay. - virtual std::string_view get_appservices_request_id() const noexcept - { - return {}; - } - - /// Write data asynchronously to the WebSocket connection. The handler function - /// will be called when the data has been sent successfully. The WebSocketOberver - /// provided when the WebSocket was created will be called if any errors occur - /// during the write operation. - /// @param data A util::Span containing the data to be sent to the server. - /// @param handler The handler function to be called when the data has been sent - /// successfully or the websocket has been closed (with - /// ErrorCodes::OperationAborted). If an error occurs during the - /// write operation, the websocket will be closed and the error - /// will be provided via the websocket_closed_handler() function. - virtual void async_write_binary(util::Span data, SyncSocketProvider::FunctionHandler&& handler) = 0; -}; - - -/// WebSocket observer interface in the SyncClient that receives the websocket -/// events during operation. -struct WebSocketObserver { - virtual ~WebSocketObserver() = default; - - /// Called when the websocket is connected, i.e. after the handshake is done. - /// The Sync Client is not allowed to send messages on the socket before the - /// handshake is complete and no message_received callbacks will be called - /// before the handshake is done. - /// - /// @param protocol The negotiated subprotocol value returned by the server - virtual void websocket_connected_handler(const std::string& protocol) = 0; - - /// Called when an error occurs while establishing the WebSocket connection - /// to the server or during normal operations. No additional binary messages - /// will be processed after this function is called. - virtual void websocket_error_handler() = 0; - - /// Called whenever a full message has arrived. The WebSocket implementation - /// is responsible for defragmenting fragmented messages internally and - /// delivering a full message to the Sync Client. - /// - /// @param data A util::Span containing the data received from the server. - /// The buffer is only valid until the function returns. - /// - /// @return bool designates whether the WebSocket object should continue - /// processing messages. The normal return value is true . False must - /// be returned if the websocket object has been destroyed during - /// execution of the function. - virtual bool websocket_binary_message_received(util::Span data) = 0; - - /// Called whenever the WebSocket connection has been closed, either as a result - /// of a WebSocket error or a normal close. - /// - /// @param was_clean Was the TCP connection closed after the WebSocket closing - /// handshake was completed. - /// @param error_code The error code received or synthesized when the websocket was closed. - /// @param message The message received in the close frame when the websocket was closed. - /// - /// @return bool designates whether the WebSocket object has been destroyed - /// during the execution of this function. The normal return value is - /// True to indicate the WebSocket object is no longer valid. If False - /// is returned, the WebSocket object will be destroyed at some point - /// in the future. - virtual bool websocket_closed_handler(bool was_clean, websocket::WebSocketError error_code, - std::string_view message) = 0; -}; - -} // namespace realm::sync diff --git a/src/realm/sync/subscriptions.cpp b/src/realm/sync/subscriptions.cpp deleted file mode 100644 index 349d2159138..00000000000 --- a/src/realm/sync/subscriptions.cpp +++ /dev/null @@ -1,1072 +0,0 @@ -/************************************************************************* - * - * Copyright 2021 Realm, Inc. - * - * 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 "realm/sync/subscriptions.hpp" - -#include "external/json/json.hpp" - -#include "realm/data_type.hpp" -#include "realm/keys.hpp" -#include "realm/list.hpp" -#include "realm/sort_descriptor.hpp" -#include "realm/sync/noinst/sync_metadata_schema.hpp" -#include "realm/table.hpp" -#include "realm/table_view.hpp" -#include "realm/transaction.hpp" -#include "realm/util/flat_map.hpp" - -#include -#include -#include - -namespace realm::sync { -namespace { -// Schema version history: -// v2: Initial public beta. - -constexpr static int c_flx_schema_version = 2; -constexpr static std::string_view c_flx_subscription_sets_table("flx_subscription_sets"); -constexpr static std::string_view c_flx_subscriptions_table("flx_subscriptions"); - -constexpr static std::string_view c_flx_sub_sets_state_field("state"); -constexpr static std::string_view c_flx_sub_sets_version_field("version"); -constexpr static std::string_view c_flx_sub_sets_error_str_field("error"); -constexpr static std::string_view c_flx_sub_sets_subscriptions_field("subscriptions"); -constexpr static std::string_view c_flx_sub_sets_snapshot_version_field("snapshot_version"); - -constexpr static std::string_view c_flx_sub_id_field("id"); -constexpr static std::string_view c_flx_sub_created_at_field("created_at"); -constexpr static std::string_view c_flx_sub_updated_at_field("updated_at"); -constexpr static std::string_view c_flx_sub_name_field("name"); -constexpr static std::string_view c_flx_sub_object_class_field("object_class"); -constexpr static std::string_view c_flx_sub_query_str_field("query"); - -using OptionalString = util::Optional; - -enum class SubscriptionStateForStorage : int64_t { - // The subscription set has been persisted locally but has not been acknowledged by the server yet. - Pending = 1, - // The server is currently sending the initial state that represents this subscription set to the client. - Bootstrapping = 2, - // This subscription set is the active subscription set that is currently being synchronized with the server. - Complete = 3, - // An error occurred while processing this subscription set on the server. Check error_str() for details. - Error = 4, - // The last bootstrap message containing the initial state for this subscription set has been received. The - // client is awaiting a mark message to mark this subscription as fully caught up to history. - AwaitingMark = 6, -}; - -SubscriptionSet::State state_from_storage(int64_t value) -{ - switch (static_cast(value)) { - case SubscriptionStateForStorage::Pending: - return SubscriptionSet::State::Pending; - case SubscriptionStateForStorage::Bootstrapping: - return SubscriptionSet::State::Bootstrapping; - case SubscriptionStateForStorage::AwaitingMark: - return SubscriptionSet::State::AwaitingMark; - case SubscriptionStateForStorage::Complete: - return SubscriptionSet::State::Complete; - case SubscriptionStateForStorage::Error: - return SubscriptionSet::State::Error; - default: - throw RuntimeError(ErrorCodes::InvalidArgument, - util::format("Invalid state for SubscriptionSet stored on disk: %1", value)); - } -} - -constexpr int64_t state_to_storage(SubscriptionSet::State state) -{ - switch (state) { - case SubscriptionSet::State::Pending: - return static_cast(SubscriptionStateForStorage::Pending); - case SubscriptionSet::State::Bootstrapping: - return static_cast(SubscriptionStateForStorage::Bootstrapping); - case SubscriptionSet::State::AwaitingMark: - return static_cast(SubscriptionStateForStorage::AwaitingMark); - case SubscriptionSet::State::Complete: - return static_cast(SubscriptionStateForStorage::Complete); - case SubscriptionSet::State::Error: - return static_cast(SubscriptionStateForStorage::Error); - default: - REALM_UNREACHABLE(); - } -} - -constexpr size_t state_to_order(SubscriptionSet::State needle) -{ - using State = SubscriptionSet::State; - switch (needle) { - case State::Uncommitted: - return 0; - case State::Pending: - return 1; - case State::Bootstrapping: - return 2; - case State::AwaitingMark: - return 3; - case State::Complete: - return 4; - case State::Error: - return 5; - case State::Superseded: - return 6; - } - REALM_UNREACHABLE(); -} - -template -void splice_if(std::list& src, std::list& dst, Predicate pred) -{ - for (auto it = src.begin(); it != src.end();) { - if (pred(*it)) { - dst.splice(dst.end(), src, it++); - } - else { - ++it; - } - } -} - -} // namespace - -Subscription::Subscription(const SubscriptionStore* parent, Obj obj) - : id(obj.get(parent->m_sub_id)) - , created_at(obj.get(parent->m_sub_created_at)) - , updated_at(obj.get(parent->m_sub_updated_at)) - , name(obj.is_null(parent->m_sub_name) ? OptionalString(util::none) - : OptionalString{obj.get(parent->m_sub_name)}) - , object_class_name(obj.get(parent->m_sub_object_class_name)) - , query_string(obj.get(parent->m_sub_query_str)) -{ -} - -Subscription::Subscription(util::Optional name, std::string object_class_name, std::string query_str) - : id(ObjectId::gen()) - , created_at(std::chrono::system_clock::now()) - , updated_at(created_at) - , name(std::move(name)) - , object_class_name(std::move(object_class_name)) - , query_string(std::move(query_str)) -{ -} - - -SubscriptionSet::SubscriptionSet(std::weak_ptr mgr, const Transaction& tr, const Obj& obj, - MakingMutableCopy making_mutable_copy) - : m_mgr(mgr) - , m_cur_version(tr.get_version()) - , m_version(obj.get_primary_key().get_int()) - , m_obj_key(obj.get_key()) -{ - REALM_ASSERT(obj.is_valid()); - if (!making_mutable_copy) { - load_from_database(obj); - } -} - -SubscriptionSet::SubscriptionSet(std::weak_ptr mgr, int64_t version, SupersededTag) - : m_mgr(mgr) - , m_version(version) - , m_state(State::Superseded) -{ -} - -void SubscriptionSet::load_from_database(const Obj& obj) -{ - auto mgr = get_flx_subscription_store(); // Throws - - m_state = state_from_storage(obj.get(mgr->m_sub_set_state)); - m_error_str = obj.get(mgr->m_sub_set_error_str); - m_snapshot_version = static_cast(obj.get(mgr->m_sub_set_snapshot_version)); - auto sub_list = obj.get_linklist(mgr->m_sub_set_subscriptions); - m_subs.clear(); - for (size_t idx = 0; idx < sub_list.size(); ++idx) { - m_subs.push_back(Subscription(mgr.get(), sub_list.get_object(idx))); - } -} - -std::shared_ptr SubscriptionSet::get_flx_subscription_store() const -{ - if (auto mgr = m_mgr.lock()) { - return mgr; - } - throw RuntimeError(ErrorCodes::BrokenInvariant, "Active SubscriptionSet without a SubscriptionStore"); -} - -int64_t SubscriptionSet::version() const -{ - return m_version; -} - -DB::version_type SubscriptionSet::snapshot_version() const -{ - return m_snapshot_version; -} - -SubscriptionSet::State SubscriptionSet::state() const -{ - return m_state; -} - -StringData SubscriptionSet::error_str() const -{ - if (m_error_str.empty()) { - return StringData{}; - } - return m_error_str; -} - -size_t SubscriptionSet::size() const -{ - return m_subs.size(); -} - -const Subscription& SubscriptionSet::at(size_t index) const -{ - return m_subs.at(index); -} - -SubscriptionSet::const_iterator SubscriptionSet::begin() const -{ - return m_subs.begin(); -} - -SubscriptionSet::const_iterator SubscriptionSet::end() const -{ - return m_subs.end(); -} - -const Subscription* SubscriptionSet::find(StringData name) const -{ - for (auto&& sub : *this) { - if (sub.name == name) - return ⊂ - } - return nullptr; -} - -const Subscription* SubscriptionSet::find(const Query& query) const -{ - const auto query_desc = query.get_description(); - const auto table_name = Group::table_name_to_class_name(query.get_table()->get_name()); - for (auto&& sub : *this) { - if (sub.object_class_name == table_name && sub.query_string == query_desc) - return ⊂ - } - return nullptr; -} - -MutableSubscriptionSet::MutableSubscriptionSet(std::weak_ptr mgr, TransactionRef tr, Obj obj) - : SubscriptionSet(mgr, *tr, obj, MakingMutableCopy{true}) - , m_tr(std::move(tr)) - , m_obj(std::move(obj)) -{ -} - -void MutableSubscriptionSet::check_is_mutable() const -{ - if (!m_tr || m_tr->get_transact_stage() != DB::transact_Writing) { - throw WrongTransactionState("Not a write transaction"); - } -} - -// This uses the 'swap and pop' idiom to run in constant time. -// The iterator returned is: -// 1. end(), if the last subscription is removed -// 2. same iterator it is passed (but pointing to the last subscription in set), otherwise -MutableSubscriptionSet::iterator MutableSubscriptionSet::erase(const_iterator it) -{ - check_is_mutable(); - REALM_ASSERT(it != end()); - if (it == std::prev(m_subs.end())) { - m_subs.pop_back(); - return end(); - } - auto back = std::prev(m_subs.end()); - // const_iterator to iterator in constant time (See https://stackoverflow.com/a/10669041) - auto iterator = m_subs.erase(it, it); - std::swap(*iterator, *back); - m_subs.pop_back(); - return iterator; -} - -bool MutableSubscriptionSet::erase(StringData name) -{ - check_is_mutable(); - auto ptr = find(name); - if (!ptr) - return false; - auto it = m_subs.begin() + (ptr - &m_subs.front()); - erase(it); - return true; -} - -bool MutableSubscriptionSet::erase(const Query& query) -{ - check_is_mutable(); - auto ptr = find(query); - if (!ptr) - return false; - auto it = m_subs.begin() + (ptr - &m_subs.front()); - erase(it); - return true; -} - -bool MutableSubscriptionSet::erase_by_class_name(StringData object_class_name) -{ - // TODO: Use std::erase_if when switching to C++20. - auto it = std::remove_if(m_subs.begin(), m_subs.end(), [&object_class_name](const Subscription& sub) { - return sub.object_class_name == object_class_name; - }); - auto erased = end() - it; - m_subs.erase(it, end()); - return erased > 0; -} - -bool MutableSubscriptionSet::erase_by_id(ObjectId id) -{ - auto it = std::find_if(m_subs.begin(), m_subs.end(), [&id](const Subscription& sub) -> bool { - return sub.id == id; - }); - if (it == end()) { - return false; - } - erase(it); - return true; -} - -void MutableSubscriptionSet::clear() -{ - check_is_mutable(); - m_subs.clear(); -} - -void MutableSubscriptionSet::insert_sub(const Subscription& sub) -{ - check_is_mutable(); - m_subs.push_back(sub); -} - -std::pair -MutableSubscriptionSet::insert_or_assign_impl(iterator it, util::Optional name, - std::string object_class_name, std::string query_str) -{ - check_is_mutable(); - if (it != end()) { - auto& sub = m_subs[it - begin()]; - sub.object_class_name = std::move(object_class_name); - sub.query_string = std::move(query_str); - sub.updated_at = Timestamp{std::chrono::system_clock::now()}; - - return {it, false}; - } - it = m_subs.insert(m_subs.end(), - Subscription(std::move(name), std::move(object_class_name), std::move(query_str))); - - return {it, true}; -} - -std::pair MutableSubscriptionSet::insert_or_assign(std::string_view name, - const Query& query) -{ - auto table_name = Group::table_name_to_class_name(query.get_table()->get_name()); - auto query_str = query.get_description(); - auto it = std::find_if(begin(), end(), [&](const Subscription& sub) { - return sub.name == name; - }); - - return insert_or_assign_impl(it, std::string{name}, std::move(table_name), std::move(query_str)); -} - -std::pair MutableSubscriptionSet::insert_or_assign(const Query& query) -{ - auto table_name = Group::table_name_to_class_name(query.get_table()->get_name()); - auto query_str = query.get_description(); - auto it = std::find_if(begin(), end(), [&](const Subscription& sub) { - return (!sub.name && sub.object_class_name == table_name && sub.query_string == query_str); - }); - - return insert_or_assign_impl(it, util::none, std::move(table_name), std::move(query_str)); -} - -void MutableSubscriptionSet::import(SubscriptionSet&& src_subs) -{ - check_is_mutable(); - SubscriptionSet::import(std::move(src_subs)); -} - -void SubscriptionSet::import(SubscriptionSet&& src_subs) -{ - m_subs = std::move(src_subs.m_subs); -} - -void MutableSubscriptionSet::set_state(State new_state) -{ - REALM_ASSERT(m_state == State::Uncommitted); - m_state = new_state; -} - -MutableSubscriptionSet SubscriptionSet::make_mutable_copy() const -{ - auto mgr = get_flx_subscription_store(); // Throws - return mgr->make_mutable_copy(*this); -} - -void SubscriptionSet::refresh() -{ - auto mgr = get_flx_subscription_store(); // Throws - if (mgr->would_refresh(m_cur_version)) { - *this = mgr->get_refreshed(m_obj_key, version()); - } -} - -util::Future SubscriptionSet::get_state_change_notification(State notify_when) const -{ - auto mgr = get_flx_subscription_store(); // Throws - - util::CheckedLockGuard lk(mgr->m_pending_notifications_mutex); - State cur_state = state(); - std::string err_str = error_str(); - - // If there have been writes to the database since this SubscriptionSet was created, we need to fetch - // the updated version from the DB to know the true current state and maybe return a ready future. - if (m_cur_version < mgr->m_db->get_version_of_latest_snapshot()) { - auto refreshed_self = mgr->get_refreshed(m_obj_key, version()); - cur_state = refreshed_self.state(); - err_str = refreshed_self.error_str(); - } - // If we've already reached the desired state, or if the subscription is in an error state, - // we can return a ready future immediately. - if (cur_state == State::Error) { - return util::Future::make_ready(Status{ErrorCodes::SubscriptionFailed, err_str}); - } - else if (state_to_order(cur_state) >= state_to_order(notify_when)) { - return util::Future::make_ready(cur_state); - } - - // Otherwise, make a promise/future pair and add it to the list of pending notifications. - auto [promise, future] = util::make_promise_future(); - mgr->m_pending_notifications.emplace_back(version(), std::move(promise), notify_when); - return std::move(future); -} - -void SubscriptionSet::get_state_change_notification( - State notify_when, util::UniqueFunction, util::Optional)> cb) const -{ - get_state_change_notification(notify_when).get_async([cb = std::move(cb)](StatusWith result) { - if (result.is_ok()) { - cb(result.get_value(), {}); - } - else { - cb({}, result.get_status()); - } - }); -} - -void SubscriptionStore::report_progress() -{ - TransactionRef tr; - report_progress(tr); -} - -void SubscriptionStore::report_progress(TransactionRef& tr) -{ - util::CheckedUniqueLock lk(m_pending_notifications_mutex); - if (m_pending_notifications.empty()) - return; - - if (!tr) - tr = m_db->start_read(); - auto sub_sets = tr->get_table(m_sub_set_table); - - struct NotificationCompletion { - util::Promise promise; - std::string_view error_str; - State state; - }; - std::vector to_finish; - m_pending_notifications.remove_if([&](NotificationRequest& req) { - Obj obj = sub_sets->get_object_with_primary_key(req.version); - if (!obj) { - to_finish.push_back({std::move(req.promise), {}, State::Superseded}); - return true; - } - - auto state = state_from_storage(obj.get(m_sub_set_state)); - if (state_to_order(state) < state_to_order(req.notify_when)) - return false; - - std::string_view error_str; - if (state == State::Error) { - error_str = std::string_view(obj.get(m_sub_set_error_str)); - } - to_finish.push_back({std::move(req.promise), error_str, state}); - return true; - }); - lk.unlock(); - - for (auto& [promise, error_str, state] : to_finish) { - if (state == State::Error) { - promise.set_error({ErrorCodes::SubscriptionFailed, error_str}); - } - else { - promise.emplace_value(state); - } - } -} - -int64_t SubscriptionStore::get_downloading_query_version(Transaction& tr) const -{ - auto sub_sets = tr.get_table(m_sub_set_table); - ObjKey key; - const Mixed states[] = { - state_to_storage(SubscriptionSet::State::Complete), - state_to_storage(SubscriptionSet::State::AwaitingMark), - state_to_storage(SubscriptionSet::State::Bootstrapping), - }; - sub_sets->where().in(m_sub_set_state, std::begin(states), std::end(states)).max(m_sub_set_version_num, &key); - return key ? sub_sets->get_object(key).get(m_sub_set_version_num) : 0; -} - -SubscriptionSet MutableSubscriptionSet::commit() -{ - if (!m_tr || m_tr->get_transact_stage() != DB::transact_Writing) { - throw LogicError(ErrorCodes::WrongTransactionState, "SubscriptionSet has already been committed"); - } - auto mgr = get_flx_subscription_store(); // Throws - - if (m_state == State::Uncommitted) { - m_state = State::Pending; - } - m_obj.set(mgr->m_sub_set_snapshot_version, static_cast(m_tr->get_version())); - - auto obj_sub_list = m_obj.get_linklist(mgr->m_sub_set_subscriptions); - obj_sub_list.clear(); - for (const auto& sub : m_subs) { - auto new_sub = obj_sub_list.create_and_insert_linked_object(obj_sub_list.size()); - new_sub.set(mgr->m_sub_id, sub.id); - new_sub.set(mgr->m_sub_created_at, sub.created_at); - new_sub.set(mgr->m_sub_updated_at, sub.updated_at); - if (sub.name) { - new_sub.set(mgr->m_sub_name, StringData(*sub.name)); - } - new_sub.set(mgr->m_sub_object_class_name, StringData(sub.object_class_name)); - new_sub.set(mgr->m_sub_query_str, StringData(sub.query_string)); - } - m_obj.set(mgr->m_sub_set_state, state_to_storage(m_state)); - - const auto flx_version = version(); - m_tr->commit_and_continue_as_read(); - - mgr->report_progress(m_tr); - - DB::VersionID commit_version = m_tr->get_version_of_current_transaction(); - // release the read lock so that this instance doesn't keep a version pinned - // for the remainder of its lifetime - m_tr.reset(); - - return mgr->get_refreshed(m_obj.get_key(), flx_version, commit_version); -} - -std::string SubscriptionSet::to_ext_json() const -{ - if (m_subs.empty()) { - return "{}"; - } - - util::FlatMap> table_to_query; - for (const auto& sub : *this) { - std::string table_name(sub.object_class_name); - auto& queries_for_table = table_to_query.at(table_name); - auto query_it = std::find(queries_for_table.begin(), queries_for_table.end(), sub.query_string); - if (query_it != queries_for_table.end()) { - continue; - } - queries_for_table.emplace_back(sub.query_string); - } - - if (table_to_query.empty()) { - return "{}"; - } - - // TODO this is pulling in a giant compile-time dependency. We should have a better way of escaping the - // query strings into a json object. - nlohmann::json output_json; - for (auto& table : table_to_query) { - // We want to make sure that the queries appear in some kind of canonical order so that if there are - // two subscription sets with the same subscriptions in different orders, the server doesn't have to - // waste a bunch of time re-running the queries for that table. - std::stable_sort(table.second.begin(), table.second.end()); - - bool is_first = true; - std::ostringstream obuf; - for (const auto& query_str : table.second) { - if (!is_first) { - obuf << " OR "; - } - is_first = false; - obuf << "(" << query_str << ")"; - } - output_json[table.first] = obuf.str(); - } - - return output_json.dump(); -} - -SubscriptionStoreRef SubscriptionStore::create(DBRef db) -{ - return std::make_shared(Private(), std::move(db)); -} - -SubscriptionStore::SubscriptionStore(Private, DBRef db) - : m_db(std::move(db)) -{ - std::vector internal_tables{ - {&m_sub_set_table, - c_flx_subscription_sets_table, - {&m_sub_set_version_num, c_flx_sub_sets_version_field, type_Int}, - { - {&m_sub_set_state, c_flx_sub_sets_state_field, type_Int}, - {&m_sub_set_snapshot_version, c_flx_sub_sets_snapshot_version_field, type_Int}, - {&m_sub_set_error_str, c_flx_sub_sets_error_str_field, type_String, true}, - {&m_sub_set_subscriptions, c_flx_sub_sets_subscriptions_field, c_flx_subscriptions_table, true}, - }}, - {&m_sub_table, - c_flx_subscriptions_table, - SyncMetadataTable::IsEmbeddedTag{}, - { - {&m_sub_id, c_flx_sub_id_field, type_ObjectId}, - {&m_sub_created_at, c_flx_sub_created_at_field, type_Timestamp}, - {&m_sub_updated_at, c_flx_sub_updated_at_field, type_Timestamp}, - {&m_sub_name, c_flx_sub_name_field, type_String, true}, - {&m_sub_object_class_name, c_flx_sub_object_class_field, type_String}, - {&m_sub_query_str, c_flx_sub_query_str_field, type_String}, - }}, - }; - - auto tr = m_db->start_read(); - // Start with a reader so it doesn't try to write until we are ready - SyncMetadataSchemaVersionsReader schema_versions_reader(tr); - - if (auto schema_version = - schema_versions_reader.get_version_for(tr, internal_schema_groups::c_flx_subscription_store)) { - if (*schema_version != c_flx_schema_version) { - throw RuntimeError(ErrorCodes::UnsupportedFileFormatVersion, - "Invalid schema version for flexible sync metadata"); - } - load_sync_metadata_schema(*tr, &internal_tables); - } - else { - tr->promote_to_write(); - // Ensure the schema versions table is initialized (may add its own commit) - SyncMetadataSchemaVersions schema_versions(tr); - // Create the metadata schema and set the version (in the same commit) - schema_versions.set_version_for(tr, internal_schema_groups::c_flx_subscription_store, c_flx_schema_version); - create_sync_metadata_schema(*tr, &internal_tables); - tr->commit_and_continue_as_read(); - } - REALM_ASSERT(m_sub_set_table); - - // Make sure the subscription set table is properly initialized - initialize_subscriptions_table(std::move(tr)); -} - -void SubscriptionStore::initialize_subscriptions_table(TransactionRef&& tr) -{ - if (auto sub_sets = tr->get_table(m_sub_set_table); sub_sets->is_empty()) { - tr->promote_to_write(); - clear(*tr); - tr->commit(); - } -} - -void SubscriptionStore::clear(Transaction& wt) -{ - auto sub_sets = wt.get_table(m_sub_set_table); - sub_sets->clear(); - // There should always be at least one subscription set so that the user can always wait - // for synchronizationon on the result of get_latest(). - auto zero_sub = sub_sets->create_object_with_primary_key(Mixed{int64_t(0)}); - zero_sub.set(m_sub_set_state, static_cast(SubscriptionSet::State::Pending)); - zero_sub.set(m_sub_set_snapshot_version, wt.get_version()); -} - -SubscriptionSet SubscriptionStore::get_latest() -{ - auto tr = m_db->start_frozen(); - auto sub_sets = tr->get_table(m_sub_set_table); - // There should always be at least one SubscriptionSet - the zeroth subscription set for schema instructions. - REALM_ASSERT(!sub_sets->is_empty()); - - auto latest_id = sub_sets->max(sub_sets->get_primary_key_column())->get_int(); - auto latest_obj = sub_sets->get_object_with_primary_key(Mixed{latest_id}); - - return SubscriptionSet(weak_from_this(), *tr, latest_obj); -} - -SubscriptionSet SubscriptionStore::get_active() -{ - auto tr = m_db->start_frozen(); - return SubscriptionSet(weak_from_this(), *tr, get_active(*tr)); -} - -Obj SubscriptionStore::get_active(const Transaction& tr) -{ - auto sub_sets = tr.get_table(m_sub_set_table); - // There should always be at least one SubscriptionSet - the zeroth subscription set for schema instructions. - REALM_ASSERT(!sub_sets->is_empty()); - - ObjKey key; - sub_sets->where() - .equal(m_sub_set_state, state_to_storage(SubscriptionSet::State::Complete)) - .Or() - .equal(m_sub_set_state, state_to_storage(SubscriptionSet::State::AwaitingMark)) - .max(m_sub_set_version_num, &key); - - // If there is no active subscription yet, return the zeroth subscription. - return key ? sub_sets->get_object(key) : sub_sets->get_object_with_primary_key(0); -} - -SubscriptionStore::VersionInfo SubscriptionStore::get_version_info() const -{ - auto tr = m_db->start_read(); - auto sub_sets = tr->get_table(m_sub_set_table); - // There should always be at least one SubscriptionSet - the zeroth subscription set for schema instructions. - REALM_ASSERT(!sub_sets->is_empty()); - - auto get = [](Mixed m) { - return m.is_type(type_Int) ? m.get_int() : SubscriptionSet::EmptyVersion; - }; - - VersionInfo ret; - ret.latest = sub_sets->max(sub_sets->get_primary_key_column())->get_int(); - ret.active = get(*sub_sets->where() - .equal(m_sub_set_state, state_to_storage(SubscriptionSet::State::Complete)) - .Or() - .equal(m_sub_set_state, state_to_storage(SubscriptionSet::State::AwaitingMark)) - .max(m_sub_set_version_num)); - ret.pending_mark = get(*sub_sets->where() - .equal(m_sub_set_state, state_to_storage(SubscriptionSet::State::AwaitingMark)) - .max(m_sub_set_version_num)); - return ret; -} - -util::Optional -SubscriptionStore::get_next_pending_version(int64_t last_query_version) const -{ - auto tr = m_db->start_read(); - auto sub_sets = tr->get_table(m_sub_set_table); - // There should always be at least one SubscriptionSet - the zeroth subscription set for schema instructions. - REALM_ASSERT(!sub_sets->is_empty()); - - ObjKey key; - sub_sets->where() - .greater(sub_sets->get_primary_key_column(), last_query_version) - .group() - .equal(m_sub_set_state, state_to_storage(SubscriptionSet::State::Pending)) - .Or() - .equal(m_sub_set_state, state_to_storage(SubscriptionSet::State::Bootstrapping)) - .end_group() - .min(m_sub_set_version_num, &key); - - if (!key) { - return util::none; - } - - auto obj = sub_sets->get_object(key); - auto query_version = obj.get_primary_key().get_int(); - auto snapshot_version = obj.get(m_sub_set_snapshot_version); - return PendingSubscription{query_version, static_cast(snapshot_version)}; -} - -std::vector SubscriptionStore::get_pending_subscriptions() -{ - std::vector subscriptions_to_recover; - auto active_sub = get_active(); - auto cur_query_version = active_sub.version(); - // get a copy of the pending subscription sets since the active version - while (auto next_pending = get_next_pending_version(cur_query_version)) { - cur_query_version = next_pending->query_version; - subscriptions_to_recover.push_back(get_by_version(cur_query_version)); - } - return subscriptions_to_recover; -} - -void SubscriptionStore::notify_all_state_change_notifications(Status status) -{ - util::CheckedUniqueLock lk(m_pending_notifications_mutex); - auto to_finish = std::move(m_pending_notifications); - lk.unlock(); - - // Just complete/cancel the pending notifications - this function does not alter the - // state of any pending subscriptions - for (auto& req : to_finish) { - req.promise.set_error(status); - } -} - -void SubscriptionStore::reset(Transaction& wt) -{ - // Clear out and initialize the subscription store - clear(wt); - - util::CheckedUniqueLock lk(m_pending_notifications_mutex); - auto to_finish = std::move(m_pending_notifications); - lk.unlock(); - - for (auto& req : to_finish) { - req.promise.emplace_value(SubscriptionSet::State::Superseded); - } -} - -void SubscriptionStore::begin_bootstrap(const Transaction& tr, int64_t query_version) -{ - auto sub_sets = tr.get_table(m_sub_set_table); - REALM_ASSERT(!sub_sets->is_empty()); - Obj obj = sub_sets->get_object_with_primary_key(query_version); - if (!obj) { - throw RuntimeError(ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received bootstrap for nonexistent query version %1", query_version)); - } - - switch (auto old_state = state_from_storage(obj.get(m_sub_set_state))) { - case State::Complete: - case State::AwaitingMark: - // Once bootstrapping has completed it remains complete even if the - // server decides to send us more bootstrap messages - return; - - case State::Pending: - obj.set(m_sub_set_state, state_to_storage(State::Bootstrapping)); - break; - - case State::Error: { - auto error = obj.get(m_sub_set_error_str); - throw RuntimeError(ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received bootstrap for query version %1 after receiving the error '%2'", - query_version, error)); - } - - default: - // Any other state is an internal bug of some sort - REALM_ASSERT_EX(false, old_state); - static_cast(old_state); - } -} - -void SubscriptionStore::do_complete_bootstrap(const Transaction& tr, int64_t query_version, State new_state) -{ - auto sub_sets = tr.get_table(m_sub_set_table); - REALM_ASSERT(!sub_sets->is_empty()); - Obj obj = sub_sets->get_object_with_primary_key(query_version); - // The sub set object being deleted while we're in the middle of applying - // a bootstrap would be an internal bug - REALM_ASSERT(obj); - - switch (auto old_state = state_from_storage(obj.get(m_sub_set_state))) { - case State::Complete: - case State::AwaitingMark: - // We were applying a bootstrap for a subscription which had already - // completed applying a bootstrap, which means something like a - // permission change occurred server-side which triggered a rebootstrap. - return; - - case State::Bootstrapping: - obj.set(m_sub_set_state, state_to_storage(new_state)); - break; - - default: - // Any other state is an internal bug of some sort - REALM_ASSERT_EX(false, old_state); - static_cast(old_state); - } - - // Supersede all older subscription sets - if (new_state == State::AwaitingMark) { - sub_sets->where().less(m_sub_set_version_num, query_version).remove(); - } -} - -void SubscriptionStore::complete_bootstrap(const Transaction& tr, int64_t query_version) -{ - do_complete_bootstrap(tr, query_version, State::AwaitingMark); -} - -void SubscriptionStore::cancel_bootstrap(const Transaction& tr, int64_t query_version) -{ - do_complete_bootstrap(tr, query_version, State::Pending); -} - -void SubscriptionStore::set_error(int64_t query_version, std::string_view error_str) -{ - auto tr = m_db->start_write(); - auto sub_sets = tr->get_table(m_sub_set_table); - auto obj = sub_sets->get_object_with_primary_key(query_version); - if (!obj) { - // This can happen either due to a bug in the sync client or due to the - // server sending us an error message for an invalid query version. We - // assume it is the latter here. - throw RuntimeError(ErrorCodes::SyncProtocolInvariantFailed, - util::format("Invalid state update for nonexistent query version %1", query_version)); - } - - auto old_state = state_from_storage(obj.get(m_sub_set_state)); - if (old_state == State::Complete) { - throw RuntimeError(ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received error '%1' for already-completed query version %2. This " - "may be due to a queryable field being removed in the server-side " - "configuration making the previous subscription set no longer valid.", - error_str, query_version)); - } - - obj.set(m_sub_set_state, state_to_storage(State::Error)); - obj.set(m_sub_set_error_str, error_str); - tr->commit(); -} - -void SubscriptionStore::download_complete() -{ - auto tr = m_db->start_read(); - auto obj = get_active(*tr); - if (state_from_storage(obj.get(m_sub_set_state)) != State::AwaitingMark) - return; - - // Although subscription sets can be created from any thread or process, - // they're only *modified* on the sync client thread, so we don't have to - // recheck that things have changed after the promote to write - tr->promote_to_write(); - obj.set(m_sub_set_state, state_to_storage(State::Complete)); - tr->commit(); -} - -SubscriptionSet SubscriptionStore::get_by_version(int64_t version_id) -{ - auto tr = m_db->start_frozen(); - auto sub_sets = tr->get_table(m_sub_set_table); - if (auto obj = sub_sets->get_object_with_primary_key(version_id)) { - return SubscriptionSet(weak_from_this(), *tr, obj); - } - REALM_ASSERT(!sub_sets->is_empty()); - if (version_id < sub_sets->min(m_sub_set_version_num)->get_int()) { - return SubscriptionSet(weak_from_this(), version_id, SubscriptionSet::SupersededTag{}); - } - throw KeyNotFound(util::format("Subscription set with version %1 not found", version_id)); -} - -SubscriptionSet SubscriptionStore::get_refreshed(ObjKey key, int64_t version, std::optional db_version) -{ - auto tr = m_db->start_frozen(db_version.value_or(VersionID{})); - auto sub_sets = tr->get_table(m_sub_set_table); - if (auto obj = sub_sets->try_get_object(key)) { - return SubscriptionSet(weak_from_this(), *tr, obj); - } - return SubscriptionSet(weak_from_this(), version, SubscriptionSet::SupersededTag{}); -} - -SubscriptionStore::TableSet SubscriptionStore::get_tables_for_latest(const Transaction& tr) const -{ - auto sub_sets = tr.get_table(m_sub_set_table); - // There should always be at least one SubscriptionSet - the zeroth subscription set for schema instructions. - REALM_ASSERT(!sub_sets->is_empty()); - - auto latest_id = sub_sets->max(sub_sets->get_primary_key_column())->get_int(); - auto latest_obj = sub_sets->get_object_with_primary_key(Mixed{latest_id}); - - TableSet ret; - auto subs = latest_obj.get_linklist(m_sub_set_subscriptions); - for (size_t idx = 0; idx < subs.size(); ++idx) { - auto sub_obj = subs.get_object(idx); - ret.emplace(sub_obj.get(m_sub_object_class_name)); - } - - return ret; -} - -MutableSubscriptionSet SubscriptionStore::make_mutable_copy(const SubscriptionSet& set) -{ - auto new_tr = m_db->start_write(); - - auto sub_sets = new_tr->get_table(m_sub_set_table); - auto new_pk = sub_sets->max(sub_sets->get_primary_key_column())->get_int() + 1; - - MutableSubscriptionSet new_set_obj(weak_from_this(), std::move(new_tr), - sub_sets->create_object_with_primary_key(Mixed{new_pk})); - for (const auto& sub : set) { - new_set_obj.insert_sub(sub); - } - - return new_set_obj; -} - -bool SubscriptionStore::would_refresh(DB::version_type version) const noexcept -{ - return version < m_db->get_version_of_latest_snapshot(); -} - -int64_t SubscriptionStore::set_active_as_latest(Transaction& wt) -{ - auto sub_sets = wt.get_table(m_sub_set_table); - auto active = get_active(wt); - // Delete all newer subscription sets, if any - sub_sets->where().greater(sub_sets->get_primary_key_column(), active.get_primary_key().get_int()).remove(); - // Mark the active set as complete even if it was previously WaitingForMark - // as we've completed rebootstrapping before calling this. - active.set(m_sub_set_state, state_to_storage(State::Complete)); - auto version = active.get_primary_key().get_int(); - - std::list to_finish; - { - util::CheckedLockGuard lock(m_pending_notifications_mutex); - splice_if(m_pending_notifications, to_finish, [&](auto& req) { - if (req.version == version && state_to_order(req.notify_when) <= state_to_order(State::Complete)) - return true; - return req.version != version; - }); - } - - for (auto& req : to_finish) { - req.promise.emplace_value(req.version == version ? State::Complete : State::Superseded); - } - - return version; -} - -int64_t SubscriptionStore::mark_active_as_complete(Transaction& wt) -{ - auto active = get_active(wt); - active.set(m_sub_set_state, state_to_storage(State::Complete)); - auto version = active.get_primary_key().get_int(); - - std::list to_finish; - { - util::CheckedLockGuard lock(m_pending_notifications_mutex); - splice_if(m_pending_notifications, to_finish, [&](auto& req) { - return req.version == version && state_to_order(req.notify_when) <= state_to_order(State::Complete); - }); - } - - for (auto& req : to_finish) { - req.promise.emplace_value(State::Complete); - } - - return version; -} - -} // namespace realm::sync diff --git a/src/realm/sync/subscriptions.hpp b/src/realm/sync/subscriptions.hpp deleted file mode 100644 index a4f8ae4ed1c..00000000000 --- a/src/realm/sync/subscriptions.hpp +++ /dev/null @@ -1,457 +0,0 @@ -/************************************************************************* - * - * Copyright 2021 Realm, Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef REALM_SYNC_SUBSCRIPTIONS_HPP -#define REALM_SYNC_SUBSCRIPTIONS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace realm::sync { - -class MutableSubscriptionSet; -class SubscriptionSet; -class SubscriptionStore; - -// A Subscription represents a single query that may be OR'd with other queries on the same object class to be -// send to the server in a QUERY or IDENT message. -class Subscription { -public: - // The unique ID for this subscription. - ObjectId id; - - // The timestamp of when this subscription was originally created. - Timestamp created_at; - - // The timestamp of the last time this subscription was updated by calling update_query. - Timestamp updated_at; - - // The name of the subscription that was set when it was created, or util::none if it was created without a name. - util::Optional name; - - // The name of the object class of the query for this subscription. - std::string object_class_name; - - // A stringified version of the query associated with this subscription. - std::string query_string; - - // Returns whether the 2 subscriptions passed have the same id. - friend bool operator==(const Subscription& lhs, const Subscription& rhs) - { - return lhs.id == rhs.id; - } - - Subscription() = default; - Subscription(const SubscriptionStore* parent, Obj obj); - Subscription(util::Optional name, std::string object_class_name, std::string query_str); -}; - -// SubscriptionSets contain a set of unique queries by either name or Query object that will be constructed into a -// single QUERY or IDENT message to be sent to the server. -class SubscriptionSet { -public: - /* - * State diagram: - * - * ┌───────────┬─────────►Error──────────────────────────┐ - * │ │ │ - * │ │ ▼ - * Uncommitted──►Pending──►Bootstrapping──►AwaitingMark──►Complete───►Superseded - * │ ▲ - * │ │ - * └────────────────────────────┘ - * - */ - enum class State { - // This subscription set has not been persisted and has not been sent to the server. This state is only valid - // for MutableSubscriptionSets - Uncommitted = 0, - // The subscription set has been persisted locally but has not been acknowledged by the server yet. - Pending, - // The server is currently sending the initial state that represents this subscription set to the client. - Bootstrapping, - // This subscription set is the active subscription set that is currently being synchronized with the server. - Complete, - // An error occurred while processing this subscription set on the server. Check error_str() for details. - Error, - // The server responded to a later subscription set to this one and this one has been trimmed from the - // local storage of subscription sets. - Superseded, - // The last bootstrap message containing the initial state for this subscription set has been received. The - // client is awaiting a mark message to mark this subscription as fully caught up to history. - AwaitingMark, - }; - - static constexpr int64_t EmptyVersion = int64_t(-1); - - // Used in tests. - inline friend std::ostream& operator<<(std::ostream& o, State state) - { - switch (state) { - case State::Uncommitted: - o << "Uncommitted"; - break; - case State::Pending: - o << "Pending"; - break; - case State::Bootstrapping: - o << "Bootstrapping"; - break; - case State::AwaitingMark: - o << "AwaitingMark"; - break; - case State::Complete: - o << "Complete"; - break; - case State::Error: - o << "Error"; - break; - case State::Superseded: - o << "Superseded"; - break; - } - return o; - } - - using const_iterator = std::vector::const_iterator; - using iterator = const_iterator; // Note: no mutable access provided through iterators. - - // This will make a copy of this subscription set with the next available version number and return it as - // a mutable SubscriptionSet to be updated. The new SubscriptionSet's state will be Uncommitted. This - // subscription set will be unchanged. - MutableSubscriptionSet make_mutable_copy() const; - - // Returns a future that will resolve either with an error status if this subscription set encounters an - // error, or resolves when the subscription set reaches at least that state. It's possible for a subscription - // set to skip a state (i.e. go from Pending to Complete or Pending to Superseded), and the future value - // will the the state it actually reached. - util::Future get_state_change_notification(State notify_when) const; - void get_state_change_notification( - State notify_when, util::UniqueFunction, util::Optional)> callback) const; - - // The query version number used in the sync wire protocol to identify this subscription set to the server. - int64_t version() const; - - // The database version that this subscription set was created at or -1 if Uncommitted. - DB::version_type snapshot_version() const; - - // The current state of this subscription set - State state() const; - - // The error string for this subscription set if any. - StringData error_str() const; - - // Returns the number of subscriptions in the set. - size_t size() const; - - // An iterator interface for finding/working with individual subscriptions. - iterator begin() const; - iterator end() const; - - const Subscription& at(size_t index) const; - - // Returns a pointer to the Subscription matching either the name or Query object, or nullptr if no such - // subscription exists. - const Subscription* find(StringData name) const; - const Subscription* find(const Query& query) const; - - // Returns this query set as extended JSON in a form suitable for transmitting to the server. - std::string to_ext_json() const; - - // Reloads the state of this SubscriptionSet so that it reflects the latest state from synchronizing with the - // server. This will invalidate all iterators. - void refresh(); - -protected: - friend class SubscriptionStore; - struct SupersededTag {}; - using MakingMutableCopy = util::TaggedBool; - - explicit SubscriptionSet(std::weak_ptr mgr, int64_t version, SupersededTag); - explicit SubscriptionSet(std::weak_ptr mgr, const Transaction& tr, const Obj& obj, - MakingMutableCopy making_mutable_copy = false); - - void load_from_database(const Obj& obj); - void import(SubscriptionSet&&); - - // Get a reference to the SubscriptionStore. It may briefly extend the lifetime of the store. - std::shared_ptr get_flx_subscription_store() const; - - std::weak_ptr m_mgr; - - DB::version_type m_cur_version = 0; - int64_t m_version = 0; - State m_state = State::Uncommitted; - std::string m_error_str; - DB::version_type m_snapshot_version = -1; - std::vector m_subs; - ObjKey m_obj_key; -}; - -class MutableSubscriptionSet : public SubscriptionSet { -public: - // Erases all subscriptions in the subscription set. - void clear(); - - // Inserts a new subscription into the set if one does not exist already - returns an iterator to the - // subscription and a bool that is true if a new subscription was actually created. The SubscriptionSet - // must be in the Uncommitted state to call this - otherwise this will throw. - // - // The Query portion of the subscription is mutable, however the name portion is immutable after the - // subscription is inserted. - // - // If insert is called twice for the same name, the Query portion and updated_at timestamp for that named - // subscription will be updated to match the new Query. - std::pair insert_or_assign(std::string_view name, const Query& query); - - // Inserts a new subscription into the set if one does not exist already - returns an iterator to the - // subscription and a bool that is true if a new subscription was actually created. The SubscriptionSet - // must be in the Uncommitted state to call this - otherwise this will throw. - // - // If insert is called twice for the same query, then the updated_at timestamp for that subscription will - // be updated. - // - // The inserted subscription will have an empty name - to update this Subscription's query, the caller - // will have - std::pair insert_or_assign(const Query& query); - - void import(SubscriptionSet&&); - - // Erases a subscription pointed to by an iterator. Returns the "next" iterator in the set - to provide - // STL compatibility. The SubscriptionSet must be in the Uncommitted state to call this - otherwise - // this will throw. - iterator erase(iterator it); - - // Erases the subscription identified by the argument, if any. Returns true if anything was removed. - bool erase(StringData name); - bool erase(const Query& query); - - bool erase_by_class_name(StringData object_class_name); - bool erase_by_id(ObjectId id); - - // This commits any changes to the subscription set and returns an this subscription set as an immutable view - // from after the commit. This MutableSubscriptionSet object must not be used after calling commit(). - SubscriptionSet commit(); - - // For testing and internal usage only. - void set_state(State new_state); - -protected: - friend class SubscriptionStore; - // Allow the MigrationStore access to insert_sub because it cannot use insert_or_assign due to having the query as - // a string and not a Query object. - friend class MigrationStore; - - MutableSubscriptionSet(std::weak_ptr mgr, TransactionRef tr, Obj obj); - - void insert_sub(const Subscription& sub); - -private: - // To refresh a MutableSubscriptionSet, you should call commit() and call refresh() on its return value. - void refresh() = delete; - - std::pair insert_or_assign_impl(iterator it, util::Optional name, - std::string object_class_name, std::string query_str); - // Throws is m_tr is in the wrong state. - void check_is_mutable() const; - - void insert_sub_impl(ObjectId id, Timestamp created_at, Timestamp updated_at, StringData name, - StringData object_class_name, StringData query_str); - - TransactionRef m_tr; - Obj m_obj; -}; - -class SubscriptionStore; -using SubscriptionStoreRef = std::shared_ptr; - -// A SubscriptionStore manages the FLX metadata tables, SubscriptionSets and Subscriptions. -class SubscriptionStore : public std::enable_shared_from_this { - struct Private {}; - -public: - static SubscriptionStoreRef create(DBRef db); - - explicit SubscriptionStore(Private, DBRef db); - SubscriptionStore(const SubscriptionStore&) = delete; - SubscriptionStore& operator=(const SubscriptionStore&) = delete; - - // Get the latest subscription created by calling update_latest(). Once bootstrapping is complete, - // this and get_active() will return the same thing. If no SubscriptionSet has been set, then - // this returns an empty SubscriptionSet that you can clone() in order to mutate. - SubscriptionSet get_latest(); - - // Gets the subscription set that has been acknowledged by the server as having finished bootstrapping. - // If no subscriptions have reached the complete stage, this returns an empty subscription with version - // zero. - SubscriptionSet get_active(); - - struct VersionInfo { - int64_t latest; - int64_t active; - int64_t pending_mark; - }; - // Returns the version number of the current active and latest subscription sets. This function guarantees - // that the versions will be read from the same underlying transaction and will thus be consistent. - VersionInfo get_version_info() const; - - // To be used internally by the sync client. This returns a read-only view of a subscription set by its - // version ID. If there is no SubscriptionSet with that version ID, this throws KeyNotFound. - SubscriptionSet get_by_version(int64_t version_id) REQUIRES(!m_pending_notifications_mutex); - - // Returns true if there have been commits to the DB since the given version - bool would_refresh(DB::version_type version) const noexcept; - - using TableSet = std::set>; - TableSet get_tables_for_latest(const Transaction& tr) const; - - struct PendingSubscription { - int64_t query_version; - DB::version_type snapshot_version; - }; - - util::Optional get_next_pending_version(int64_t last_query_version) const; - std::vector get_pending_subscriptions() REQUIRES(!m_pending_notifications_mutex); - - // Mark query_version as having received an error from the server. Will - // throw an exception if the version is not in a state where an error is - // expected (i.e. if it's already completed or superseded). - // - // This should only be called internally within the sync client. - void set_error(int64_t query_version, std::string_view error_str); - // Mark query_version as having begun bootstrapping. This should be called - // inside the write transaction used to store the first set of changesets. - // Has no effect if the version is already complete. Throws if the version - // is superseded or errored. - // - // This should only be called internally within the sync client. - void begin_bootstrap(const Transaction&, int64_t query_version); - // Mark query_version as having completed bootstrapping. This should be - // called inside the write transaction which removes the final pending changeset. - // Has no effect if the version is already complete. Throws if the version - // is superseded or errored. - // - // This should only be called internally within the sync client. - void complete_bootstrap(const Transaction&, int64_t query_version); - // Roll query_version back to the Pending state if it is currently Bootstrapping. - // Has no effect if the bootstrap in progress is not the first boostrap for - // this subscription set. - // - // This should only be called internally within the sync client. - void cancel_bootstrap(const Transaction&, int64_t query_version); - // Report that a download has completed, meaning that the active subscription - // set should advance to the Completed state if it is currently in the - // AwaitingMark state. Has no effect if it is in any other state. - // - // This should only be called internally within the sync client. - void download_complete(); - - // If there are any notifications registered, check if they have been completed - // and fulfill them if so. - void report_progress() REQUIRES(!m_pending_notifications_mutex); - void report_progress(TransactionRef& tr) REQUIRES(!m_pending_notifications_mutex); - - // Get the query version which we most recently received a DOWNLOAD message - // for (which may be distinct from both the latest and active versions). - int64_t get_downloading_query_version(Transaction& rt) const; - - // Mark the currently active subscription set as being complete without going - // through the normal bootstrapping flow. Used for client resets where we - // copy the data for the subscription over from the fresh Realm. - int64_t mark_active_as_complete(Transaction& wt) REQUIRES(!m_pending_notifications_mutex); - - // Notify all subscription state change notification handlers on this subscription store with the - // provided Status - this does not change the state of any pending subscriptions. - // Does not necessarily need to be called from the event loop thread. - void notify_all_state_change_notifications(Status status) REQUIRES(!m_pending_notifications_mutex); - - // Reset SubscriptionStore and erase all current subscriptions and supersede any pending - // subscriptions. Must be called from the event loop thread to prevent data race issues - // with the subscription store. - void reset(Transaction& wt) REQUIRES(!m_pending_notifications_mutex); - - // Recreate the active subscription set, marking any newer pending ones as - // superseded. This is a no-op if there are no pending subscription sets. - int64_t set_active_as_latest(Transaction& wt) REQUIRES(!m_pending_notifications_mutex); - -private: - using State = SubscriptionSet::State; - using std::enable_shared_from_this::weak_from_this; - DBRef m_db; - - struct NotificationRequest { - NotificationRequest(int64_t version, util::Promise promise, - SubscriptionSet::State notify_when) - : version(version) - , promise(std::move(promise)) - , notify_when(notify_when) - { - } - - int64_t version; - util::Promise promise; - SubscriptionSet::State notify_when; - }; - - Obj get_active(const Transaction& tr); - SubscriptionSet get_refreshed(ObjKey, int64_t flx_version, std::optional version = util::none); - MutableSubscriptionSet make_mutable_copy(const SubscriptionSet& set); - - // Ensure the subscriptions table is properly initialized. No-op if already initialized. - void initialize_subscriptions_table(TransactionRef&& tr); - // Clear the table and reinitialize it. - void clear(Transaction& wt); - void do_complete_bootstrap(const Transaction&, int64_t query_version, SubscriptionSet::State new_state); - - friend class MutableSubscriptionSet; - friend class Subscription; - friend class SubscriptionSet; - - TableKey m_sub_table; - ColKey m_sub_id; - ColKey m_sub_created_at; - ColKey m_sub_updated_at; - ColKey m_sub_name; - ColKey m_sub_object_class_name; - ColKey m_sub_query_str; - - TableKey m_sub_set_table; - ColKey m_sub_set_version_num; - ColKey m_sub_set_snapshot_version; - ColKey m_sub_set_state; - ColKey m_sub_set_error_str; - ColKey m_sub_set_subscriptions; - - util::CheckedMutex m_pending_notifications_mutex; - std::list m_pending_notifications GUARDED_BY(m_pending_notifications_mutex); -}; - -} // namespace realm::sync - -#endif // REALM_SYNC_SUBSCRIPTIONS_HPP diff --git a/src/realm/sync/tools/CMakeLists.txt b/src/realm/sync/tools/CMakeLists.txt deleted file mode 100644 index 2740c288559..00000000000 --- a/src/realm/sync/tools/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ - -add_executable(InspectorInspectClientRealm "inspect_client_realm.cpp") -set_target_properties(InspectorInspectClientRealm PROPERTIES - OUTPUT_NAME "realm-inspect-client-realm" - DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}") -target_link_libraries(InspectorInspectClientRealm Sync Storage) - -add_executable(InspectorPrintChangeset "print_changeset.cpp") -set_target_properties(InspectorPrintChangeset PROPERTIES - OUTPUT_NAME "realm-print-changeset" - DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}") -target_link_libraries(InspectorPrintChangeset Sync Storage) - -add_executable(ApplyToStateCommand apply_to_state_command.cpp) -set_target_properties(ApplyToStateCommand PROPERTIES - OUTPUT_NAME "realm-apply-to-state" - DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}") -target_link_libraries(ApplyToStateCommand Sync Storage) - -add_executable(HistCommand hist_command.cpp) -set_target_properties(HistCommand PROPERTIES - OUTPUT_NAME "realm-hist" - DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}") -target_link_libraries(HistCommand Sync Storage) - -install(TARGETS - HistCommand - ApplyToStateCommand - InspectorInspectClientRealm - InspectorPrintChangeset - COMPONENT runtime - DESTINATION bin -) diff --git a/src/realm/sync/tools/apply_to_state_command.cpp b/src/realm/sync/tools/apply_to_state_command.cpp deleted file mode 100644 index 49b52c62b9a..00000000000 --- a/src/realm/sync/tools/apply_to_state_command.cpp +++ /dev/null @@ -1,323 +0,0 @@ -#include "realm/db.hpp" -#include "realm/transaction.hpp" -#include "realm/sync/history.hpp" -#include "realm/sync/instruction_applier.hpp" -#include "realm/sync/impl/clamped_hex_dump.hpp" -#include "realm/sync/noinst/client_history_impl.hpp" -#include "realm/sync/noinst/protocol_codec.hpp" -#include "realm/sync/protocol.hpp" -#include "realm/sync/transform.hpp" -#include "realm/sync/changeset_parser.hpp" -#include "realm/util/cli_args.hpp" -#include "realm/util/compression.hpp" -#include "realm/util/load_file.hpp" -#include "realm/util/safe_int_ops.hpp" - -#include - -#include -#include -#include -#include -#include - -namespace { -using namespace realm; -using namespace realm::util; -using namespace realm::_impl; - -struct ServerIdentMessage { - realm::sync::session_ident_type session_ident; - realm::sync::SaltedFileIdent file_ident; - - static ServerIdentMessage parse(HeaderLineParser& msg); -}; - -struct DownloadMessage { - realm::sync::session_ident_type session_ident; - realm::sync::SyncProgress progress; - realm::sync::SaltedVersion latest_server_version; - uint64_t downloadable_bytes; - realm::sync::DownloadBatchState batch_state; - int64_t query_version; - - Buffer uncompressed_body_buffer; - std::vector changesets; - - static DownloadMessage parse(HeaderLineParser& msg, Logger& logger, bool is_flx_sync); -}; - -struct UploadMessage { - realm::sync::session_ident_type session_ident; - realm::sync::UploadCursor upload_progress; - realm::sync::version_type locked_server_version; - - Buffer uncompressed_body_buffer; - std::vector changesets; - - static UploadMessage parse(HeaderLineParser& msg, Logger& logger); -}; - -using Message = mpark::variant; - -Message parse_message(HeaderLineParser& msg, Logger& logger, bool is_flx_sync) -{ - auto message_type = msg.read_next(); - if (message_type == "download") { - return DownloadMessage::parse(msg, logger, is_flx_sync); - } - else if (message_type == "upload") { - return UploadMessage::parse(msg, logger); - } - else if (message_type == "ident") { - return ServerIdentMessage::parse(msg); - } - throw ProtocolCodecException("could not find valid message in input"); -} - -ServerIdentMessage ServerIdentMessage::parse(HeaderLineParser& msg) -{ - ServerIdentMessage ret; - ret.session_ident = msg.read_next(); - ret.file_ident.ident = msg.read_next(); - ret.file_ident.salt = msg.read_next('\n'); - - return ret; -} - -DownloadMessage DownloadMessage::parse(HeaderLineParser& msg, Logger& logger, bool is_flx_sync) -{ - DownloadMessage ret; - - ret.session_ident = msg.read_next(); - ret.progress.download.server_version = msg.read_next(); - ret.progress.download.last_integrated_client_version = msg.read_next(); - ret.progress.latest_server_version.version = msg.read_next(); - ret.progress.latest_server_version.salt = msg.read_next(); - ret.progress.upload.client_version = msg.read_next(); - ret.progress.upload.last_integrated_server_version = msg.read_next(); - if (is_flx_sync) { - ret.query_version = msg.read_next(); - ret.batch_state = static_cast(msg.read_next()); - } - else { - ret.query_version = 0; - ret.batch_state = sync::DownloadBatchState::SteadyState; - } - ret.downloadable_bytes = msg.read_next(); - auto is_body_compressed = msg.read_next(); - auto uncompressed_body_size = msg.read_next(); - auto compressed_body_size = msg.read_next('\n'); - - logger.trace(util::LogCategory::changeset, - "decoding download message. " - "{download: {server: %1, client: %2} upload: {server: %3, client: %4}, latest: %5}", - ret.progress.download.server_version, ret.progress.download.last_integrated_client_version, - ret.progress.upload.last_integrated_server_version, ret.progress.upload.client_version, - ret.latest_server_version.version); - - std::string_view body_str; - if (is_body_compressed) { - ret.uncompressed_body_buffer.set_size(uncompressed_body_size); - auto compressed_body = msg.read_sized_data(compressed_body_size); - std::error_code ec = util::compression::decompress(compressed_body, ret.uncompressed_body_buffer); - - if (ec) { - throw ProtocolCodecException("error decompressing download message"); - } - - body_str = std::string_view(ret.uncompressed_body_buffer.data(), uncompressed_body_size); - } - else { - body_str = msg.read_sized_data(uncompressed_body_size); - } - - HeaderLineParser body(body_str); - while (!body.at_end()) { - realm::sync::RemoteChangeset cur_changeset; - cur_changeset.remote_version = body.read_next(); - cur_changeset.last_integrated_local_version = body.read_next(); - cur_changeset.origin_timestamp = body.read_next(); - cur_changeset.origin_file_ident = body.read_next(); - cur_changeset.original_changeset_size = body.read_next(); - auto changeset_size = body.read_next(); - - realm::sync::Changeset parsed_changeset; - auto changeset_data = body.read_sized_data(changeset_size); - auto changeset_stream = realm::util::SimpleInputStream(changeset_data); - realm::sync::parse_changeset(changeset_stream, parsed_changeset); - logger.trace(util::LogCategory::changeset, - "found download changeset: serverVersion: %1, clientVersion: %2, origin: %3 %4", - cur_changeset.remote_version, cur_changeset.last_integrated_local_version, - cur_changeset.origin_file_ident, parsed_changeset); - cur_changeset.data = changeset_data; - ret.changesets.push_back(cur_changeset); - } - - return ret; -} - -UploadMessage UploadMessage::parse(HeaderLineParser& msg, Logger& logger) -{ - UploadMessage ret; - - ret.session_ident = msg.read_next(); - auto is_body_compressed = msg.read_next(); - auto uncompressed_body_size = msg.read_next(); - auto compressed_body_size = msg.read_next(); - ret.upload_progress.client_version = msg.read_next(); - ret.upload_progress.last_integrated_server_version = msg.read_next(); - ret.locked_server_version = msg.read_next('\n'); - - // if is_body_compressed == true, we must decompress the received body. - std::string_view body_str; - if (is_body_compressed) { - ret.uncompressed_body_buffer.set_size(uncompressed_body_size); - auto compressed_body = msg.read_sized_data(compressed_body_size); - std::error_code ec = util::compression::decompress(compressed_body, ret.uncompressed_body_buffer); - - if (ec) { - throw ProtocolCodecException("error decompressing upload message"); - } - - body_str = std::string_view(ret.uncompressed_body_buffer.data(), uncompressed_body_size); - } - else { - body_str = msg.read_sized_data(uncompressed_body_size); - } - - HeaderLineParser body(body_str); - while (!body.at_end()) { - realm::sync::Changeset cur_changeset; - cur_changeset.version = body.read_next(); - cur_changeset.last_integrated_remote_version = body.read_next(); - cur_changeset.origin_timestamp = body.read_next(); - cur_changeset.origin_file_ident = body.read_next(); - auto changeset_size = body.read_next(); - - auto changeset_buffer = body.read_sized_data(changeset_size); - - logger.trace(util::LogCategory::changeset, "found upload changeset: %1 %2 %3 %4 %5", - cur_changeset.last_integrated_remote_version, cur_changeset.version, - cur_changeset.origin_timestamp, cur_changeset.origin_file_ident, changeset_size); - realm::util::SimpleInputStream changeset_stream(changeset_buffer); - try { - realm::sync::parse_changeset(changeset_stream, cur_changeset); - } - catch (...) { - logger.error("error decoding changeset after instructions %1", cur_changeset); - throw; - } - logger.trace(util::LogCategory::changeset, "Decoded changeset: %1", cur_changeset); - ret.changesets.push_back(std::move(cur_changeset)); - } - - return ret; -} - -void print_usage(std::string_view program_name) -{ - std::cout << "Synopsis: " << program_name << " -r -i [OPTIONS]" - << "\n" - "Options:\n" - " -h, --help Display command-line synopsis followed by the list of\n" - " available options.\n" - " -e, --encryption-key The file-system path of a file containing a 64-byte\n" - " encryption key to be used for accessing the specified\n" - " Realm file.\n" - " -r, --realm The file-system path to the realm to be created and/or have\n" - " state applied to.\n" - " -i, --input The file-system path a file containing UPLOAD, DOWNLOAD,\n" - " and IDENT messages to apply to the realm state\n" - " -f, --flx-sync Flexible sync session\n" - " --verbose Print all messages including trace messages to stderr\n" - " -v, --version Show the version of the Realm Sync release that this\n" - " command belongs to." - << std::endl; -} - -} // namespace - -int main(int argc, const char** argv) -{ - CliArgumentParser arg_parser; - CliFlag help_arg(arg_parser, "help", 'h'); - CliArgument realm_arg(arg_parser, "realm", 'r'); - CliArgument encryption_key_arg(arg_parser, "encryption-key", 'e'); - CliArgument input_arg(arg_parser, "input", 'i'); - CliFlag verbose_arg(arg_parser, "verbose"); - CliFlag flx_sync_arg(arg_parser, "flx-sync", 'f'); - auto arg_results = arg_parser.parse(argc, argv); - - std::unique_ptr logger = - std::make_unique(verbose_arg ? Logger::Level::all : Logger::Level::error); - - if (help_arg) { - print_usage(arg_results.program_name); - return EXIT_SUCCESS; - } - - if (!realm_arg) { - logger->error("missing path to realm to apply changesets to"); - print_usage(arg_results.program_name); - return EXIT_FAILURE; - } - if (!input_arg) { - logger->error("missing path to messages to apply to realm"); - print_usage(arg_results.program_name); - return EXIT_FAILURE; - } - auto realm_path = realm_arg.as(); - - std::string encryption_key; - if (encryption_key_arg) { - encryption_key = load_file(encryption_key_arg.as()); - } - - realm::DBOptions db_opts(encryption_key.empty() ? nullptr : encryption_key.c_str()); - realm::sync::ClientReplication repl{}; - auto local_db = realm::DB::create(repl, realm_path, db_opts); - auto& history = repl.get_history(); - - auto input_contents = load_file(input_arg.as()); - HeaderLineParser msg(input_contents); - while (!msg.at_end()) { - Message message; - try { - message = parse_message(msg, *logger, bool(flx_sync_arg)); - } - catch (const ProtocolCodecException& e) { - logger->error("Error parsing input message file: %1", e.what()); - return EXIT_FAILURE; - } - - mpark::visit(realm::util::overload{ - [&](const DownloadMessage& download_message) { - realm::sync::VersionInfo version_info; - auto transact = bool(flx_sync_arg) ? local_db->start_write() : local_db->start_read(); - history.integrate_server_changesets(download_message.progress, - download_message.downloadable_bytes, - download_message.changesets, version_info, - download_message.batch_state, *logger, transact); - }, - [&](const UploadMessage& upload_message) { - for (const auto& changeset : upload_message.changesets) { - history.set_local_origin_timestamp_source([&]() { - return changeset.origin_timestamp; - }); - auto transaction = local_db->start_write(); - realm::sync::InstructionApplier applier(*transaction); - applier.apply(changeset); - auto generated_version = transaction->commit(); - logger->debug("integrated local changesets as version %1", generated_version); - history.set_local_origin_timestamp_source(realm::sync::generate_changeset_timestamp); - } - }, - [&](const ServerIdentMessage& ident_message) { - history.set_client_file_ident(ident_message.file_ident, true); - }}, - message); - } - - return EXIT_SUCCESS; -} diff --git a/src/realm/sync/tools/hist_command.cpp b/src/realm/sync/tools/hist_command.cpp deleted file mode 100644 index 31656ede289..00000000000 --- a/src/realm/sync/tools/hist_command.cpp +++ /dev/null @@ -1,2385 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace realm; -using util::TimestampFormatter; -using IntegerBpTree = BPlusTree; // FIXME: Avoid use of optional type `std::int64_t` -using sync::file_ident_type; -using sync::salt_type; -using sync::SaltedFileIdent; -using sync::timestamp_type; -using sync::UploadCursor; -using sync::version_type; -using ClientType = _impl::ServerHistory::ClientType; - -// FIXME: Ideas for additional forms of filtering: -// - Filter to class (only changesets that refer to an object of this class or -// to the class itself). -// - Filter to origin time range (only changesets that originate in the -// specified time range). - - -namespace { - -enum class Format { auto_, nothing, version, info, annotate, changeset, hexdump, raw }; -enum class Summary { auto_, off, brief, full }; - -struct FormatSpec { - static util::EnumAssoc map[]; -}; -util::EnumAssoc FormatSpec::map[] = { - {int(Format::auto_), "auto"}, {int(Format::nothing), "nothing"}, {int(Format::version), "version"}, - {int(Format::info), "info"}, {int(Format::annotate), "annotate"}, {int(Format::changeset), "changeset"}, - {int(Format::hexdump), "hexdump"}, {int(Format::raw), "raw"}, {0, nullptr}}; -using FormatEnum = util::Enum; - -struct SummarySpec { - static util::EnumAssoc map[]; -}; -util::EnumAssoc SummarySpec::map[] = {{int(Summary::auto_), "auto"}, - {int(Summary::off), "off"}, - {int(Summary::brief), "brief"}, - {int(Summary::full), "full"}, - {0, nullptr}}; -using SummaryEnum = util::Enum; - -struct InstructionTypeSpec { - static util::EnumAssoc map[]; -}; - -util::EnumAssoc InstructionTypeSpec::map[] = {{int(sync::Instruction::Type::AddTable), "AddTable"}, - {int(sync::Instruction::Type::EraseTable), "EraseTable"}, - {int(sync::Instruction::Type::CreateObject), "CreateObject"}, - {int(sync::Instruction::Type::EraseObject), "EraseObject"}, - {int(sync::Instruction::Type::Update), "Update"}, - {int(sync::Instruction::Type::AddInteger), "AddInteger"}, - {int(sync::Instruction::Type::AddColumn), "AddColumn"}, - {int(sync::Instruction::Type::EraseColumn), "EraseColumn"}, - {int(sync::Instruction::Type::ArrayInsert), "ArrayInsert"}, - {int(sync::Instruction::Type::ArrayMove), "ArrayMove"}, - {int(sync::Instruction::Type::ArrayErase), "ArrayErase"}, - {int(sync::Instruction::Type::Clear), "Clear"}, - {int(sync::Instruction::Type::SetInsert), "SetInsert"}, - {int(sync::Instruction::Type::SetErase), "SetErase"}, - {0, nullptr}}; -using InstructionTypeEnum = util::Enum; - - -template -std::string format_num_something(T num, const char* singular_form, const char* plural_form, - std::locale loc = std::locale{}) -{ - using lim = std::numeric_limits; - bool need_singular = (num == T(1) || (lim::is_signed && num == T(-1))); - const char* form = (need_singular ? singular_form : plural_form); - std::ostringstream out; - out.imbue(loc); - out << num << " " << form; - return std::move(out).str(); -} - -std::string format_num_history_entries(std::size_t num) -{ - return format_num_something(num, "history entry", "history entries"); -} - - -bool get_changeset_size(const BinaryColumn& col, std::size_t row_ndx, std::size_t& size) noexcept -{ - std::size_t size_2 = 0; - std::size_t pos = 0; - BinaryData chunk = col.get_at(row_ndx, pos); - if (!chunk.is_null()) { - for (;;) { - size_2 += chunk.size(); - if (pos == 0) { - size = size_2; - return true; - } - chunk = col.get_at(row_ndx, pos); - } - } - return false; -} - - -bool get_changeset(const BinaryColumn& col, std::size_t row_ndx, util::AppendBuffer& buffer) -{ - std::size_t pos = 0; - BinaryData chunk = col.get_at(row_ndx, pos); - if (!chunk.is_null()) { - for (;;) { - buffer.append(chunk.data(), chunk.size()); // Throws - if (pos == 0) - return true; - chunk = col.get_at(row_ndx, pos); - } - } - return false; -} - - -enum class LogicalClientType { - special, - upstream, - self, - indirect, - regular, - subserver, - legacy, -}; - - -void all_client_files(std::set& types) -{ - types = {LogicalClientType::special, LogicalClientType::upstream, LogicalClientType::self, - LogicalClientType::indirect, LogicalClientType::regular, LogicalClientType::subserver, - LogicalClientType::legacy}; -} - - -bool parse_client_types(std::string_view string, std::set& types) -{ - std::set types_2; - for (char ch : string) { - switch (ch) { - case 'r': - types_2.insert(LogicalClientType::regular); - continue; - case 's': - types_2.insert(LogicalClientType::subserver); - continue; - case 'l': - types_2.insert(LogicalClientType::legacy); - continue; - case 'i': - types_2.insert(LogicalClientType::indirect); - continue; - case 'u': - types_2.insert(LogicalClientType::upstream); - continue; - case 'S': - types_2.insert(LogicalClientType::self); - continue; - case 'U': - types_2.insert(LogicalClientType::special); - continue; - } - return false; - } - types = types_2; - return true; -} - - -class Expr { -public: - using InternString = sync::InternString; - using Instruction = sync::Instruction; - using Payload = Instruction::Payload; - using Changeset = sync::Changeset; - - struct InstrInfo { - Instruction::Type type; - InternString class_name; - sync::instr::PrimaryKey object_id; - InternString property; - const Payload* payload; - - bool is_modification() const noexcept - { - return true; - } - }; - - virtual void reset(const Changeset&) noexcept {} - - virtual bool eval(const InstrInfo&) const noexcept = 0; - - virtual ~Expr() noexcept {} -}; - - -class InstructionTypeExpr : public Expr { -public: - InstructionTypeExpr(Instruction::Type type) noexcept - : m_type{type} - { - } - - bool eval(const InstrInfo& instr) const noexcept override final - { - return (instr.type == m_type); - } - -private: - const Instruction::Type m_type; -}; - - -class ModifiesClassExpr : public Expr { -public: - ModifiesClassExpr(std::string class_name) noexcept - : m_class_name{std::move(class_name)} - { - } - - void reset(const Changeset& changeset) noexcept override - { - m_interned_class_name = changeset.find_string(m_class_name); - } - - bool eval(const InstrInfo& instr) const noexcept override - { - return (instr.class_name && instr.class_name == m_interned_class_name && instr.is_modification()); - } - -private: - const std::string m_class_name; - InternString m_interned_class_name; -}; - - -class ModifiesObjectExpr : public ModifiesClassExpr { -public: - ModifiesObjectExpr(std::string class_name, sync::instr::PrimaryKey object_id) noexcept - : ModifiesClassExpr{std::move(class_name)} - , m_object_id{std::move(object_id)} - { - } - - bool eval(const InstrInfo& instr) const noexcept override - { - return (ModifiesClassExpr::eval(instr) && (instr.object_id != sync::instr::PrimaryKey{mpark::monostate{}}) && - instr.object_id == m_object_id); - } - -private: - const sync::instr::PrimaryKey m_object_id; -}; - - -class ModifiesPropertyExpr : public ModifiesObjectExpr { -public: - ModifiesPropertyExpr(std::string class_name, sync::instr::PrimaryKey object_id, std::string property) noexcept - : ModifiesObjectExpr{std::move(class_name), std::move(object_id)} - , m_property{std::move(property)} - { - } - - void reset(const Changeset& changeset) noexcept override final - { - ModifiesObjectExpr::reset(changeset); - m_interned_property = changeset.find_string(m_property); - } - - bool eval(const InstrInfo& instr) const noexcept override final - { - return (ModifiesObjectExpr::eval(instr) && instr.property && instr.property == m_interned_property); - } - -private: - const std::string m_property; - InternString m_interned_property; -}; - - -class LinksToObjectExpr : public Expr { -public: - LinksToObjectExpr(std::string class_name, sync::instr::PrimaryKey object_id) noexcept - : m_class_name{std::move(class_name)} - , m_object_id{std::move(object_id)} - { - } - - void reset(const Changeset& changeset) noexcept override - { - m_interned_class_name = changeset.find_string(m_class_name); - } - - bool eval(const InstrInfo& instr) const noexcept override - { - return (instr.payload && instr.payload->type == Payload::Type::Link && - instr.payload->data.link.target_table == m_interned_class_name && - instr.payload->data.link.target == m_object_id); - } - -private: - const std::string m_class_name; - const sync::instr::PrimaryKey m_object_id; - InternString m_interned_class_name; -}; - - -class AndExpr : public Expr { -public: - AndExpr(std::unique_ptr left, std::unique_ptr right) noexcept - : m_left{std::move(left)} - , m_right{std::move(right)} - { - } - - void reset(const Changeset& changeset) noexcept override - { - m_left->reset(changeset); - m_right->reset(changeset); - } - - bool eval(const InstrInfo& instr) const noexcept override - { - return (m_left->eval(instr) && m_right->eval(instr)); - } - -private: - const std::unique_ptr m_left, m_right; -}; - - -class InstructionMatcher { -public: - using InternString = sync::InternString; - using Instruction = sync::Instruction; - using Payload = Instruction::Payload; - - explicit InstructionMatcher(const Expr& expression) noexcept - : m_expression{expression} - { - } - - bool operator()(Instruction::AddTable& instr) noexcept - { - m_selected_class_name = instr.table; - return modify_class(Instruction::Type::AddTable); - } - - bool operator()(Instruction::EraseTable& instr) noexcept - { - m_selected_class_name = instr.table; - return modify_class(Instruction::Type::EraseTable); - } - - bool operator()(Instruction::AddColumn&) noexcept - { - return modify_class(Instruction::Type::AddColumn); - } - - bool operator()(Instruction::EraseColumn&) noexcept - { - return modify_class(Instruction::Type::EraseColumn); - } - - bool operator()(Instruction::CreateObject& instr) noexcept - { - return modify_object(Instruction::Type::CreateObject, instr.object); - } - - bool operator()(Instruction::EraseObject& instr) noexcept - { - return modify_object(Instruction::Type::EraseObject, instr.object); - } - - bool operator()(Instruction::Update& instr) noexcept - { - return modify_object(Instruction::Type::Update, instr.object); - } - - bool operator()(Instruction::AddInteger&) noexcept - { - return modify_property(Instruction::Type::AddInteger); - } - - bool operator()(Instruction::ArrayInsert& instr) noexcept - { - return modify_object(Instruction::Type::ArrayInsert, instr.object); - } - - bool operator()(Instruction::ArrayMove&) noexcept - { - return modify_property(Instruction::Type::ArrayMove); - } - - bool operator()(Instruction::ArrayErase&) noexcept - { - return modify_property(Instruction::Type::ArrayErase); - } - - bool operator()(Instruction::Clear&) noexcept - { - return modify_property(Instruction::Type::Clear); - } - - bool operator()(Instruction::SetInsert&) noexcept - { - return modify_property(Instruction::Type::SetInsert); - } - - bool operator()(Instruction::SetErase&) noexcept - { - return modify_property(Instruction::Type::SetErase); - } - -private: - const Expr& m_expression; - InternString m_selected_class_name; - sync::instr::PrimaryKey m_selected_object_id; - InternString m_selected_property; - - bool modify_class(Instruction::Type instruction_type) const noexcept - { - GlobalKey object_id; - return modify_object(instruction_type, object_id); - } - - bool modify_object(Instruction::Type instruction_type, sync::instr::PrimaryKey object_id, - const Payload* payload = nullptr) const noexcept - { - InternString property; - Expr::InstrInfo info = {instruction_type, m_selected_class_name, object_id, property, payload}; - return m_expression.eval(info); - } - - bool modify_property(Instruction::Type instruction_type, const Payload* payload = nullptr) const noexcept - { - Expr::InstrInfo info = {instruction_type, m_selected_class_name, m_selected_object_id, m_selected_property, - payload}; - return m_expression.eval(info); - } -}; - - -class SyncHistoryCursor { -public: - virtual ~SyncHistoryCursor() = default; - virtual bool next() = 0; - virtual version_type get_version() const = 0; - virtual file_ident_type get_origin_file() const = 0; - virtual timestamp_type get_origin_timestamp() const = 0; - virtual void print_info(std::ostream&) const = 0; - virtual void print_annotated_info(std::ostream&, TimestampFormatter&) const = 0; - virtual void get_changeset(util::AppendBuffer&) const = 0; -}; - - -class ClientFilesCursor { -public: - virtual ~ClientFilesCursor() = default; - virtual bool next() = 0; - virtual LogicalClientType get_logical_client_type() const = 0; - virtual ClientType get_client_type() const = 0; - virtual std::time_t get_last_seen_timestamp() const = 0; - virtual version_type get_locked_version() const = 0; - virtual void print_annotated_info(std::ostream&, TimestampFormatter&) const = 0; -}; - - -class CursorFactory { -public: - using HistoryCursorPtr = std::unique_ptr; - using ClientFilesCursorPtr = std::unique_ptr; - - virtual ~CursorFactory() = default; - - virtual HistoryCursorPtr create_history_cursor(util::Optional reciprocal) = 0; - virtual HistoryCursorPtr create_history_cursor(util::Optional reciprocal, - version_type version) = 0; - virtual HistoryCursorPtr create_history_cursor(util::Optional reciprocal, - version_type begin_version, version_type end_version) = 0; - - virtual ClientFilesCursorPtr create_client_files_cursor() = 0; - virtual ClientFilesCursorPtr create_client_files_cursor(file_ident_type client_file) = 0; -}; - - -class RegularSyncHistoryCursor : public SyncHistoryCursor { -public: - virtual bool reciprocal(file_ident_type recip_file_ident) = 0; - - void init() - { - m_begin_version = m_base_version; - m_end_version = m_last_version; - m_curr_version = m_begin_version; - } - - bool init(version_type version) - { - if (version <= m_base_version || version > m_last_version) { - std::cerr << "ERROR: Specified version is out of range\n"; // Throws - return false; - } - m_begin_version = version_type(version - 1); - m_end_version = version; - m_curr_version = m_begin_version; - return true; - } - - bool init(version_type begin_version, version_type end_version) - { - if (begin_version < m_base_version || begin_version > m_last_version) { - std::cerr << "ERROR: Specified begin version is out of range\n"; // Throws - return false; - } - if (end_version < begin_version || end_version > m_last_version) { - std::cerr << "ERROR: Specified end version is out of range\n"; // Throws - return false; - } - m_begin_version = begin_version; - m_end_version = end_version; - m_curr_version = m_begin_version; - return true; - } - - bool next() override final - { - if (m_curr_version < m_end_version) { - ++m_curr_version; - return true; - } - return false; - } - - version_type get_version() const override final - { - get_history_entry_index(); // Throws - return m_curr_version; - } - -protected: - version_type m_base_version = 0; - version_type m_last_version = 0; - - std::size_t get_history_entry_index() const - { - if (m_curr_version > m_begin_version) - return std::size_t(m_curr_version - m_base_version - 1); - throw std::runtime_error("Bad cursor state"); - } - - version_type get_current_version() const noexcept - { - return m_curr_version; - } - -private: - version_type m_begin_version = 0; - version_type m_end_version = 0; - version_type m_curr_version = 0; -}; - - -class RegularClientFilesCursor : public ClientFilesCursor { -public: - void init() - { - m_begin = 0; - m_end = m_size; - m_next = 0; - } - - bool init(file_ident_type client_file) - { - std::size_t client_file_index = std::size_t(client_file); - if (client_file_index >= m_size) { - std::cerr << "ERROR: Specified client file identifier is out of range\n"; // Throws - return false; - } - m_begin = client_file_index; - m_end = std::size_t(client_file_index + 1); - m_next = client_file_index; - return true; - } - - bool next() override final - { - if (m_next < m_end) { - ++m_next; - return true; - } - return false; - } - -protected: - std::size_t m_size = 0; - - std::size_t get_client_file_index() const - { - if (m_next > m_begin) - return std::size_t(m_next - 1); - throw std::runtime_error("Bad cursor state"); - } - -private: - std::size_t m_begin = 0; - std::size_t m_end = 0; - std::size_t m_next = 0; -}; - - -class RegularCursorFactory : public CursorFactory { -public: - HistoryCursorPtr create_history_cursor(util::Optional reciprocal) override - { - std::unique_ptr cursor = do_create_history_cursor(); // Throws - if (REALM_UNLIKELY(!cursor)) - return nullptr; - if (reciprocal) { - if (REALM_UNLIKELY(!cursor->reciprocal(*reciprocal))) // Throws - return nullptr; - } - cursor->init(); // Throws - return cursor; - } - - HistoryCursorPtr create_history_cursor(util::Optional reciprocal, version_type version) override - { - std::unique_ptr cursor = do_create_history_cursor(); // Throws - if (REALM_UNLIKELY(!cursor)) - return nullptr; - if (reciprocal) { - if (REALM_UNLIKELY(!cursor->reciprocal(*reciprocal))) // Throws - return nullptr; - } - if (REALM_UNLIKELY(!cursor->init(version))) // Throws - return nullptr; - return cursor; - } - - HistoryCursorPtr create_history_cursor(util::Optional reciprocal, version_type begin_version, - version_type end_version) override - { - std::unique_ptr cursor = do_create_history_cursor(); // Throws - if (REALM_UNLIKELY(!cursor)) - return nullptr; - if (reciprocal) { - if (REALM_UNLIKELY(!cursor->reciprocal(*reciprocal))) // Throws - return nullptr; - } - if (REALM_UNLIKELY(!cursor->init(begin_version, end_version))) // Throws - return nullptr; - return cursor; - } - - ClientFilesCursorPtr create_client_files_cursor() override - { - std::unique_ptr cursor = do_create_client_files_cursor(); // Throws - if (REALM_UNLIKELY(!cursor)) - return nullptr; - cursor->init(); // Throws - return cursor; - } - - ClientFilesCursorPtr create_client_files_cursor(file_ident_type client_file) override - { - std::unique_ptr cursor = do_create_client_files_cursor(); // Throws - if (REALM_UNLIKELY(!cursor)) - return nullptr; - if (!cursor->init(client_file)) // Throws - return nullptr; - return cursor; - } - -protected: - virtual std::unique_ptr do_create_history_cursor() = 0; - virtual std::unique_ptr do_create_client_files_cursor() = 0; -}; - - -class NullSyncHistoryCursor : public RegularSyncHistoryCursor { -public: - bool reciprocal(file_ident_type) override final - { - return false; - } - - file_ident_type get_origin_file() const override final - { - get_history_entry_index(); // Throws - return 0; - } - - timestamp_type get_origin_timestamp() const override final - { - get_history_entry_index(); // Throws - return 0; - } - - void print_info(std::ostream&) const override final - { - get_history_entry_index(); // Throws - } - - void print_annotated_info(std::ostream&, TimestampFormatter&) const override final - { - get_history_entry_index(); // Throws - } - - void get_changeset(util::AppendBuffer&) const override final - { - get_history_entry_index(); // Throws - } -}; - - -class NullClientFilesCursor : public RegularClientFilesCursor { -public: - LogicalClientType get_logical_client_type() const override final - { - get_client_file_index(); // Throws - return {}; - } - - ClientType get_client_type() const override final - { - get_client_file_index(); // Throws - return {}; - } - - std::time_t get_last_seen_timestamp() const override final - { - get_client_file_index(); // Throws - return 0; - } - - version_type get_locked_version() const override final - { - get_client_file_index(); // Throws - return 0; - } - - void print_annotated_info(std::ostream&, TimestampFormatter&) const override final - { - get_client_file_index(); // Throws - } -}; - - -class NullCursorFactory : public RegularCursorFactory { -protected: - std::unique_ptr do_create_history_cursor() override - { - return std::make_unique(); // Throws - } - - std::unique_ptr do_create_client_files_cursor() override - { - return std::make_unique(); // Throws - } -}; - - -class ClientHistoryCursor : public RegularSyncHistoryCursor { -public: - ClientHistoryCursor(Allocator& alloc, ref_type root_ref, int schema_version, - version_type current_snapshot_version) - { - REALM_ASSERT(schema_version == 12); - - if (root_ref == 0) - return; - - // Size of fixed-size arrays - std::size_t root_size = 21; - - // Slots in root array of history compartment - std::size_t changesets_iip = 13; - std::size_t reciprocal_transforms_iip = 14; - std::size_t remote_versions_iip = 15; - std::size_t origin_file_idents_iip = 16; - std::size_t origin_timestamps_iip = 17; - - Array root{alloc}; - root.init_from_ref(root_ref); - if (root.size() != root_size) - throw std::runtime_error("Unexpected size of root array of history compartment"); - { - ref_type ref = root.get_as_ref(changesets_iip); - m_changesets.reset(new BinaryColumn(alloc)); // Throws - m_changesets->init_from_ref(ref); - } - { - ref_type ref = root.get_as_ref(reciprocal_transforms_iip); - m_reciprocal_transforms.reset(new BinaryColumn(alloc)); // Throws - m_reciprocal_transforms->init_from_ref(ref); - } - { - m_remote_versions.reset(new IntegerBpTree(alloc)); // Throws - m_remote_versions->set_parent(&root, remote_versions_iip); // Throws - m_remote_versions->init_from_parent(); - } - { - m_origin_file_idents.reset(new IntegerBpTree(alloc)); // Throws - m_origin_file_idents->set_parent(&root, origin_file_idents_iip); // Throws - m_origin_file_idents->init_from_parent(); - } - { - m_origin_timestamps.reset(new IntegerBpTree(alloc)); // Throws - m_origin_timestamps->set_parent(&root, origin_timestamps_iip); // Throws - m_origin_timestamps->init_from_parent(); - } - std::size_t history_size = m_changesets->size(); - m_base_version = version_type(current_snapshot_version - history_size); - m_last_version = current_snapshot_version; - } - - bool reciprocal(file_ident_type recip_file_ident) override final - { - if (recip_file_ident != 0) { - std::cerr << "ERROR: Bad reciprocal file identifier (must be zero)\n"; // Throws - return false; - } - m_reciprocal = true; - return true; - } - - file_ident_type get_origin_file() const override final - { - std::size_t index = get_history_entry_index(); // Throws - return file_ident_type(m_origin_file_idents->get(index)); - } - - timestamp_type get_origin_timestamp() const override final - { - std::size_t index = get_history_entry_index(); // Throws - return timestamp_type(m_origin_timestamps->get(index)); - } - - void print_info(std::ostream& out) const override final - { - std::size_t index = get_history_entry_index(); // Throws - version_type client_version = get_current_version(); - file_ident_type origin_file = file_ident_type(m_origin_file_idents->get(index)); - timestamp_type origin_timestamp = timestamp_type(m_origin_timestamps->get(index)); - version_type server_version = version_type(m_remote_versions->get(index)); - std::size_t changeset_size = get_changeset_size(index); - out << client_version << " " << origin_file << " " << origin_timestamp - << " " - "" - << server_version << " " << changeset_size << "\n"; // Throws - } - - void print_annotated_info(std::ostream& out, TimestampFormatter& timestamp_formatter) const override final - { - std::size_t index = get_history_entry_index(); // Throws - version_type client_version = get_current_version(); - file_ident_type origin_file = file_ident_type(m_origin_file_idents->get(index)); - const char* origin = (origin_file == 0 ? "local" : "remote"); - timestamp_type origin_timestamp = timestamp_type(m_origin_timestamps->get(index)); - std::time_t time = 0; - long nanoseconds = 0; - sync::map_changeset_timestamp(origin_timestamp, time, nanoseconds); - version_type server_version = version_type(m_remote_versions->get(index)); - std::size_t changeset_size = get_changeset_size(index); - out << "Produced client version: " << client_version - << "\n" - "Identifier of origin file: " - << origin_file << " (" << origin - << " origin)\n" - "Origin timestamp: " - << origin_timestamp - << " " - "(" - << timestamp_formatter.format(time, nanoseconds) - << ")\n" - "Last integrated server version: " - << server_version - << "\n" - "Changeset size: " - << changeset_size << "\n"; // Throws - } - - void get_changeset(util::AppendBuffer& buffer) const override final - { - std::size_t index = get_history_entry_index(); // Throws - if (m_reciprocal) { - if (::get_changeset(*m_reciprocal_transforms, index, buffer)) // Throws - return; - } - ::get_changeset(*m_changesets, index, buffer); // Throws - } - -private: - std::unique_ptr m_changesets; - std::unique_ptr m_reciprocal_transforms; - std::unique_ptr m_remote_versions; - std::unique_ptr m_origin_file_idents; - std::unique_ptr m_origin_timestamps; - bool m_reciprocal = false; - - std::size_t get_changeset_size(std::size_t index) const noexcept - { - std::size_t size = 0; - if (m_reciprocal) { - if (::get_changeset_size(*m_reciprocal_transforms, index, size)) - return size; - } - ::get_changeset_size(*m_changesets, index, size); - return size; - } -}; - - -class ClientCursorFactory : public RegularCursorFactory { -public: - ClientCursorFactory(Allocator& alloc, ref_type root_ref, int schema_version, - version_type current_snapshot_version) noexcept - : m_alloc{alloc} - , m_root_ref{root_ref} - , m_schema_version{schema_version} - , m_current_snapshot_version{current_snapshot_version} - { - } - -protected: - std::unique_ptr do_create_history_cursor() override - { - return std::make_unique(m_alloc, m_root_ref, m_schema_version, - m_current_snapshot_version); // Throws - } - - std::unique_ptr do_create_client_files_cursor() override - { - return std::make_unique(); // Throws - } - -private: - Allocator& m_alloc; - const ref_type m_root_ref; - const int m_schema_version; - const version_type m_current_snapshot_version; -}; - - -class ServerHistoryCursor_6_to_10 : public RegularSyncHistoryCursor { -public: - ServerHistoryCursor_6_to_10(Allocator& alloc, ref_type root_ref, int schema_version) - : m_schema_version{schema_version} - , m_root{alloc} - { - REALM_ASSERT(schema_version >= 6 && schema_version <= 10); - - if (root_ref == 0) - return; - - // Size of fixed-size arrays - std::size_t root_size = 11; - std::size_t sync_history_size = 6; - if (schema_version < 8) - root_size = 10; - - // Slots in root array of history compartment - std::size_t history_base_version_iip = 1; - std::size_t sync_history_iip = 3; - - // Slots in root array of `sync_history` table - std::size_t sh_version_salts_iip = 0; - std::size_t sh_origin_files_iip = 1; - std::size_t sh_client_versions_iip = 2; - std::size_t sh_timestamps_iip = 3; - std::size_t sh_changesets_iip = 4; - - m_root.init_from_ref(root_ref); - if (m_root.size() != root_size) - throw std::runtime_error("Unexpected size of root array of history compartment"); - Array sync_history{alloc}; - sync_history.init_from_ref(m_root.get_as_ref_or_tagged(sync_history_iip).get_as_ref()); - if (sync_history.size() != sync_history_size) - throw std::runtime_error("Unexpected size of root array of `sync_history` table"); - { - m_version_salts.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = sync_history.get_as_ref(sh_version_salts_iip); - m_version_salts->init_from_ref(ref); // Throws - } - { - m_origin_files.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = sync_history.get_as_ref(sh_origin_files_iip); - m_origin_files->init_from_ref(ref); // Throws - } - { - m_client_versions.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = sync_history.get_as_ref(sh_client_versions_iip); - m_client_versions->init_from_ref(ref); // Throws - } - { - m_timestamps.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = sync_history.get_as_ref(sh_timestamps_iip); - m_timestamps->init_from_ref(ref); // Throws - } - { - ref_type ref = sync_history.get_as_ref(sh_changesets_iip); - m_changesets.reset(new BinaryColumn(alloc)); // Throws - m_changesets->init_from_ref(ref); - } - std::size_t history_size = m_version_salts->size(); - REALM_ASSERT(m_origin_files->size() == history_size); - REALM_ASSERT(m_client_versions->size() == history_size); - REALM_ASSERT(m_timestamps->size() == history_size); - REALM_ASSERT(m_changesets->size() == history_size); - m_base_version = version_type(m_root.get_as_ref_or_tagged(history_base_version_iip).get_as_int()); - m_last_version = version_type(m_base_version + history_size); - } - - bool reciprocal(file_ident_type recip_file_ident) override final - { - // Size of fixed-size arrays - std::size_t client_files_size = 8; - if (m_schema_version < 8) { - client_files_size = 6; - } - else if (m_schema_version < 10) { - client_files_size = 7; - } - - // Slots in root array of history compartment - std::size_t client_files_iip = 0; - - // Slots in root array of `client_files` table - std::size_t cf_rh_base_versions_iip = 2; - std::size_t cf_recip_hist_refs_iip = 3; - - Allocator& alloc = m_root.get_alloc(); - Array client_files{alloc}; - client_files.init_from_ref(m_root.get_as_ref_or_tagged(client_files_iip).get_as_ref()); - if (client_files.size() != client_files_size) - throw std::runtime_error("Unexpected size of root array of `client_files` table"); - - IntegerBpTree cf_rh_base_versions{alloc}; - { - ref_type ref = client_files.get_as_ref(cf_rh_base_versions_iip); - cf_rh_base_versions.init_from_ref(ref); // Throws - } - IntegerBpTree cf_recip_hist_refs{alloc}; - { - ref_type ref = client_files.get_as_ref(cf_recip_hist_refs_iip); - cf_recip_hist_refs.init_from_ref(ref); // Throws - } - - std::size_t num_client_files = cf_rh_base_versions.size(); - std::size_t client_file_index = std::size_t(recip_file_ident); - bool good_recip_file_ident = (recip_file_ident >= 1 && client_file_index < num_client_files); - if (!good_recip_file_ident) { - std::cerr << "ERROR: Bad reciprocal file identifier\n"; // Throws - return false; - } - - std::size_t recip_hist_size = 0; - { - ref_type ref = ref_type(cf_recip_hist_refs.get(client_file_index)); - if (ref != 0) { - m_recip_hist.reset(new BinaryColumn(alloc)); // Throws - recip_hist_size = m_recip_hist->size(); - m_recip_hist->init_from_ref(ref); - } - } - - version_type recip_hist_base_version = version_type(cf_rh_base_versions.get(client_file_index)); - std::size_t recip_hist_offset = std::size_t(recip_hist_base_version - m_base_version); - - m_base_version = recip_hist_base_version; - m_recip_hist_offset = recip_hist_offset; - m_recip_hist_size = recip_hist_size; - m_reciprocal = true; - return true; - } - - file_ident_type get_origin_file() const override final - { - std::size_t index_1 = get_history_entry_index(); // Throws - std::size_t index_2 = get_real_history_index(index_1); - return file_ident_type(m_origin_files->get(index_2)); - } - - timestamp_type get_origin_timestamp() const override final - { - std::size_t index_1 = get_history_entry_index(); // Throws - std::size_t index_2 = get_real_history_index(index_1); - return timestamp_type(m_timestamps->get(index_2)); - } - - void print_info(std::ostream& out) const override final - { - std::size_t index_1 = get_history_entry_index(); // Throws - std::size_t index_2 = get_real_history_index(index_1); - version_type server_version = get_current_version(); - salt_type version_salt = salt_type(m_version_salts->get(index_2)); - file_ident_type origin_file = file_ident_type(m_origin_files->get(index_2)); - timestamp_type origin_timestamp = timestamp_type(m_timestamps->get(index_2)); - version_type client_version = version_type(m_client_versions->get(index_2)); - std::size_t changeset_size = get_changeset_size(index_1); - out << server_version << " " << version_salt << " " << origin_file - << " " - "" - << origin_timestamp << " " << client_version - << " " - "" - << changeset_size << "\n"; // Throws - } - - void print_annotated_info(std::ostream& out, TimestampFormatter& timestamp_formatter) const override final - { - std::size_t index_1 = get_history_entry_index(); // Throws - std::size_t index_2 = get_real_history_index(index_1); - version_type server_version = get_current_version(); - salt_type version_salt = salt_type(m_version_salts->get(index_2)); - file_ident_type origin_file = file_ident_type(m_origin_files->get(index_2)); - const char* origin = (origin_file == 0 ? "local" : "remote"); - timestamp_type origin_timestamp = timestamp_type(m_timestamps->get(index_2)); - std::time_t time = 0; - long nanoseconds = 0; - sync::map_changeset_timestamp(origin_timestamp, time, nanoseconds); - version_type client_version = version_type(m_client_versions->get(index_2)); - std::size_t changeset_size = get_changeset_size(index_1); - out << "Produced server version: " << server_version - << "\n" - "Server version salt: " - << version_salt - << "\n" - "Identifier of origin file: " - << origin_file << " (" << origin - << " origin)\n" - "Origin timestamp: " - << origin_timestamp - << " " - "(" - << timestamp_formatter.format(time, nanoseconds) - << ")\n" - "Last integrated client version: " - << client_version - << "\n" - "Changeset size: " - << changeset_size << "\n"; // Throws - } - - void get_changeset(util::AppendBuffer& buffer) const override final - { - std::size_t index = get_history_entry_index(); // Throws - if (m_reciprocal) { - bool coverage = (index < m_recip_hist_size); - if (coverage && ::get_changeset(*m_recip_hist, index, buffer)) // Throws - return; - index = m_recip_hist_offset + index; - } - ::get_changeset(*m_changesets, index, buffer); // Throws - } - -private: - const int m_schema_version; - Array m_root; - std::unique_ptr m_version_salts; - std::unique_ptr m_origin_files; - std::unique_ptr m_client_versions; - std::unique_ptr m_timestamps; - std::unique_ptr m_changesets; - std::unique_ptr m_recip_hist; - std::size_t m_recip_hist_offset = 0; - std::size_t m_recip_hist_size = 0; - bool m_reciprocal = false; - - std::size_t get_real_history_index(std::size_t index) const noexcept - { - return (m_reciprocal ? m_recip_hist_offset + index : index); - } - - std::size_t get_changeset_size(std::size_t index) const noexcept - { - std::size_t size = 0; - if (m_reciprocal) { - bool coverage = (index < m_recip_hist_size); - if (coverage && ::get_changeset_size(*m_recip_hist, index, size)) - return size; - index = m_recip_hist_offset + index; - } - ::get_changeset_size(*m_changesets, index, size); - return size; - } -}; - - -class ServerClientFilesCursor_6_to_10 : public RegularClientFilesCursor { -public: - ServerClientFilesCursor_6_to_10(Allocator& alloc, ref_type root_ref, int schema_version) - : m_root{alloc} - { - REALM_ASSERT(schema_version >= 6 && schema_version <= 10); - - if (root_ref == 0) - return; - - // Size of fixed-size arrays - std::size_t root_size = 11; - std::size_t client_files_size = 8; - std::size_t sync_history_size = 6; - if (schema_version < 8) { - root_size = 10; - client_files_size = 6; - } - else if (schema_version < 10) { - client_files_size = 7; - } - - // Slots in root array of history compartment - std::size_t client_files_iip = 0; - std::size_t history_base_version_iip = 1; - std::size_t sync_history_iip = 3; - std::size_t upstream_status_iip = 6; - std::size_t partial_sync_iip = 7; - - // Slots in root array of `client_files` table - std::size_t cf_ident_salts_iip = 0; - std::size_t cf_client_versions_iip = 1; - std::size_t cf_rh_base_versions_iip = 2; - std::size_t cf_proxy_files_iip = 4; - std::size_t cf_client_types_iip = 5; - std::size_t cf_last_seen_timestamps_iip = 6; - std::size_t cf_locked_server_versions_iip = 7; - if (schema_version < 10) { - cf_client_types_iip = std::size_t(-1); - cf_last_seen_timestamps_iip = 5; - cf_locked_server_versions_iip = 6; - } - - // Slots in root array of `sync_history` table - std::size_t sh_version_salts_iip = 0; - - // Slots in root array of `upstream_status` - std::size_t us_client_file_ident_iip = 0; - - // Slots in root array of `partial_sync` - std::size_t ps_partial_file_ident_iip = 0; - - m_root.init_from_ref(root_ref); - if (m_root.size() != root_size) - throw std::runtime_error("Unexpected size of root array of history compartment"); - Array client_files{alloc}; - client_files.init_from_ref(m_root.get_as_ref_or_tagged(client_files_iip).get_as_ref()); - if (client_files.size() != client_files_size) - throw std::runtime_error("Unexpected size of root array of `client_files` table"); - { - m_ident_salts.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = client_files.get_as_ref(cf_ident_salts_iip); - m_ident_salts->init_from_ref(ref); // Throws - } - { - m_client_versions.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = client_files.get_as_ref(cf_client_versions_iip); - m_client_versions->init_from_ref(ref); // Throws - } - { - m_rh_base_versions.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = client_files.get_as_ref(cf_rh_base_versions_iip); - m_rh_base_versions->init_from_ref(ref); // Throws - } - { - m_proxy_files.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = client_files.get_as_ref(cf_proxy_files_iip); - m_proxy_files->init_from_ref(ref); // Throws - } - if (schema_version >= 10) { - m_client_types.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = client_files.get_as_ref(cf_client_types_iip); - m_client_types->init_from_ref(ref); // Throws - } - { - m_last_seen_timestamps.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = client_files.get_as_ref(cf_last_seen_timestamps_iip); - m_last_seen_timestamps->init_from_ref(ref); // Throws - } - if (schema_version >= 8) { - m_locked_server_versions.reset(new IntegerBpTree(alloc)); // Throws - ref_type ref = client_files.get_as_ref(cf_locked_server_versions_iip); - m_locked_server_versions->init_from_ref(ref); // Throws - } - m_size = m_ident_salts->size(); - REALM_ASSERT(m_client_versions->size() == m_size); - REALM_ASSERT(m_rh_base_versions->size() == m_size); - REALM_ASSERT(m_proxy_files->size() == m_size); - REALM_ASSERT(!m_client_types || m_client_types->size() == m_size); - REALM_ASSERT(m_last_seen_timestamps->size() == m_size); - REALM_ASSERT(!m_locked_server_versions || m_locked_server_versions->size() == m_size); - - { - Array sync_history{alloc}; - sync_history.init_from_ref(m_root.get_as_ref_or_tagged(sync_history_iip).get_as_ref()); - if (sync_history.size() != sync_history_size) - throw std::runtime_error("Unexpected size of root array of `sync_history` table"); - IntegerBpTree version_salts{alloc}; // Throws - ref_type ref = sync_history.get_as_ref(sh_version_salts_iip); - version_salts.init_from_ref(ref); // Throws - std::size_t history_size = version_salts.size(); - version_type base_version = - version_type(m_root.get_as_ref_or_tagged(history_base_version_iip).get_as_int()); - m_last_version = version_type(base_version + history_size); - } - - // Find the client file entry that corresponds to ourselves - { - ref_type upstream_status_ref = m_root.get_as_ref(upstream_status_iip); - ref_type partial_sync_ref = m_root.get_as_ref(partial_sync_iip); - REALM_ASSERT_RELEASE(upstream_status_ref == 0 || partial_sync_ref == 0); - if (upstream_status_ref != 0) { - Array us{alloc}; - us.init_from_ref(upstream_status_ref); - if (file_ident_type file_ident = file_ident_type(us.get(us_client_file_ident_iip))) - m_self = file_ident; - } - else if (partial_sync_ref != 0) { - Array ps{alloc}; - ps.init_from_ref(partial_sync_ref); - m_self = file_ident_type(ps.get(ps_partial_file_ident_iip)); - } - } - } - - LogicalClientType get_logical_client_type() const override final - { - std::size_t index = get_client_file_index(); // Throws - if (REALM_UNLIKELY(index == 0)) - return LogicalClientType::special; - if (REALM_UNLIKELY(index == 1)) { - file_ident_type client_file_ident = file_ident_type(index); - return (client_file_ident == m_self ? LogicalClientType::self : LogicalClientType::upstream); - } - switch (get_client_type(index)) { - case ClientType::upstream: - return LogicalClientType::upstream; - case ClientType::self: - return LogicalClientType::self; - case ClientType::indirect: - return LogicalClientType::indirect; - case ClientType::legacy: - return LogicalClientType::legacy; - case ClientType::regular: - return LogicalClientType::regular; - case ClientType::subserver: - return LogicalClientType::subserver; - } - REALM_ASSERT(false); - return {}; - } - - ClientType get_client_type() const override final - { - std::size_t index = get_client_file_index(); // Throws - return get_client_type(index); - } - - std::time_t get_last_seen_timestamp() const override final - { - std::size_t index = get_client_file_index(); // Throws - std::int_fast64_t value = m_last_seen_timestamps->get(index); - return std::time_t(value); - } - - version_type get_locked_version() const override final - { - std::size_t index = get_client_file_index(); // Throws - std::int_fast64_t value_1 = m_rh_base_versions->get(index); - std::int_fast64_t value_2 = m_locked_server_versions->get(index); - return std::min(version_type(value_1), version_type(value_2)); - } - - void print_annotated_info(std::ostream& out, TimestampFormatter& timestamp_formatter) const override final - { - std::size_t client_file_index = get_client_file_index(); // Throws - SaltedFileIdent client_file_ident = get_client_file_ident(); // Throws - UploadCursor upload_progress = get_upload_progress(); // Throws - version_type locked_server_version = get_locked_server_version(); // Throws - file_ident_type proxy_file = get_proxy_file(); // Throws - ClientType client_type = get_client_type(client_file_index); - std::time_t last_seen_timestamp = get_last_seen_timestamp(); // Throws - std::string client_description = describe_client(client_file_ident.ident, client_type, proxy_file); // Throws - out << "Client file identifier: " << client_file_ident.ident - << "\n" - "File identifier salt: " - << client_file_ident.salt - << "\n" - "Last integrated client version: " - << upload_progress.client_version - << "\n" - "Reciprocal history base version: " - "" - << upload_progress.last_integrated_server_version - << "\n" - "Locked server version: " - << locked_server_version - << "\n" - "Identifier of proxy file: " - << proxy_file - << "\n" - "Client type: " - << int(client_type) << " (" << client_description - << ")\n" - "Last seen timestamp: " - << last_seen_timestamp; // Throws - if (_impl::ServerHistory::is_direct_client(client_type)) { - out << " "; - bool is_expired = (last_seen_timestamp == 0); - if (is_expired) { - out << "(expired)"; // Throws - } - else { - out << "(" << timestamp_formatter.format(last_seen_timestamp, 0) << ")"; // Throws - } - } - out << "\n"; // Throws - } - -private: - Array m_root; - std::unique_ptr m_ident_salts; - std::unique_ptr m_client_versions; - std::unique_ptr m_rh_base_versions; - std::unique_ptr m_proxy_files; - std::unique_ptr m_client_types; - std::unique_ptr m_last_seen_timestamps; - std::unique_ptr m_locked_server_versions; - version_type m_last_version = 0; - file_ident_type m_self = 1; - - SaltedFileIdent get_client_file_ident() const - { - std::size_t index = get_client_file_index(); // Throws - file_ident_type file_ident = file_ident_type(index); - salt_type ident_salt = salt_type(m_ident_salts->get(index)); - return {file_ident, ident_salt}; - } - - UploadCursor get_upload_progress() const - { - std::size_t index = get_client_file_index(); // Throws - version_type client_version = version_type(m_client_versions->get(index)); - version_type rh_base_version = version_type(m_rh_base_versions->get(index)); - return {client_version, rh_base_version}; - } - - version_type get_locked_server_version() const - { - std::size_t index = get_client_file_index(); // Throws - if (m_locked_server_versions) - return version_type(m_locked_server_versions->get(index)); - return m_last_version; - } - - file_ident_type get_proxy_file() const - { - std::size_t index = get_client_file_index(); // Throws - return file_ident_type(m_proxy_files->get(index)); - } - - ClientType get_client_type(std::size_t client_file_index) const noexcept - { - if (m_client_types) - return ClientType(m_client_types->get(client_file_index)); - if (client_file_index < 2) - return ClientType(0); - if (file_ident_type(client_file_index) == m_self) - return ClientType::self; - auto ident_salt = salt_type(m_ident_salts->get(client_file_index)); - if (ident_salt != 0) - return ClientType::legacy; - auto proxy_file = file_ident_type(m_proxy_files->get(client_file_index)); - if (proxy_file != 0) - return ClientType::indirect; - return ClientType::upstream; - } - - std::string describe_client(file_ident_type client_file_ident, ClientType client_type, - file_ident_type proxy_file) const - { - if (client_file_ident == 0) { - REALM_ASSERT(client_type == ClientType(0)); - return "special"; // Throws - } - if (client_file_ident == 1) { - REALM_ASSERT(client_type == ClientType(0)); - if (client_file_ident == m_self) - return "self"; // Throws - return "root of star topology server cluster"; // Throws - } - switch (client_type) { - case ClientType::upstream: - break; - case ClientType::self: - return "self"; // Throws - case ClientType::indirect: { - REALM_ASSERT(proxy_file != 0); - auto proxy_file_index = std::size_t(proxy_file); - auto proxy_file_type = get_client_type(proxy_file_index); - return "client of " + describe_client(proxy_file, proxy_file_type, 0); // Throws - } - case ClientType::legacy: - return "legacy entry"; // Throws - case ClientType::regular: - return "regular client"; // Throws - case ClientType::subserver: - return "subserver"; // Throws - } - return "reachable via upstream server"; // Throws - } -}; - - -class ServerCursorFactory_6_to_10 : public RegularCursorFactory { -public: - ServerCursorFactory_6_to_10(Allocator& alloc, ref_type root_ref, int schema_version) - : m_alloc{alloc} - , m_root_ref{root_ref} - , m_schema_version{schema_version} - { - } - -protected: - std::unique_ptr do_create_history_cursor() override - { - return std::make_unique(m_alloc, m_root_ref, - m_schema_version); // Throws - } - - std::unique_ptr do_create_client_files_cursor() override - { - return std::make_unique(m_alloc, m_root_ref, - m_schema_version); // Throws - } - -private: - Allocator& m_alloc; - const ref_type m_root_ref; - const int m_schema_version; -}; - - -void inspect_history(SyncHistoryCursor& cursor, util::Optional origin_file, Expr* expression, - Format format, Summary summary, bool with_versions, std::ostream& out) -{ - - TimestampFormatter::Config timestamp_config; - timestamp_config.precision = TimestampFormatter::Precision::milliseconds; - TimestampFormatter timestamp_formatter{timestamp_config}; - util::AppendBuffer buffer; - std::size_t num_history_entries = 0; - version_type min_version = std::numeric_limits::max(); - version_type max_version = std::numeric_limits::min(); - timestamp_type min_timestamp = std::numeric_limits::max(); - timestamp_type max_timestamp = std::numeric_limits::min(); - while (cursor.next()) { - version_type version = cursor.get_version(); // Throws - if (REALM_UNLIKELY(origin_file)) { - if (REALM_LIKELY(cursor.get_origin_file() != *origin_file)) - continue; - } - if (REALM_UNLIKELY(expression)) { - buffer.clear(); - cursor.get_changeset(buffer); // Throws - util::SimpleInputStream in{buffer}; - size_t decompressed_size; - auto decompressed = util::compression::decompress_nonportable_input_stream(in, decompressed_size); - sync::Changeset changeset; - sync::parse_changeset(*decompressed, changeset); // Throws - expression->reset(changeset); - InstructionMatcher matcher{*expression}; - bool instr_was_found = false; - for (auto instr : changeset) { - REALM_ASSERT(instr); - bool did_match = instr->visit(matcher); - if (REALM_LIKELY(!did_match)) - continue; - instr_was_found = true; - break; - } - if (REALM_LIKELY(!instr_was_found)) - continue; - } - switch (format) { - case Format::auto_: { - REALM_ASSERT(false); - break; - } - case Format::nothing: { - break; - } - case Format::version: { - out << version << "\n"; // Throws - break; - } - case Format::info: { - cursor.print_info(out); // Throws - break; - } - case Format::annotate: { - if (num_history_entries > 0) - out << "\n"; - cursor.print_annotated_info(out, timestamp_formatter); // Throws - break; - } - case Format::changeset: { - if (with_versions) - out << "# Version " << version << "\n"; // Throws - buffer.clear(); - cursor.get_changeset(buffer); // Throws - util::SimpleInputStream in{buffer}; - size_t decompressed_size; - auto decompressed = util::compression::decompress_nonportable_input_stream(in, decompressed_size); - sync::Changeset changeset; - sync::parse_changeset(*decompressed, changeset); // Throws -#if REALM_DEBUG - changeset.print(out); // Throws -#else - REALM_ASSERT(false); -#endif - break; - } - case Format::hexdump: { - if (with_versions) - out << version << " "; // Throws - buffer.clear(); - cursor.get_changeset(buffer); // Throws - out << util::hex_dump(buffer.data(), buffer.size()) << "\n"; // Throws - break; - } - case Format::raw: { - buffer.clear(); - cursor.get_changeset(buffer); // Throws - out.write(buffer.data(), buffer.size()); // Throws - break; - } - } - ++num_history_entries; - if (REALM_UNLIKELY(version < min_version)) - min_version = version; - if (REALM_UNLIKELY(version > max_version)) - max_version = version; - if (summary == Summary::full) { - timestamp_type timestamp = cursor.get_origin_timestamp(); // Throws - if (REALM_UNLIKELY(timestamp < min_timestamp)) - min_timestamp = timestamp; - if (REALM_UNLIKELY(timestamp > max_timestamp)) - max_timestamp = timestamp; - } - } - - if (format == Format::annotate && summary != Summary::off && num_history_entries > 0) - out << "\n"; - - switch (summary) { - case Summary::auto_: - REALM_ASSERT(false); - break; - case Summary::off: - break; - case Summary::brief: - out << format_num_history_entries(num_history_entries); - if (num_history_entries > 0) { - out << " (version " << (min_version - 1) - << " -> " - "" - << max_version << ")"; // Throws - } - out << "\n"; // Throws - break; - case Summary::full: - out << "Number of selected history entries: " << num_history_entries << "\n"; - if (num_history_entries > 0) { - std::time_t min_time = 0, max_time = 0; - long min_nanoseconds = 0, max_nanoseconds = 0; - sync::map_changeset_timestamp(min_timestamp, min_time, min_nanoseconds); - sync::map_changeset_timestamp(max_timestamp, max_time, max_nanoseconds); - out << "Version range: " << (min_version - 1) - << " -> " - "" - << max_version - << "\n" - "Time range: " - "" - << timestamp_formatter.format(min_time, min_nanoseconds) - << " -> " - "" - << timestamp_formatter.format(max_time, max_nanoseconds) - << " " - "(unreliable)\n"; // Throws - } - break; - } -} - - -void inspect_client_files(ClientFilesCursor& cursor, std::ostream& out, - const std::set& client_file_types, bool unexpired_client_files, - bool expired_client_files, std::time_t min_last_seen_timestamp, - std::time_t max_last_seen_timestamp, version_type max_locked_version) -{ - TimestampFormatter timestamp_formatter; - std::size_t num_client_files = 0; - std::time_t min_timestamp = std::numeric_limits::max(); - std::time_t max_timestamp = 0; - while (cursor.next()) { - LogicalClientType logical_client_type = cursor.get_logical_client_type(); // Throws - if (client_file_types.count(logical_client_type) == 0) - continue; - std::time_t last_seen_timestamp = cursor.get_last_seen_timestamp(); // Throws - bool is_unexpired = (last_seen_timestamp > 0); - ClientType client_type = cursor.get_client_type(); // Throws - if (_impl::ServerHistory::is_direct_client(client_type)) { - if (is_unexpired) { - if (!unexpired_client_files) - continue; - if (last_seen_timestamp < min_last_seen_timestamp) - continue; - if (last_seen_timestamp > max_last_seen_timestamp) - continue; - if (max_locked_version < std::numeric_limits::max()) { - version_type locked_version = cursor.get_locked_version(); - if (locked_version > max_locked_version) - continue; - } - } - else { - if (!expired_client_files) - continue; - } - } - if (num_client_files > 0) - out << "\n"; - cursor.print_annotated_info(out, timestamp_formatter); // Throws - ++num_client_files; - if (is_unexpired) { - if (REALM_UNLIKELY(last_seen_timestamp < min_timestamp)) - min_timestamp = last_seen_timestamp; - if (REALM_UNLIKELY(last_seen_timestamp > max_timestamp)) - max_timestamp = last_seen_timestamp; - } - } - if (num_client_files > 0) - out << "\n"; - out << "Number of selected client files: " << num_client_files << "\n"; - bool have_timestamp_range = (max_timestamp > 0); - if (have_timestamp_range) { - out << "Range of last seen timestamps: " - "" - << min_timestamp << " (" << timestamp_formatter.format(min_timestamp, 0) - << ") -> " - "" - << max_timestamp << " (" << timestamp_formatter.format(max_timestamp, 0) << ")\n"; // Throws - } -} - -} // unnamed namespace - - -int main(int argc, char* argv[]) -{ - bool client_files = false; - int commandline_form = 0; - std::string realm_path; - version_type begin_version = 0, end_version = 0; - FormatEnum format = Format::auto_; - SummaryEnum summary = Summary::auto_; - bool with_versions = false; - util::Optional reciprocal; - util::Optional origin_file; - std::string class_name; - GlobalKey object_id; - std::string property; - std::unique_ptr expression; - file_ident_type client_file = 0; - std::set client_file_types = {LogicalClientType::regular, LogicalClientType::subserver, - LogicalClientType::legacy}; - bool unexpired_client_files = true; - bool expired_client_files = false; - std::time_t min_last_seen_timestamp = std::numeric_limits::min(); - std::time_t max_last_seen_timestamp = std::numeric_limits::max(); - version_type max_locked_version = std::numeric_limits::max(); - ; - std::string encryption_key; - - // Process command-line - { - const char* prog = argv[0]; - --argc; - ++argv; - bool error = false; - bool help = false; - bool version = false; - int argc_2 = 0; - int i = 0; - char* arg = nullptr; - auto get_string_value = [&](std::string& var) { - if (i < argc) { - var = argv[i++]; - return true; - } - return false; - }; - auto get_parsed_value_with_check = [&](auto& var, auto check_val) { - std::string str_val; - if (get_string_value(str_val)) { - std::istringstream in(str_val); - in.imbue(std::locale::classic()); - in.unsetf(std::ios_base::skipws); - using value_type = typename std::remove_reference::type; - value_type val = value_type{}; - in >> val; - using traits = std::istringstream::traits_type; - if (in && in.peek() == traits::eof() && check_val(val)) { - var = val; - return true; - } - } - return false; - }; - auto get_parsed_value = [&](auto& var) { - return get_parsed_value_with_check(var, [](auto) { - return true; - }); - }; - auto add_expr = [&](std::unique_ptr e) { - if (expression) - e = std::make_unique(std::move(expression), std::move(e)); - expression = std::move(e); - }; - while (i < argc) { - arg = argv[i++]; - if (arg[0] != '-') { - argv[argc_2++] = arg; - continue; - } - if (std::strcmp(arg, "-c") == 0 || std::strcmp(arg, "--client-files") == 0) { - if (argc_2 == 1) { - client_files = true; - } - else { - std::cerr << "ERROR: Unexpected command-line argument: " << arg << "\n"; - error = true; - } - continue; - } - else if (std::strcmp(arg, "-h") == 0 || std::strcmp(arg, "--help") == 0) { - help = true; - continue; - } - else if (std::strcmp(arg, "-f") == 0 || std::strcmp(arg, "--format") == 0) { - if (get_parsed_value(format)) - continue; - } - else if (std::strcmp(arg, "-s") == 0 || std::strcmp(arg, "--summary") == 0) { - if (get_parsed_value(summary)) - continue; - } - else if (std::strcmp(arg, "-V") == 0 || std::strcmp(arg, "--with-versions") == 0) { - with_versions = true; - continue; - } - else if (std::strcmp(arg, "-r") == 0 || std::strcmp(arg, "--reciprocal") == 0) { - file_ident_type value; - if (get_parsed_value(value)) { - reciprocal = value; - continue; - } - } - else if (std::strcmp(arg, "-a") == 0 || std::strcmp(arg, "--origin-file") == 0) { - file_ident_type value; - if (get_parsed_value(value)) { - origin_file = value; - continue; - } - } - else if (std::strcmp(arg, "-I") == 0 || std::strcmp(arg, "--instruction-type") == 0) { - InstructionTypeEnum value; - if (get_parsed_value(value)) { - auto expr = std::make_unique(value); - add_expr(std::move(expr)); - continue; - } - } - else if (std::strcmp(arg, "-C") == 0 || std::strcmp(arg, "--class") == 0) { - std::string value; - if (get_string_value(value)) { - class_name = std::move(value); - continue; - } - } - else if (std::strcmp(arg, "-O") == 0 || std::strcmp(arg, "--object") == 0) { - GlobalKey value; - if (get_parsed_value(value)) { - object_id = std::move(value); - continue; - } - } - else if (std::strcmp(arg, "-P") == 0 || std::strcmp(arg, "--property") == 0) { - std::string value; - if (get_string_value(value)) { - property = std::move(value); - continue; - } - } - else if (std::strcmp(arg, "-m") == 0 || std::strcmp(arg, "--modifies-object") == 0) { - auto expr = std::make_unique(class_name, object_id); - add_expr(std::move(expr)); - continue; - } - else if (std::strcmp(arg, "-p") == 0 || std::strcmp(arg, "--modifies-property") == 0) { - auto expr = std::make_unique(class_name, object_id, property); - add_expr(std::move(expr)); - continue; - } - else if (std::strcmp(arg, "-l") == 0 || std::strcmp(arg, "--links-to-object") == 0) { - auto expr = std::make_unique(class_name, object_id); - add_expr(std::move(expr)); - continue; - } - else if (std::strcmp(arg, "-A") == 0 || std::strcmp(arg, "--all-client-files") == 0) { - all_client_files(client_file_types); - unexpired_client_files = true; - expired_client_files = true; - continue; - } - else if (std::strcmp(arg, "-T") == 0 || std::strcmp(arg, "--client-file-types") == 0) { - std::string value; - if (get_string_value(value)) { - if (parse_client_types(value, client_file_types)) - continue; - } - } - else if (std::strcmp(arg, "-E") == 0 || std::strcmp(arg, "--also-expired-client-files") == 0) { - unexpired_client_files = true; - expired_client_files = true; - continue; - } - else if (std::strcmp(arg, "-F") == 0 || std::strcmp(arg, "--only-expired-client-files") == 0) { - unexpired_client_files = false; - expired_client_files = true; - continue; - } - else if (std::strcmp(arg, "-U") == 0 || std::strcmp(arg, "--only-unexpired-client-files") == 0) { - unexpired_client_files = true; - expired_client_files = false; - continue; - } - else if (std::strcmp(arg, "-M") == 0 || std::strcmp(arg, "--min-last-seen-timestamp") == 0) { - std::time_t value; - if (get_parsed_value(value)) { - min_last_seen_timestamp = value; - continue; - } - } - else if (std::strcmp(arg, "-N") == 0 || std::strcmp(arg, "--max-last-seen-timestamp") == 0) { - std::time_t value; - if (get_parsed_value(value)) { - max_last_seen_timestamp = value; - continue; - } - } - else if (std::strcmp(arg, "-L") == 0 || std::strcmp(arg, "--max-locked-version") == 0) { - version_type value; - if (get_parsed_value(value)) { - max_locked_version = value; - continue; - } - } - else if (std::strcmp(arg, "-e") == 0 || std::strcmp(arg, "--encryption-key") == 0) { - if (get_string_value(encryption_key)) - continue; - } - else if (std::strcmp(arg, "-v") == 0 || std::strcmp(arg, "--version") == 0) { - version = true; - continue; - } - std::cerr << "ERROR: Bad or missing value for command-line option: " << arg << "\n"; - error = true; - } - argc = argc_2; - - i = 0; - if (!get_string_value(realm_path)) { - error = true; - } - else if (i + 0 == argc) { - commandline_form = 1; - } - else if (i + 1 == argc) { - commandline_form = 2; - if (!client_files) { - if (!get_parsed_value(end_version)) - error = true; - } - else { - if (!get_parsed_value(client_file)) - error = true; - } - } - else if (i + 2 == argc && !client_files) { - commandline_form = 3; - if (!get_parsed_value(begin_version)) - error = true; - if (!error && !get_parsed_value(end_version)) - error = true; - } - else { - std::cerr << "ERROR: Too many command-line arguments\n"; - error = true; - } - - bool bad_combination = (reciprocal && client_files); - if (bad_combination) - error = true; - - if (help) { - // clang-format off - std::cerr << - "Synopsis: "<\n" - " "< \n" - " "< \n" - " "< (-c | --client-files)\n" - " "< (-c | --client-files) \n" - "\n" - "The first three forms are for inspecting a specific range of the\n" - "synchronization history of the specified Realm file. In the first form, the\n" - "range is the entire history. In the second form, the range is the one history\n" - "entry whose changeset produced the specifed synchronization version. In the\n" - "third form, the range is as specified.\n" - "\n" - "The last two forms are for inspecting the client files registry of a server-\n" - "side file. In the first of these two forms, information about all registered\n" - "client files is shown (subject to `--all-client-files`). In the last form,\n" - "information is shown only for the client file identified by the specified\n" - "client file identifier.\n" - "\n" - "Options:\n" - " -h, --help Display command-line synopsis followed by the list of\n" - " available options.\n" - " -f, --format What to output for each selected history entry. The\n" - " value can be `auto` (default), `nothing`, `version`,\n" - " `info`, `annotate`, `changeset`, `hexdump`, or `raw`.\n" - " When the value is `auto`, the effective value is\n" - " `nothing` in the 1st and 3rd command-line forms, and\n" - " `annotate` in the 2nd command-line form. `annotate`\n" - " shows information that is stored in each history entry,\n" - " but not the changeset itself. `info` shows the same\n" - " information, and in the same order as `annotate`, but\n" - " using only a single line per history entry, and without\n" - " annotations. `version` shows only the synchronization\n" - " version produced by the changeset of each of the\n" - " selected history entries. `hexdump` shows a hex dump of\n" - " the changeset (one line per history entry). `changeset`\n" - " shows the changeset in a human-readable form (only\n" - " available when tool is built in debug mode).\n" - " -s, --summary What to output as a final summary. The value can be\n" - " `auto` (default), `off`, `brief`, or `full`. When the\n" - " value is `auto`, the effective value is `brief` if\n" - " `--format` is effectively `nothing`, `annotate`, or\n" - " `changeset`. Otherwise it is `off`.\n" - " -V, --with-versions When `--format` is `changeset` or `hexdump`, also show\n" - " which version is produced by each of the selected\n" - " changesets.\n" - " -r, --reciprocal \n" - " Instead of inspecting the main history, inspect instead\n" - " the reciprocal history for the reciprocal file\n" - " identified by . With client-side files, this\n" - " must be zero, and the implied reciprocal file is the\n" - " server-side file.\n" - " -a, --origin-file \n" - " Only include history entries whose changeset originated\n" - " from the file identified by .\n" - " -I, --instruction-type \n" - " Only include history entries whose changeset contains an\n" - " instruction of the specified type. See header file\n" - " `` for the list of\n" - " instruction types. This acts as an additional\n" - " instruction condition. See `--modifies-object` for more\n" - " on instruction conditions.\n" - " -C, --class The class name that applies when specifying various\n" - " instruction conditions, such as `--modifies-object`.\n" - " -O, --object \n" - " The object identifier that applies when specifying\n" - " various instruction conditions, such as\n" - " `--modifies-object`. An object identifier is a pair of\n" - " integers in hexadecimal form separated by a hyphen (`-`)\n" - " and enclosed in curly braces. It could be `{5-17A}`, for\n" - " example.\n" - " -P, --property The property name that applies when specifying various\n" - " instruction conditions, such as `--modifies-property`.\n" - " -m, --modifies-object Only include history entries that contain an\n" - " instruction that modifies the object specified by\n" - " `--class` and `--object`. This acts as an additional\n" - " instruction condition. When at least one instruction\n" - " condition is specified (`--instruction-type`,\n" - " `--modifies-object`, `--modifies-property`, or\n" - " `--links-to-object`), a changeset is included only if an\n" - " instruction can be found in that changeset, that\n" - " satisfies all the specified instruction conditions.\n" - " -p, --modifies-property\n" - " Only include history entries that contain an instruction\n" - " that modifies the property specified by `--class`,\n" - " `--object`, and `--property`. This acts as an additional\n" - " instruction condition. See `--modifies-object` for more\n" - " on instruction conditions.\n" - " -l, --links-to-object Only include history entries that contain an\n" - " instruction that establishes a link to the object\n" - " specified by `--class` and `--object`. This acts as an\n" - " additional instruction condition. See\n" - " `--modifies-object` for more on instruction conditions.\n" - " -A, --all-client-files Include all types of client file entries. Equivalent\n" - " to passing `rspliuSU` to `--client-file-types` and also\n" - " specifying `--also-expired-client-files`.\n" - " -T, --client-file-types \n" - " Specify which types of client file entries to include\n" - " when using the `--client-files` form of this command.\n" - " The argument is a string in which each letter specifies\n" - " that a particular type of client file entries must be\n" - " included. The valid letters are as follows: `r` for\n" - " regular direct clients, `s` for files on direct\n" - " subservers, `p` for direct partial views, `l` for legacy\n" - " entries, `i` for indirect clients (clients of subservers\n" - " or of partial views), `u` for entries reachable via the\n" - " upstream server or via the reference file, `S` for the\n" - " entry representing the file itself, and `U` for the\n" - " special entry used to represent the upstream server,\n" - " when one exists. The default value is `rspl`. This\n" - " option has no effect when a specific client file is\n" - " specified after `--client-files`, i.e., in the 5th form\n" - " shown above. See also `--also-expired-client-files`.\n" - " -E, --also-expired-client-files\n" - " Include both expired and unexpired client file entries\n" - " when using the `--client-files` form of this command. By\n" - " default, only unexpired entries are included. The\n" - " expired / unexpired distinction only applies to types of\n" - " entries associated with direct clients (i.e., `r`, `s`,\n" - " `p`, and `l`). See also `--only-expired-client-files`,\n" - " `--only-unexpired-client-files`, and\n" - " `--client-file-types`.\n" - " -F, --only-expired-client-files\n" - " Include only expired client file entries when using the\n" - " `--client-files` form of this command. See also\n" - " `--also-expired-client-files`.\n" - " -U, --only-unexpired-client-files\n" - " Include only unexpired client file entries when using\n" - " the `--client-files` form of this command. See also\n" - " `--also-expired-client-files`.\n" - " -M, --min-last-seen-timestamp \n" - " Only include entries for direct clients whose\n" - " 'last seen' timestamp is at least (seconds\n" - " since beginning of UNIX epoch). This applies only to\n" - " unexpired entries associated with direct clients (i.e.,\n" - " `r`, `s`, `p`, and `l`). See also `--client-file-types`.\n" - " -N, --max-last-seen-timestamp \n" - " Only include entries for direct clients wose 'last seen'\n" - " timestamp is at most . See also\n" - " `--min-last-seen-timestamp`.\n" - " -L, --max-locked-version \n" - " Only include entries for direct clients where either\n" - " `rh_base_version` or `locked_server_version` is less\n" - " than, or equal to ``. Here, `rh_base_version`\n" - " is the base version of the base version of the\n" - " reciprocal history, and `locked_server_version` is as\n" - " explained in the specification of the UPLOAD message.\n" - " This applies only to unexpired entries associated with\n" - " direct clients (i.e., `r`, `s`, `p`, and `l`). To select\n" - " client file entries which are blocking in-place history\n" - " compaction beyond (until + 1) given\n" - " a particular